Mvi架构浅析

  • 本篇文章是简单使用了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 架构了解一下~

你可能感兴趣的:(Kotlin,架构,kotlin)