首页 > 其他分享 >Android JetPack Compose MVI 框架

Android JetPack Compose MVI 框架

时间:2024-11-05 13:18:36浏览次数:3  
标签:Compose val JetPack hilt MVIState fun Android class varState

学习基础布局完成后基本的 布局层面 可以实现了 但是只学习静态的页面 肯定是不行的要让我们的页面灵活动态的 我们来 学习一下网络请求获取数据

谈及框架并不陌生 MVI 框架核心原理知识

  1. Model:表示应用的状态和业务逻辑。通常是不可变的,描述当前视图所需的数据。

  2. View:负责展示用户界面,通常是 Activity 或 Fragment。它观察 Model 的变化并根据变化更新 UI。

  3. Intent:表示用户的意图或事件,例如按钮点击、输入变化等。用户的交互会被转换成 Intent 以供处理。

MVI 的工作流程

  1. 用户交互:用户在 UI 上进行操作,这些操作会被转化为 Intent。

  2. Intent 处理:应用接收 Intent,更新 Model 的状态并触发状态变更。

  3. 状态更新: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

相关文章

  • 解决Android Studio项目初始化下载gradle过慢问题
    解决方法将谷歌官方源替换为国内阿里云或者腾讯云源解决方案替换掉谷歌原地址:官网地址:https://services.gradle.org/distributions/阿里云镜像Gradle下载地址:https://mirrors.aliyun.com/macports/distfiles/gradle/腾讯镜像Gradle下载地址:https://mirrors.cloud.tencent......
  • 如何从 Android 图库中恢复误删除的照片
    如果您正在阅读这篇文章,那么您肯定意外地从Android设备中删除了照片。并且您正在寻找一种简单的方法来恢复Android图库中已删除的照片。从图库恢复已删除的照片随着技术的进步,现在使用单个设备(即Android手机),您就可以捕捉图像、根据需要编辑图像、高效管理图像、存储图......
  • 一文了解Android SELinux
    在Android系统中,SELinux(Security-EnhancedLinux)是一个增强的安全机制,用于对系统进行强制访问控制(MandatoryAccessControl,MAC)。它限制了应用程序和进程的访问权限,提供了更高的安全性,以防止未经授权的访问。SELinux的引入是为了提升Android系统的安全防护能力,尤其是在面对......
  • Android 加密知识详解
    在Android开发中,加密技术是确保数据安全的重要手段。本文将详细介绍几种常见的加密文件格式(如PKCS#12、JKS、BKS)及其用途,以及常用的加密方法(如X.509证书和SHA哈希函数),并结合实际应用场景进行讲解。1.keytool和OpenSSL的使用及下载方式1.1keytoolkeytool是Java开......
  • Android的自定义View和自定义ViewGroup
    Android自定义视图(View)和视图组(ViewGroup)详解在Android开发中,有时候我们需要创建一些标准控件无法满足需求的自定义视图(View)和视图组(ViewGroup)。本文将详细介绍如何创建自定义视图和视图组,包括构造方法、自定义属性、绘制逻辑、测量逻辑、布局逻辑和设置布局参数等内容。1.......
  • Android Studio启动安卓模拟器失败,出现The emulator process for AVD Medium_Phone_AP
    前言软件版本已安装的SDKTools包。AndroidStudio安装设置Proxy代理问题。可在此处设置代理,可在本窗口的左下角的CheckConnection处进行检测链接的有效性。也可以查看以下地址,设置代理的地址:阿里云Android仓库清华大学开源软件镜像站模拟器问题如果你在这里运行......
  • 开发 Android 应用时,可以使用以下几种编程语言:
    开发Android应用时,可以使用以下几种编程语言:###1.**Java**-**描述**:Java是Android开发的传统语言,广泛使用,并且有丰富的文档和社区支持。-**特点**:面向对象,适合大型应用开发。AndroidSDK和大多数库均支持Java。###2.**Kotlin**-**描述**:Kotlin是官方推荐的Andro......
  • 基于node.js+vue基于Android的快递管理系统的设计与实现前(开题+程序+论文)计算机毕业设
    本系统(程序+源码+数据库+调试部署+开发环境)带文档lw万字以上,文末可获取源码系统程序文件列表开题报告内容一、选题背景随着电子商务的迅猛发展,快递业务量呈爆炸式增长。关于快递管理系统的研究,现有研究主要以PC端管理系统为主,专门针对Android平台的快递管理系统的研究较少......
  • dockerfile 和 docker compose
    目录1.dockerfile和dockercompose区别 主要区别目的:格式:使用场景:2.Dockerfile2.1基本格式 2.2模块解析 2.3例子 3.dockercompose 3.1安装 3.2格式 3.3执行1.dockerfile和dockercompose区别 Dockerfile和DockerCompose是Docker生态系统中两个......
  • 安卓Android 图片/Bitmap工具类
    图片/Bitmap工具类1、根据uri解码图片,通常用在从相册选择照片(1)此方法包含了压缩Bitmap,根据目标尺寸缩放等/***根据Uri解码图片**@paramselectedImage图片的Uri*@return解码后的Bitmap对象*@throwsFileNotFoundException如果文件找不......