当前位置:网站首页>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 架构了解一下~
边栏推荐
- 请写出SparkSQL语句
- DNS被劫持如何处理?
- Cron(Crontab)--使用/教程/实例
- bytebuffer 内部结构
- upload upload pictures to Tencent cloud, how to upload pictures
- How do newcomers get started and learn software testing?
- Bosses, I noticed that a mysql CDC connector parameters scan. The incremental. Sna
- 【 8.4 】 source code - [math] [calendar] [delete library 】 【 is not a simple sequence (Bonus) 】
- 1007 Climb Stairs (greedy | C thinking)
- Bytebuffer put flip compact clear method demonstration
猜你喜欢
随机推荐
国学*周易*梅花易数 代码实现效果展示 - 梅花心易
软件管理rpm
Analyses the mainstream across technology solutions
机器学习概述
AUTOCAD - dimension association
炎炎夏日教你利用小米智能家居配件+树莓派4接入Apple HomeKit
【测量学】速成汇总——摘录高数帮
mutillidae下载及安装
[MRCTF2020]PYWebsite
如何解决复杂的分销分账问题?
upload upload pictures to Tencent cloud, how to upload pictures
AUTOCAD——标注关联
dedecms error The each() function is deprecated
开发属于自己的node包
【8.2】代码源 - 【货币系统】【硬币】【新年的问题(数据加强版)】【三段式】
概率论的学习和整理8: 几何分布和超几何分布
UI自动化测试 App的WebView页面中,当搜索栏无搜索按钮时处理方法
[BSidesCF 2019]Kookie
Index Mysql in order to optimize paper 02 】 【 10 kinds of circumstances and the principle of failure
[MRCTF2020]Ezpop(详解)
![[CISCN2019 South China Division]Web11](/img/15/843334fec0a5cc8cfaba92aab938db.png)




![[BJDCTF2020]EasySearch](/img/60/464de3bcdda876171b9f61ad31bff1.png)

![[MRCTF2020]Ezpop(详解)](/img/19/920877ca36d1eda8d118637388ab05.png)
![[MRCTF2020] PYWebsite](/img/d4/57e8e5ee45b742894679f3f5671516.png)
