学习基础布局完成后基本的 布局层面 可以实现了 但是只学习静态的页面 肯定是不行的要让我们的页面灵活动态的 我们来 学习一下网络请求获取数据
谈及框架并不陌生 MVI 框架核心原理知识
-
Model:表示应用的状态和业务逻辑。通常是不可变的,描述当前视图所需的数据。
-
View:负责展示用户界面,通常是 Activity 或 Fragment。它观察 Model 的变化并根据变化更新 UI。
-
Intent:表示用户的意图或事件,例如按钮点击、输入变化等。用户的交互会被转换成 Intent 以供处理。
MVI 的工作流程
-
用户交互:用户在 UI 上进行操作,这些操作会被转化为 Intent。
-
Intent 处理:应用接收 Intent,更新 Model 的状态并触发状态变更。
-
状态更新:Model 更新后,View 观察到状态变化并重新渲染 UI。
MVI 的优点
- 单向数据流:所有的数据变更都通过 Intent 流向 Model,避免了复杂的状态管理问题。
- 可测试性:由于状态是不可变的,可以更容易地进行单元测试。
- 解耦合:视图和业务逻辑之间的分离,使得维护和扩展变得更加简单。
好了废话不多说 直接上代码
引入依赖
//当前模块 build.Gradle 中 加入hilt 插件
//然后在 总项目 gradle 中 依赖上 (记得添加时使用 加入镜像)
//以下是框架 所需的 依赖 自行 操作 放入对应的位置 即可
MVI框架网络依赖
// okhttp
api 'com.squareup.okhttp3:okhttp:4.12.0'
api 'com.squareup.okhttp3:logging-interceptor:4.12.0'
// retrofit
api 'com.squareup.retrofit2:retrofit:2.11.0'
api 'com.squareup.retrofit2:converter-gson:2.11.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
api "com.google.dagger:hilt-android:2.48"
kapt "com.google.dagger:hilt-android-compiler:2.48"
api 'com.github.zyj1609wz:RetrofitFlowCallAdapter:1.1.0'
api "androidx.hilt:hilt-navigation-compose:1.0.0"
//hilt插件
id 'kotlin-kapt'
id 'dagger.hilt.android.plugin'
build.gradle加入hilt 脚本
buildscript {
dependencies {
classpath 'com.google.dagger:hilt-android-gradle-plugin:2.48'
}
}
首先在 BaseRepository类 写了一个 将map集合 转化为Json 的 方法
open class BaseRepository {
//map集合 转换为json
fun mapToBody(map: Map<String,Any>) :RequestBody{
val json = Gson().toJson(map)
return RequestBody.create("application/json".toMediaType(),json)
Log.i("方法", "BaseRepository: mapToBody() ")
}
}
BaseViewModel 这里用来接受 意图 并且处理
abstract class BaseViewModel<vm:MVIIntent> :ViewModel(){
//存放意图的管道
val channel =Channel<vm>(Channel.UNLIMITED)
//可变的状态用于更新UI
private var varState =MutableStateFlow<MVIState>(MVIState.Loading)
val valState :StateFlow<MVIState>
get() = varState
/** 1. val channel = Channel<vm>(Channel.UNLIMITED)
Channel:Kotlin 协程库中的一个类,用于在协程之间传递数据。它提供了一种安全的方式来实现异步消息传递。
类型参数 <vm>:这表示通道将传递类型为 vm 的数据。具体的 vm 类型取决于上下文,可能是一个视图模型或某个特定的数据类。
Channel.UNLIMITED:这表明通道可以容纳无限数量的元素,不会因为容量限制而阻塞发送操作。这在某些情况下可以提高性能,但也要注意,使用无限通道可能导致内存使用增加。
2. private var varState = MutableStateFlow<MVIState>(MVIState.Loading)
MutableStateFlow:这是 Kotlin 的一种状态流,允许你在应用程序中表示和观察可变状态。与普通的 StateFlow 不同,MutableStateFlow 允许你修改其值。
MVIState:这是一个表示应用状态的类,通常用于模型-视图-意图(MVI)架构。这里的状态初始化为 MVIState.Loading,表示当前应用的加载状态。
private:表明这个变量只能在当前类内部访问,确保了状态的封装性,外部不能直接修改它。
3. val valState: StateFlow<MVIState> get() = varState
valState:这是一个只读属性,返回 StateFlow<MVIState> 类型的状态流,允许外部观察状态但不允许修改。
get() = varState:这是一个自定义的 getter,返回 varState 的引用。这意味着外部可以通过 valState 观察状态,但无法直接修改 varState 的值,从而保持了数据的封装性和一致性。*/
init {
viewModelScope.launch {
channel.receiveAsFlow().collect{
HandlerIntent(it)
}
}
Log.i("方法", "BaseViewModel: init() ")
}
abstract fun HandlerIntent(vm: MVIIntent)
/**
* 这段代码是一个 Kotlin 初始化块,它在 ViewModel 中使用 viewModelScope.launch 启动了一个协程。
* 协程从 channel 中接收数据并转换为流 (Flow),然后对每个接收到的意图 (MVIIntent) 调用 HandlerIntent 函数。
* HandlerIntent 是一个抽象函数,必须在子类中实现,用于处理不同的意图。
* 这种结构通常用于实现 MVI(Model-View-Intent)架构,以便在用户交互时更新视图模型。
*/
fun sendIntent(intent:vm){
viewModelScope.launch {
channel.send(intent)
}
Log.i("方法", "BaseViewModel: sendIntent() ")
}
//网络请求
fun httpRequest(request :Flow<ApiResult<List<Any>>>){
Log.i("方法", "BaseViewModel: httpRequest() ")
viewModelScope.launch {
//指定 协程在 子线程 进行
request.flowOn(Dispatchers.IO).catch {
Log.i("TagA","${it.message.toString()}")
}.collect{ res->
if (res.statusCode==200){
varState.value =MVIState.Success(res.data)
}else{
varState.value =MVIState.Failed(res.msg.toString())
}
}
}
}
}
sealed class MVIState {
//密封类 保证数据 保持单向
//网络 请求 获取的状态
data class Success<out T>(val data :T) :MVIState()
data class Failed(val msg:String):MVIState()
data object Loading:MVIState()
}
接下来 开始 网络 工具类 封装 通过 枚举的 类型 来实现 不同场景的 网络请求头 比如 token sign 等....
//网络请求工具类
object NetUtils {
val client =OkHttpClient.Builder()
.writeTimeout(30,TimeUnit.SECONDS)
.connectTimeout(30,TimeUnit.SECONDS)
.callTimeout(30,TimeUnit.SECONDS)
.readTimeout(30,TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level =HttpLoggingInterceptor.Level.BODY
})
@JvmStatic
var retrofit :Retrofit?=null
fun getInstance(type: HttpType):Retrofit{
when(type) {
HttpType.NONE->{
retrofit = Retrofit.Builder()
.baseUrl("url")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(FlowCallAdapterFactory.create())
.client(client.build())
.build()
}
HttpType.TOKEN->{
retrofit = Retrofit.Builder()
.baseUrl("url")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(FlowCallAdapterFactory.create())
.client(client.addInterceptor{
chain ->
chain.proceed(chain.request().newBuilder().addHeader("token","").build())
}.build())
.build()
}
HttpType.SIGN->{
}
}
return retrofit !!
}
}
调用网络 NetUtils 工具类是 需要 传递 枚举 类型 也可以 自行修改
枚举类
Http API Respnse 所有的网络 相应
接下来 我们 开始 写入 Api接口 (这个 是 获取网络文件数据)
开始 对这个 请求 进行 统一进入 MVI 框架的 整个接口管理
Repo 层 对请求 body 进行一些处理
@OptIn
class FileRepository @Inject constructor() :BaseRepository() {
//将网络 工具 实例化 调用方法 继承自 BaseRepo 转换 map
val apiService by lazy { NetUtils.getInstance(HttpType.NONE).create(ApiService::class.java) }
fun getFileDesc(map :Map<String,Int>) : Flow<ApiResult<List<FileBean>>> {
return apiService.getFileDesc(mapToBody(map))
}
}
VIewModel 一个页面的 通道管理 可以 传递 处理 多个 网络 请求 只需要 在@Composable 方法中 调用 即可
这个 类 继承了 Base 层 并通过 hilt 依赖注入 引入了 调用BaseViewmodel 层 对意图 进行处理 发送
@HiltViewModel
class FileViewModel @Inject constructor(private val fileRepository: FileRepository) :BaseViewModel<FileIntent>(){
override fun HandlerIntent(mviIntent: MVIIntent) {
when(mviIntent){
is FileIntent.getFileDesc->{
httpRequest(fileRepository.getFileDesc(mviIntent.map) as Flow<ApiResult<List<Any>>>)
}
}
}
}
注意 使用 hilt 要在 Application 中 进行初始化 在清单文件中 name 中添加对应的 文件名称
并且 还要在 Activity 中 加入 @AndroidEntryPoint 的 注解 否则报错 无法获取到 请求数据 数据无法 通过viewModel 传递
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
MyMVITheme {
FileShowPager() // 执行网络请求 页面
}
}
}
}
最后 我们 可以 执行网络请求了 在 @Composable 组合函数中 执行
@Composable
fun FileShowPager(vm :FileViewModel= hiltViewModel()){
val list = remember {
SnapshotStateList<FileBean>()
}
LaunchedEffect(key1 = "", block = {
launch {
vm.valState.collect {
when (it) {
is MVIState.Success<*> -> {
Log.i("===", it.data.toString())
list.addAll(it.data as Collection<FileBean>)
}
is MVIState.Failed -> {
Log.i("===", it.msg)
}
else -> {}
}
}
}
//发送意图
vm.sendIntent(FileIntent.getFileDesc(mapOf("userId" to 1, "page" to 1)))
})
//展示网络 列表
LazyHorizontalGrid (GridCells.Fixed(2),
content = {
items(list){
FileItem(item = it)
}
}
)
}
@Composable
fun FileItem(item :FileBean){
Row {
AsyncImage(model = item.filePath, contentDescription ="", modifier = Modifier.size(100.dp) )
Spacer(modifier = Modifier.width(10.dp))
Column {
Text(text = item.fileName)
Text(text = SimpleDateFormat("yyyy-MM -dd hh:mm:ss").format(item.fileTime))
}
}
}
标签:Compose,val,JetPack,hilt,MVIState,fun,Android,class,varState From: https://blog.csdn.net/2301_79780572/article/details/143448218