从简略到杂乱,MVI 架构界说与封装运用总结

前语

时刻回到一年多前议论度很高的 MVI 架构,现在也已尘土落地,没有什么争议并各自都有自己的一套结束计划了,接下来我们就看看这些网上各式各样的 MVI 架构是怎样从简略到杂乱,从 Java 到 Kotlin 到协程再到 Compose 的各个场景的使用。

首要你能点进来看到这篇文章,阐明你或多或少都对 MVI 架构有些了解,我这儿就不贴一些重复的图去解释 MVI 架构原理流程什么的了。

你只需求知道,不管怎样它怎样改动,使用到哪一种言语,哪一种架构,其实质便是 MVVM 架构,它来自 MVVM 架构又扩展自 MVVM 架构。

由于 MVVM 架构的状况与行为的处理紊乱导致多人协同开发进程或许历史问题或架构问题导致直接操作 ViewModel 中的数据源导致或许的数据紊乱。

所以 MVI 架构实质便是在 MVVM 架构的基础上进行了行为和数据上的 捆绑,把数据流变成了单向活动,把状况会合处理形成仅有可信数据源。

从谷歌的安卓使用架构指南中能够看出谷歌现已推荐开发者运用 MVI 架构开发使用了。

尘土落地  遍历全网Android-MVI架构,学习总结一波

(PS:谷歌要筛选 LiveData ???)

尘土落地  遍历全网Android-MVI架构,学习总结一波

更多的代码能够去看源码,不贴图了。

当然除了谷歌推荐的 MVI 架构还有一些开源的一站式计划的如 Mosby MVI 库,如mavericks MVI 库协作 epoxy 架构快速结束页面。等等不少的一站式 MVI 组合式架构。这些就不在本文的议论范围了!

接下来我们仍是说回正文,看看现在干流的 MVI 架构从易到难都做了哪些进化?

一、不运用 Kotlin 协程行不行?

现在的文章看都看不懂,都是 Kotlin 言语和协程Flow,不会 Kotlin 就不能开发 Android 了吗?我们项目仍是 Java 项目,难道我们就不配运用 MVI 了吗?

当然不是!当然能够用!MVI 仅仅架构层面,Java 言语尽管做起来费事一点但也能结束,尽管现在干流都是运用协程Flow 来驱动数据,可是 LiveData 经过一番自界说之后也能结束相似的逻辑。

(PS:现在还不会 Kotlin 言语的话尽管能够开发 Android 可是真的是越来越难了,就算不会写至少也期望我们能读懂吧!)

一般来说 MVI 我们都会界说 Intent 与 State 。

Intent 一般我们都是密封类:

    sealed class DemoIntent {
        object RequestIndustry : DemoIntent()
        object RequestSchool : DemoIntent()
        object RequestAllData : DemoIntent()
        data class UpdateChanged(val isChange: Boolean) : DemoIntent()
    }

假定 Java 言语没有密封类,也可用抽象类写,例如:

public abstract class DemoIntent {
    private DemoIntent() {}
    public static final class RequestIndustry extends DemoIntent {
        public RequestIndustry() {}
    }
    public static final class RequestSchool extends DemoIntent {
        public RequestSchool() {}
    }
    public static final class RequestAllData extends DemoIntent {
        public RequestAllData() {}
    }
    public static final class UpdateChanged extends DemoIntent {
        private final boolean isChange;
        public UpdateChanged(boolean isChange) {
            this.isChange = isChange;
        }
        public boolean getIsChange() {
            return isChange;
        }
    }
}

State 能够是密封类,也能够是一般类。现在都推荐密封类,这儿就以一般类来写。

    data class Demo14ViewState(
        val industrys: List<Industry> = emptyList(),
        val schools: List<SchoolBean> = emptyList(),
        var isChanged: Boolean = false
    ) : BaseViewState()

然后我们在 ViewModel 中界说数据源 MutableLiveData 与 手动的 intent 分发进口:

@HiltViewModel
class Damo14ViewModel @Inject constructor(
    private val mRepository: Demo5Repository,
     val savedState: SavedStateHandle
) : BaseViewModel() {
    private val _viewStates: MutableLiveData<Demo14ViewState> = MutableLiveData(Demo14ViewState())
    //只需求暴露一个LiveData,包含页面全部状况
    val viewStates: LiveData<Demo14ViewState> = _viewStates
    //Action分发进口
    fun dispatch(intent: DemoIntent) {
        when (intent) {
            is DemoIntent.RequestIndustry -> requestIndustry()
            is DemoIntent.RequestSchool -> requestSchool()
            is DemoIntent.RequestAllData -> getTotalData()
            is DemoIntent.UpdateChanged -> changeData(intent.isChange)
        }
    }
    ...
    //以获取全部数据为示例
    private fun getTotalData() {
        //默许执行在主线程的协程-有必要用(可选择默许执行在IO线程的协程)
        launchOnUI {
            //开始Loading
            loadStartProgress()
            val industryResult = async {
                mRepository.getIndustry()
            }
            val schoolResult = async {
                mRepository.getSchool()
            }
            //一同处理数据
            val industry = industryResult.await()
            val school = schoolResult.await()
            //假定都成功了才一同回来
            if (industry is OkResult.Success && school is OkResult.Success) {
                loadHideProgress()
                //设置多种LiveData
                _viewStates.setState {
                    copy(industrys = industry.data ?: emptyList(), schools = school.data ?: emptyList())
                }
            }
        }
    }
}

接下来我们在 Activity 就能监听我们在 ViewModel 中界说的 MutableLiveData 值。

    override fun startObserve() {
        //监听两者数据改动
        mViewModel.viewStates.observeState(
            this,
            Damo14ViewModel.Demo14ViewState::industrys,
            Damo14ViewModel.Demo14ViewState::schools
        ) { industry, school ->
            YYLogUtils.w("industry: $industry ; school: $school")
        }
        //只监听changed的改换
        mViewModel.viewStates.observeState(this, Damo14ViewModel.Demo14ViewState::isChanged) {
            if (it) {
                val industry = mViewModel.viewStates.value?.industrys
                val school = mViewModel.viewStates.value?.schools
                mBinding.tvMessage.text = "industry: $industry ; school: $school"
            }
        }
    }

需求注意的是自界说的 setState 办法与 observeState 办法是我们自界说的,由于我们有或许只需求设置 State 数据源中的一个字段,或许只监听 MutableLiveData 中的一个字段的改动。

LiveData 的扩展办法如下:

//监听一个特征
fun <T, A> LiveData<T>.observeState(
    lifecycleOwner: LifecycleOwner,
    prop1: KProperty1<T, A>,
    action: (A) -> Unit
) {
    this.map {
        StateTuple1(prop1.get(it))
    }.distinctUntilChanged().observe(lifecycleOwner) { (a) ->
        action.invoke(a)
    }
}
//监听两个特征
fun <T, A, B> LiveData<T>.observeState(
    lifecycleOwner: LifecycleOwner,
    prop1: KProperty1<T, A>,
    prop2: KProperty1<T, B>,
    action: (A, B) -> Unit
) {
    this.map {
        StateTuple2(prop1.get(it), prop2.get(it))
    }.distinctUntilChanged().observe(lifecycleOwner) { (a, b) ->
        action.invoke(a, b)
    }
}
//监听三个特征
fun <T, A, B, C> LiveData<T>.observeState(
    lifecycleOwner: LifecycleOwner,
    prop1: KProperty1<T, A>,
    prop2: KProperty1<T, B>,
    prop3: KProperty1<T, C>,
    action: (A, B, C) -> Unit
) {
    this.map {
        StateTuple3(prop1.get(it), prop2.get(it), prop3.get(it))
    }.distinctUntilChanged().observe(lifecycleOwner) { (a, b, c) ->
        action.invoke(a, b, c)
    }
}
internal data class StateTuple1<A>(val a: A)
internal data class StateTuple2<A, B>(val a: A, val b: B)
internal data class StateTuple3<A, B, C>(val a: A, val b: B, val c: C)
//更新State
fun <T> MutableLiveData<T>.setState(reducer: T.() -> T) {
    //拿到T去处理,处理结束之后的T,再次赋值给LiveData
    this.value = this.value?.reducer()
}

最后便是 Intent 的作业会合处理。由于我们没有 Flow 的流监听,这儿最简略的做法是直接在 Activity 中调用分发接口:


   override fun init() {
        //发送Intent指令,详细的结束由ViewModel结束
        mViewModel.dispatch(Damo14ViewModel.DemoAction.RequestAllData)
    }
    fun fetchData() {
        //发送Intent指令,详细的结束由ViewModel结束
        mViewModel.dispatch(Damo14ViewModel.DemoIntent.UpdateChanged(true))
    }

这样就结束了一个最简略的单向流与数据一致的State处理。尽管有了最简略的结束,可是假定用 Kotlin 的 Flow 来做的话,会愈加的典雅。

二、运用协程 Flow 之后的运用与封装

运用一个标准的 Kotlin Flow 的界说的 MVI 架构,我们把 Intent 与 State 都运用封装类。

/**
 * 页面意图
 */
sealed class MVI3Intent {
    //行为- 想要获取作业数据
    object GetIndustry : MVI3Intent()
    //行为- 想要获取校园数据
    object GetSchool : MVI3Intent()
}

关于 State 我们封装了页面状况与数据在一同:

/**
 * 页面状况
 */
sealed class MVI3State {
    //默许闲暇
    object Idle : MVI3State()
    //加载
    object Loading : MVI3State()
    //过失信息
    data class Error(val error: String) : MVI3State()
    //成功的作业数据
    data class Industries(val indusory: List<Industry>) : MVI3State()
    //成功的校园数据
    data class Schools(val schools: List<SchoolBean>) : MVI3State()
}

在 ViewModel 中我们就能够经过 Channel 来分发 Intent 作业。

   //创立意图管道,容量无限大 (能够用Flow的监听效果,查询者办法改动之后就主动更新)
    //为什么用Channel不必Flow,是由于只需求单向活动就行了,Channel是单对单,Flow的单对多
    //Channel的发送send和接纳receive,宣告的作业只能被接纳一次,接纳之后就不能再次接纳了,很适合这个场景。 如果屏幕旋转重建了也不会再度触发作业。
    val mainIntentChannel = Channel<MVI3Intent>(Channel.UNLIMITED)
    val mainIntentChannel = Channel<MVI3Intent>(Channel.UNLIMITED)
    init {
        //之前我们是用dispatch主动分发,这儿是经过Channel的办法主动分发的。
        viewModelScope.launch {
            //搜集意图 (查询者办法改动之后就主动更新)用于协程通讯的,所以需求在协程中调用
            mainIntentChannel.consumeAsFlow().collect { value ->
                when (value) {
                    //根据意图作业分别调用不同的办法
                    is MVI3Intent.GetIndustry -> requestIndustry()
                    is MVI3Intent.GetSchool -> requestSchool()
                    else -> {}
                }
            }
        }
    }

我们用 StateFlow 来界说和接纳页面的状况与数据,整个 ViewModel 的数据如下:

@HiltViewModel
class Demo14MVI3ViewModel @Inject constructor(
    private val mRepository: Demo5Repository,
    val savedState: SavedStateHandle
) : BaseViewModel() {
    val mainIntentChannel = Channel<MVI3Intent>(Channel.UNLIMITED)
    private val _uiState = MutableStateFlow<MVI3State>(MVI3State.Idle)
    val uiState: StateFlow<MVI3State> get() = _uiState
    init {
        viewModelScope.launch {
            mainIntentChannel.consumeAsFlow().collect { value ->
                when (value) {
                    is MVI3Intent.GetIndustry -> requestIndustry()
                    is MVI3Intent.GetSchool -> requestSchool()
                    else -> {}
                }
            }
        }
    }
    //获取作业数据
    private fun requestIndustry() {
        viewModelScope.launch {
            //央求Loading
            _uiState.value = MVI3State.Loading
            val result = mRepository.getIndustry()
            result.checkResult(success = {
                //央求成功
                _uiState.value = MVI3State.Industries(it!!)
            }, error = {
                //央求失利
                MVI3State.Error(it ?: "UnKnown Error")
            })
        }
    }
    //获取校园数据
    private fun requestSchool() {
        viewModelScope.launch {
            //央求Loading
            _uiState.value = MVI3State.Loading
            val result = mRepository.getSchool()
            result.checkResult(success = {
                //央求成功
                _uiState.value = MVI3State.Schools(it!!)
            }, error = {
                //央求失利
                MVI3State.Error(it ?: "UnKnown Error")
            })
        }
    }
}

在 Activity 中的 Intent 发送与 State 接纳和之前的比较相似,仅仅把作业的手动调用变为了 Channel 分发,LiveData 的监听变为 StateFlow 的监听。

  override fun init() {
        observeViewModel()
        mBinding.btnGetData.text = "点击获取数据"
        mBinding.btnGetData.setOnClickListener { view ->
            lifecycleScope.launch {
                mViewModel.mainIntentChannel.send(MVI3Intent.GetIndustry)
            }
        }
    }
    /**
     * 查询ViewModel
     */
    private fun observeViewModel() {
        lifecycleScope.launch {
            //状况搜集,(实践便是StateFlow的监听)
            mViewModel.uiState.collect {
                when (it) {
                    is MVI3State.Idle -> {
                    }
                    is MVI3State.Loading -> {
                        LoadingDialogManager.get().showLoading(this@Demo14MVI3Activity)
                    }
                    is MVI3State.Industries -> {
                        LoadingDialogManager.get().dismissLoading()
                        YYLogUtils.w("indusory:$it.indusory")
                    }
                    is MVI3State.Schools -> {
                        LoadingDialogManager.get().dismissLoading()
                        YYLogUtils.w("schools:${it.schools}")
                    }
                    is MVI3State.Error -> {
                        LoadingDialogManager.get().dismissLoading()
                        YYLogUtils.d("过失: $it.error")
                    }
                }
            }
        }
    }

这样便是最常见的 MVI 格式,也是我们推荐比较多的计划了。既然是最常见的计划了,我们就能封装一下便利运用了。

三、协作 ViewBinding 与 ViewModel 基类封装

我们能够把 Intent 与 State 抽取出接口类,便利承继泛型,并且在 ViewModel 中封装对应的代码逻辑,并暴露每个 ViewModel 需求处理的 State 即可。

而页面的封装,我们能够结合 ViewBinding 来封装,究竟现已是 MVI 单向数据流了,那么运用 ViewBinding 就比 DataBinding 愈加适合了,不然就失掉单向数据的含义了。

先界说 State 与 Intent 的接口限制类:

//MVI 页面作业处理的基类
//在R8代码混杂紧缩时有必要保存被符号的类或办法,以避免代码出现因混杂而导致的溃散。
@Keep
interface IUiIntent
//MVI 页面状况处理的基类
@Keep
interface IUiState

基于此接口我们就能封装 ViewModel 的逻辑,把一些公共的代码封装起来,并暴露每个页面不同的处理:

abstract class BaseVB2ViewModel<UiIntent : IUiIntent, UiState : IUiState> : BaseViewModel() {
    private val _uiStateFlow = MutableStateFlow(initUiState())
    val uiStateFlow: StateFlow<UiState> = _uiStateFlow
    //页面作业的 Channel 分发
    private val _uiIntentFlow = Channel<UiIntent>(Channel.UNLIMITED)
    //更新页面状况
     fun updateUiState(reducer: UiState.() -> UiState) {
        _uiStateFlow.update { reducer(_uiStateFlow.value) }
    }
    //更新State
    fun <T> sendUiState2(reducer: T.() -> T) {
    }
    //发送页面作业
    fun sendUiIntent(uiIntent: UiIntent) {
        viewModelScope.launch {
            _uiIntentFlow.send(uiIntent)
        }
    }
    init {
        // 这儿是经过Channel的办法主动分发的。
        viewModelScope.launch {
            //搜集意图 (查询者办法改动之后就主动更新)用于协程通讯的,所以需求在协程中调用
            _uiIntentFlow.consumeAsFlow().collect { intent ->
                handleIntent(intent)
            }
        }
    }
    //每个页面的 UiState 都不相同,有必要实自己去创立
    protected abstract fun initUiState(): UiState
    //每个页面处理的 UiIntent 都不同,有必要结束自己页面对应的状况处理
    protected abstract fun handleIntent(intent: UiIntent)
}

运用的时分,我们界说自己页面的 State 与 Intent 如下:

sealed class Demo14Intent : IUiIntent {
    object GetIndustry : Demo14Intent()
    object GetSchool : Demo14Intent()
}

由于我自己的页面 Load 逻辑是单独封装处理的,这儿仅仅演示一下怎样自己手动处理UI加载的状况。假定你也自行处理了 UI 加载状况,那么是能够不需求的。

data class Demo14State(val industryUiState: IndustryUiState, val schoolUiState: SchoolUiState, val loadUiState: LoadUiState) : IUiState {}
// 内部分子类,界说了常见的四种加载状况
sealed class LoadUiState {
    object Idle : LoadUiState()  //默许闲暇
    data class Loading(var isShow: Boolean) : LoadUiState()  //展现Loading
    object ShowContent : LoadUiState()           //展现布局
    data class Error(val msg: String) : LoadUiState()  //失利
}
// 内部分子类,并结束初始化值,成功值,失利值
sealed class IndustryUiState {
    object INIT : IndustryUiState()
    data class SUCCESS(val industries: List<Industry>) : IndustryUiState()
}
// 内部分子类,并结束初始化值,成功值,失利值
sealed class SchoolUiState {
    object INIT : SchoolUiState()
    data class SUCCESS(val schooles: List<SchoolBean>) : SchoolUiState()
}

那么运用的时分,我们初始化自己的 ViewModel 的时分就把自己的 Intent 与 State 传入泛型即可:

@HiltViewModel
class Demo14JMVI2ViewModel @Inject constructor(
    private val mRepository: Demo5Repository,
    val savedState: SavedStateHandle
) : BaseVB2ViewModel<Demo14Intent, Demo14State>() {
    override fun initUiState(): Demo14State {
        return return Demo14State(IndustryUiState.INIT, SchoolUiState.INIT, LoadUiState.Idle);
    }
    override fun handleIntent(intent: Demo14Intent) {
        when (intent) {
            Demo14Intent.GetIndustry -> requestIndustry()
            Demo14Intent.GetSchool -> requestSchool()
            else -> {}
        }
    }
    //获取作业数据
    private fun requestIndustry() {
        viewModelScope.launch {
            //央求Loading
            updateUiState {
                copy(loadUiState = LoadUiState.Loading(true))
            }
            val result = mRepository.getIndustry()
            result.checkResult(success = {
                //央求成功
                updateUiState {
                    //调用data class 的copy 赋值更便利
                    copy(
                        industryUiState = IndustryUiState.SUCCESS(it ?: emptyList()),
                        loadUiState = LoadUiState.ShowContent
                    )
                }
            }, error = {
                //央求失利
                updateUiState {
                    copy(loadUiState = LoadUiState.Error(it ?: "不知道过失"))
                }
            })
        }
    }
    //获取校园数据
    private fun requestSchool() {
        viewModelScope.launch {
            //央求Loading
            updateUiState {
                copy(loadUiState = LoadUiState.Loading(true))
            }
            val result = mRepository.getSchool()
            result.checkResult(success = {
                //央求成功
                updateUiState {
                    //调用data class 的copy 赋值更便利
                    copy(
                        schoolUiState = SchoolUiState.SUCCESS(it ?: emptyList()),
                        loadUiState = LoadUiState.ShowContent
                    )
                }
            }, error = {
                //央求失利
                updateUiState {
                    copy(loadUiState = LoadUiState.Error(it ?: "不知道过失"))
                }
            })
        }
    }
}

关于 Activity 的封装,也是比较简略,我们把 ViewBinding 的代码封装一下:

能够用结构传参高阶函数处理:

abstract class BaseVB2Activity<VM : ViewModel, VB : ViewBinding>(
    val block: (LayoutInflater) -> VB
) : AbsActivity() {
    protected lateinit var mViewModel: VM
    private var _binding: VB? = null
    protected val mBinding: VB
        get() = requireNotNull(_binding) { "ViewBinding政策为空" }
    open protected fun createViewModel(): VM {
        return ViewModelProvider(this).get(getVMCls(this))
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        _binding = block(layoutInflater)
        super.onCreate(savedInstanceState)
    }
    override fun setContentView() {
        setContentView(mBinding.root)
        mViewModel = createViewModel()
    }
    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

也能够暴露办法去处理:

abstract class BaseVB2Activity<VM : ViewModel, VB : ViewBinding>() : AbsActivity() {
    protected lateinit var mViewModel: VM
    private var _binding: VB? = null
    protected val mBinding: VB
        get() = requireNotNull(_binding) { "ViewBinding政策为空" }
    abstract fun createViewBinding(): VB
    open protected fun createViewModel(): VM {
        return ViewModelProvider(this).get(getVMCls(this))
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        _binding = createViewBinding()
        super.onCreate(savedInstanceState)
    }
    override fun setContentView() {
        setContentView(mBinding.root)
        mViewModel = createViewModel()
    }
    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

逻辑是相同的,运用的完好 Activity 为:

@AndroidEntryPoint
class Demo14MVIActivity : BaseVB2Activity<Demo14JMVI2ViewModel, ActivityDemo14JavaMviBinding>(
    ActivityDemo14JavaMviBinding::inflate
) {
    override fun init() {
        w("init - mBinding.btnGetData:" + mBinding.btnGetData)
        mBinding.btnGetData.text = "点击获取数据"
        mBinding.btnGetData.setOnClickListener { view ->
            w("点击到按钮:$view")
            mViewModel.sendUiIntent(Demo14Intent.GetSchool)
        }
        lifecycleScope.launchWhenStarted {
            //现已是StateFlow了,还需求防抖去重吗?
            mViewModel.uiStateFlow.map { it.schoolUiState }
                .distinctUntilChanged()
                .collect { schoolState ->
                    when (schoolState) {
                        is SchoolUiState.INIT -> {}
                        is SchoolUiState.SUCCESS -> {
                            YYLogUtils.w("加载校园数据成功")
                        }
                    }
                }
        }
    }
}

这样我们在之前的 Kotlin 运用的基础上就结合 ViewModel 与 ViewBinding结束了一个简略的封装。

四、State 继续细分为 UIEffect 和 UIState,继续封装

State 是我们的UI状况我们都知道了,可是其间假定要差异持久性UI状况,与一次性UI状况,怎样办?

很典型的比如,横竖屏切换在发生生命周期改动时分需求回放数据,UIState作为持久性UI状况是没问题的,可是一些一次性消费UI作业,如弹窗,吐司,导航等状况我们并不好处理,所以为了差异持久性状况与一次性状况,一些 MVI 架构继续演化,把之前的 State 继续细分为 UIEffect 和 UIState。

用什么数据来结束一次性状况呢? StateFlow 必定不行,我们得用 SharedFlow 。这下 Channel StateFlow SharedFlow 都齐活了,为什么这么用关于它们之间的差异假定不了解能够看看我前期的文章 【传送门】

我们在上章的基础上加上 Effect 的类型:

@Keep
interface IUIEffect

BaseViewModel修改如下:

abstract class BaseVB2ViewModel<UiIntent : IUiIntent, UiState : IUiState, UIEffect : IUIEffect> : BaseViewModel() {
 ...
    //一次性作业,无需更新
    private val _effectFlow = MutableSharedFlow<UIEffect>()
    val uiEffectFlow: SharedFlow<UIEffect> by lazy { _effectFlow.asSharedFlow() }
    //两种办法发射
    protected fun sendEffect(builder: suspend () -> UIEffect?) = viewModelScope.launch {
        builder()?.let { _effectFlow.emit(it) }
    }
    //两种办法发射
    protected suspend fun sendEffect(effect: UIEffect) = _effectFlow.emit(effect)
}

界说我们页面的 Effect 类:

sealed class Demo14Effect : IUIEffect {
    data class NavigationToSchoolDetail(val id: Int) : Demo14Effect()
}

那么详细的 ViewModel 我们就改为:

@HiltViewModel
class Demo14JMVI2ViewModel @Inject constructor(
    private val mRepository: Demo5Repository,
    val savedState: SavedStateHandle
) : BaseVB2ViewModel<Demo14Intent, Demo14State, Demo14Effect>() {
    override fun initUiState(): Demo14State {
        return return Demo14State(IndustryUiState.INIT, SchoolUiState.INIT, LoadUiState.Idle);
    }
    override fun handleIntent(intent: Demo14Intent) {
        when (intent) {
            Demo14Intent.GetSchool -> requestSchool()
            else -> {}
        }
    }
    //获取校园数据
    private fun requestSchool() {
        viewModelScope.launch {
            //央求Loading
            updateUiState {
                copy(loadUiState = LoadUiState.Loading(true))
            }
            val result = mRepository.getSchool()
            if (result is OkResult.Success) {
                val data = result.data
                //央求成功
                updateUiState {
                    //调用data class 的copy 赋值更便利
                    copy(
                        schoolUiState = SchoolUiState.SUCCESS(data ?: emptyList()),
                        loadUiState = LoadUiState.ShowContent
                    )
                }
                sendEffect(Demo14Effect.NavigationToSchoolDetail(data!![0].school_id))
            } else {
                val message = (result as OkResult.Error).exception.message
                updateUiState {
                    copy(loadUiState = LoadUiState.Error(message ?: "不知道过失"))
                }
            }
        }
    }
}

关于 Activity 的封装我们也能修改,之前我们需求在 Activity 中调用 ViewBinding 的 inflate 办法。现在我们能够在基类中像 ViewModel 相同的反射调用初始化。

abstract class BaseVB2Activity<VM : ViewModel, VB : ViewBinding> : AbsActivity() {
    protected lateinit var mViewModel: VM
    private var _binding: VB? = null
    protected val mBinding: VB
        get() = requireNotNull(_binding) { "ViewBinding政策为空" }
    //反射创立ViewModel
    open protected fun createViewModel(): VM {
        return ViewModelProvider(this).get(getVMCls(this))
    }
    //反射创立ViewBinding
    open protected fun createViewBinding() {
        val clazz: Class<*> =  (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments[1] as Class<VB>
        try {
            _binding = clazz.getMethod(
                "inflate", LayoutInflater::class.java
            ).invoke(null, layoutInflater) as VB
        } catch (e: Exception) {
            e.printStackTrace()
            throw IllegalArgumentException("无法经过反射创立ViewBinding政策")
        }
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        createViewBinding()
        super.onCreate(savedInstanceState)
    }
    override fun setContentView() {
        setContentView(mBinding.root)
        mViewModel = createViewModel()
    }
    override fun onDestroy() {
        super.onDestroy()
        _binding = null
    }
}

运用 Activity 起来就愈加简略啦:

Activity 就简略啦:

@AndroidEntryPoint
class Demo14MVIActivity : BaseVB2Activity<Demo14JMVI2ViewModel, ActivityDemo14JavaMviBinding>() {
    override fun init() {
        w("init - mBinding.btnGetData:" + mBinding.btnGetData)
        mBinding.btnGetData.text = "点击获取数据"
        mBinding.btnGetData.setOnClickListener { view ->
            w("点击到按钮:$view")
            mViewModel.sendUiIntent(Demo14Intent.GetSchool)
        }
        lifecycleScope.launchWhenStarted {
            mViewModel.uiStateFlow
                .map { it.schoolUiState }
                .distinctUntilChanged()
                .collect { schoolState ->
                    when (schoolState) {
                        is SchoolUiState.INIT -> {}
                        is SchoolUiState.SUCCESS -> {
                            YYLogUtils.w("Flow回调 -  加载校园数据成功")
                        }
                    }
                }
        }
        lifecycleScope.launchWhenResumed {
            //一次性状况的接纳与处理
            mViewModel.uiEffectFlow
                .collect {
                    when (it) {
                        is Demo14Effect.NavigationToSchoolDetail -> {
                            ToastUtils.makeText(mActivity, "Flow回调 - 跳转到校园概况:${it.id}")
                        }
                    }
                }
        }
    }

尽管代码是越分越细了,可是我们能够经过封装的办法尽量让一些重复的逻辑与代码简略化。

其实能够看出 MVI 架构一步步也是从开始的简略到后边的杂乱了,责任差异越来越详尽,封装也愈加杂乱了。

其实不止这一点全部的架构都是这样,包含我们 Android 开发架构从 MVC -> MVP -> MVVP(Lite版) -> MVVM(DataBinding) -> MVI 它们的改动进程其实越是一步步的杂乱起来的,从之前代码都在一个类里边到后边的按责任差异功用一步步的细化再细化。

总结

尽管说 MVI 架构有许多的结束计划,可是我并不是说推荐越杂乱越好,也不是说推荐我们必定就用 MVI 架构,架构越是杂乱,层级越多,责任差异越是详尽,那么就会导致类越多,逻辑越多,其实并不适合每一个人或开发团队。仍是那句话老话按需选择即可。

从后边好几种 Kotlin 言语与协程架构包含 Flow 的结合之后组成一个 MVI 架构能够说是充分发挥了他们的特征。假定是运用 MVI 架构的更推荐运用 Kotlin 言语结束,相对典雅。

关于 Compose 这种声明式 UI 与 MVI 架构分配就更舒服了,由于我还没开源 Compose 项目就不写啦,其实写法都是大差不差了的,我们能够自行测验、

关于本文的内容假想象检查源码能够点击这儿 【传送门】。你也能够注重我的这个Kotlin项目(尽管是两年前的老项目了),我有时刻都会继续更新。

假定有更多的更好的其他办法,也期望我们能议论区交流一同学习前进。假定我的代码或许注释、解说等不到位或讹夺的当地,期望同学们能够指出。

假定感觉本文对你有一点点的启示,还望你能点赞支撑一下,你的支撑是我最大的动力。

Ok,这一期就此结束。

尘土落地  遍历全网Android-MVI架构,学习总结一波