当前位置:网站首页>Mvi架构浅析
Mvi架构浅析
2022-08-05 04:40:00 【慢行的骑兵】
- 本篇文章是简单使用了Kotlin + 协程 + flow + channel写了一个伪登录请求案例(dev_20220804_mvi分支),通过该案例的来了解Mvi架构。
- 在了解Mvi之前,建议先了解一下Mvvm,可以参考Mvc、Mvp和Mvvm
一.代码环节
- 单单先去了解概念会有一种抽象的感觉,我们通过分析代码的逻辑以及代码对应的类结合Mvi的概念一同理解,会清晰很多;
- 案例一共包含4个类,MainActivity、DemoViewModel、DemoIntent、DemoUiState,先贴上;
1.1.MainActivity
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
class MainActivity : AppCompatActivity() {
private lateinit var textView: TextView
private val mDemoViewModel: DemoViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.textView)
lifecycleScope.launch {
//3.View层设置监听(VM层根据意图执行相应的逻辑后会主动触发该回调)
mDemoViewModel.mUiState.collect {
uiState ->
when (uiState) {
is DemoUiState.loginSuccess -> {
textView.setText(uiState.success)
}
is DemoUiState.loginFail -> {
textView.setText("登录失败")
}
is DemoUiState.beforeLogin -> {
textView.setText(uiState.bengin)
}
}
}
}
}
//1.点击按钮模拟网络请求
fun login(view: View) {
lifecycleScope.launch {
//1.1.通过管道将意图传递给ViewModel层
mDemoViewModel.mChannel.send(DemoIntent.LoginIntent)
}
}
override fun onDestroy() {
super.onDestroy()
//Channel是一种协程资源['热'流],跨越不同的协程进行通信
//在使用完若不去关闭,会造成不必要的浪费
mDemoViewModel.mChannel.close()
}
}
1.2.DemoViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
/** * 意图类 * 可以将意图类写在对应的ViewModel类中,不需要分开出去 * 好处:避免粒度分的过细,从而增加项目的复杂度 */
sealed class DemoIntent {
//意图的个数根据实际的需求进行添加
object LoginIntent : DemoIntent()
}
class DemoViewModel : ViewModel() {
val mChannel = Channel<DemoIntent>()
private val mDemoUiState = MutableStateFlow<DemoUiState>(DemoUiState.beforeLogin("准备登录"))
//使用flow来监听
val mUiState: StateFlow<DemoUiState> = mDemoUiState
init {
handleIntentInfo()
}
private fun handleIntentInfo() {
viewModelScope.launch {
mChannel.consumeEach{
//consumeEach:channel(管道)读数据推荐方案[直接使用receive是很容易出问题的]
//2.接收用户传输过来的意图
when(it){
//这里为什么要经过一个意图呢?而不是有通信即可,目的是为了便于管理,视图是为了V层跟VM层更好地解耦 以及代码扩展
is DemoIntent.LoginIntent -> getLoginData()
}
}
}
}
private fun getLoginData() {
viewModelScope.launch {
requestLogin.flowOn(Dispatchers.Default)
.catch {
exception ->
mDemoUiState.value = DemoUiState.loginFail(exception)
}.collect {
loginInfo ->
mDemoUiState.value = DemoUiState.loginSuccess(loginInfo)
}
}
}
private val requestLogin: Flow<String> = flow {
val loginInfo = "登录成功" //模拟请求登录接口
emit(loginInfo)
}
}
1.3.DemoUiState
sealed class DemoUiState {
//为了降低复杂度,方法的参数都写定(实际项目中括号内部的参数建议封装成泛型)
data class beforeLogin(var bengin: String):DemoUiState()
data class loginSuccess(var success: String):DemoUiState()
data class loginFail(var exception: Throwable):DemoUiState()
}
- 接下来我们结合概念和代码来理解Mvi
二.Mvi
2.1.站在Android开发者的角度简单理解Mvi
- 假如用户在操作我们的App,要执行登录操作,那么代码层面会将用户的登录操作封装层一个意图(对应Mvi的i),然后将这个意图传递给ViewModel(M层的一部分),然后ViewModel执行完相应的逻辑后,会通过回调的方式通知View层(实现方式有多种,常用的就是Livedata,案例中用的是Flow);
- 上面的场景,用户的操作从V层开始,最终的反馈也是在View层,数据的流向是单向的;
2.2.Mvi和Mvvm的区别
- 多了一个i(对应案例的DemoIntent 类)和少了一个databinding,其它的个人觉得没什么区别(至于DemoUiState 类,也可以在Mvvm中也有对应的封装);
2.3.对比一下代码逻辑
- 从MainActivity的login方法开始,用户执行登录,会通过协程的管道(协程的一种资源,不是本文的重点,了解即可)传递一个DemoIntent.LoginIntent参数,其中DemoIntent类就是一个意图类,LoginIntent是一个具体的意图,至于需要在DemoIntent中定义多少个意图由具体的需求来决定。将登录的需要包装成一个意图然后传递到ViewModel层,ViewModel层接收了,对应在DemoViewModel类的handleIntentInfo方法中处理View层传递过来的意图,我们只要分析该方法的When语句即可,判断it到底对应哪个DemoIntent的意图来决定执行逻辑,当是DemoIntent.LoginIntent执行登录的(伪)网络请求()调用到getLoginData方法,请求接口后修改mDemoUiState.value的值,从而触发MainActivity类中的mDemoViewModel.mUiState设置的监听,然后View层根据不同的条件执行不同的逻辑;
三.总结
- 本篇文章从具体的代码描述Mvi不同于Mvvm的点,Mvi的M层可以对应Mvvm的VM层和M层的结合,同时包含意图和UI状态改变的两个部分。使用层面更简单了,但是个人觉得Mvi的M层于Mvvm的臃肿了一些,而这个臃肿的点大家可以学习Mvvm的优点对Mvi进行灵活变动。
- 关于Mvi的学习,个人推荐文章-MVVM 进阶版:MVI 架构了解一下~
边栏推荐
- 【背包九讲——01背包问题】
- 多列属性column元素的可见性:display、visibility、opacity、垂直对齐方式:vertical-align、z-index 越大越显示在上层
- C语言-大白话理解原码,反码和补码
- [8.2] Code Source - [Currency System] [Coins] [New Year's Questions (Data Enhanced Edition)] [Three Stages]
- 【8.4】代码源 - 【数学】【历法】【删库】【不朴素的数列(Bonus)】
- Detailed explanation of Mysql's undo log
- UE4 更改组件变量 (以修改第一人称角色模板的最大行走速度和跳跃高度为例)
- MySql index learning and use; (I think it is detailed enough)
- Day019 Method overriding and introduction of related classes
- Bytebuffer put flip compact clear method demonstration
猜你喜欢

小程序_动态设置tabBar主题皮肤

upload upload pictures to Tencent cloud, how to upload pictures

Use IDEA to connect to TDengine server

Learning and finishing of probability theory 8: Geometric and hypergeometric distributions

4T硬盘剩余很多提示“No space left on device“磁盘空间不足

Error creating bean with name ‘configDataContextRefresher‘ defined in class path resource

creo怎么测量点到面的距离

请写出SparkSQL语句

University Physics---Particle Kinematics

dedecms后台生成提示读取频道信息失败的解决方法
随机推荐
bytebuffer put flip compact clear 方法演示
dedecms error The each() function is deprecated
C language - vernacular to understand the original code, inverse code and complement code
What is ASEMI photovoltaic diode, the role of photovoltaic diode
flink reads mongodb data source
Mysql's redo log detailed explanation
About the installation of sklearn library
Shell(4)条件控制语句
How to wrap markdown - md file
七夕节赚徽章拉
狗仔队:表面编辑多视点图像处理
bytebuffer use demo
[BSidesCF 2019]Kookie
浅析主流跨端技术方案
bytebuffer 使用demo
[MRCTF2020] Ezpop (detailed)
overloaded operator
What is the function of industrial-grade remote wireless transmission device?
多御安全浏览器新版下载 | 功能优秀性能出众
特征预处理