当前位置:网站首页>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 架构了解一下~
边栏推荐
- how to measure distance from point to face in creo
- There are several common event handling methods in Swing?How to listen for events?
- Mini Program_Dynamic setting of tabBar theme skin
- 1007 Climb Stairs (贪心 | C思维)
- Some conventional routines of program development (1)
- There are a lot of 4T hard drives remaining, prompting "No space left on device" insufficient disk space
- The first performance test practice, there are "100 million" a little nervous
- 雷克萨斯lm的安全性到底体现在哪里?一起来看看吧
- Why did you start preparing for the soft exam just after the PMP exam?
- How to solve the three major problems of bank data collection, data supplementary recording and index management?
猜你喜欢
Four-digit display header design
flink读取mongodb数据源
UE4 第一人称角色模板 添加生命值和调试伤害
In the hot summer, teach you to use Xiaomi smart home accessories + Raspberry Pi 4 to connect to Apple HomeKit
大学物理---质点运动学
什么是ASEMI光伏二极管,光伏二极管的作用
What is the function of industrial-grade remote wireless transmission device?
UI自动化测试 App的WebView页面中,当搜索栏无搜索按钮时处理方法
Ali's local life's single-quarter revenue is 10.6 billion, Da Wenyu's revenue is 7.2 billion, and Cainiao's revenue is 12.1 billion
[MRCTF2020] PYWebsite
随机推荐
8.04 Day35-----MVC三层架构
机器学习概述
SkiaSharp 之 WPF 自绘 粒子花园(案例版)
flink reads mongodb data source
flink读取mongodb数据源
[BJDCTF2020] EasySearch
数字孪生技术在电力系统中的应用现状
JeeSite New Report
C+ +核心编程
BI业务分析思维:现金流量风控分析(二)信用、流动和投资风险
DNS被劫持如何处理?
大学物理---质点运动学
特征预处理
UE4 第一人称角色模板 添加蹲伏功能
Cron(Crontab)--使用/教程/实例
Detailed explanation of Mysql's undo log
UE4 更改组件变量 (以修改第一人称角色模板的最大行走速度和跳跃高度为例)
多御安全浏览器新版下载 | 功能优秀性能出众
[极客大挑战 2019]FinalSQL
The most comprehensive exam questions for software testing engineers in 2022