当前位置: 首页 > 科技观察

MVI架构更好的实践:支持LiveData属性监控

时间:2023-03-12 11:16:33 科技观察

前言我们已经介绍了MVI架构的基本原理和用法:MVVM进阶版:了解MVI架构~MVI架构需要编写多个LiveData才能解决复杂的逻辑MVVM(可变+不可变)问题,使用ViewState集中管理State,只需要订阅一个ViewState就可以获取页面的所有状态。通过对ViewState的集中管理,只需要对外暴露一个LiveData,解决了MVVM模式下LiveData的扩容问题。但是页面的所有状态都是通过一个LiveData来管理的,这也带来了一个比较严重的问题,就是页面不支持局部刷新。虽然RecyclerView可以通过DifferUtil来解决,但是毕竟不是所有的页面都是通过RecyclerView来写的,支持DifferUtil也有一定的开发成本。因此,直接使用MVI架构会带来一定的性能损失。相信这也是很多人不愿意使用MVI架构的原因之一。本文主要介绍如何通过监控LiveData的属性实现MVI架构下的局部刷新。Mavericks框架简介Mavericks框架是Airbnb开源的MVI框架。Mavericks基于AndroidJetpack和KotlinCoroutines。主要目标是让页面开发更高效、更轻松、更有趣。它已在数百个Airbnb页面上使用。让我们看看Mavericks是如何使用的。//1.包含数据类dataclassCounterState(valcount:Int=0):MavericksState//2.ViewModel负责处理业务逻辑,便于单元测试classCounterViewModel(initialState:CounterState):MavericksViewModel(initialState){//通过setState更新页面状态funincrementCount()=setState{copy(count=count+1)}}//3.View层必须实现MavericksView接口类CounterFragment:Fragment(R.layout.counter_fragment),MavericksView{privatevalviewModel:CounterViewModelbyfragmentViewModel()overridefunonViewCreated(view:View,savedInstanceState:Bundle?){counterText.setOnClickListener{viewModel.incrementCount()}}//4.页面刷新回调,每当状态被刷新时这里回调overridefuninvalidate()=withState(viewModel){state->counterText.text="Count:${state.count}"}}如上图,看起来很简洁主要包括几个模块:包括pages所有状态的Model层,其中的状态都是不可变的,有默认值。负责处理业务逻辑的ViewModel,其中页面状态通过setState更新。视图层必须实现MavericksView接口。每当状态刷新时,都会回调invalidate函数,UI会渲染到这里。可以看出,小牛中View层和Model层的交互并没有封装成Action,而是直接暴露出来的方法。上一篇很多同学说用Action交互很麻烦。看来Action层确实是可选的,Airbnb也没用过。主要看个人开发习惯。支持局部刷新上面介绍了Mavericks的简单使用,下面看看Mavericks是如何实现局部刷新的。数据类UserState(valscore:Int=0,valpreviousHighScore:Int=150,vallivesLeft:Int=99,):MavericksState{valpointsUntilHighScore=(previousHighScore-score).coerceAtLeast(0)valisHighScore=score>=previousHighScore}classCounterFragment:Fragment(R.layout.counter_fragment),MavericksView{overridefunonViewCreated(view:View,savedInstanceState:Bundle?){//直接监听State的属性,支持设置监听方式viewModel.onEach(UserState::pointsUntilHighScore,deliveryMode=uniqueOnly()){//..}viewModel.onEach(UserState::score){//...}}}如上图,小牛只能监听State的其中一个属性,实现局部刷新,onlywhen该回调仅在该属性发生变化时触发。onEach还可以设置监听方式,主要是为了防止数据倒流,比如Toast只需要播放一次,页面重建的时候不应该恢复,所以适合使用uniqueOnly监听方式。Mavericks实现属性监控的原理也很简单,一起来看看源码吧。fun,S:MavericksState,A>VM._internal1(owner:LifecycleOwner?,prop1:KProperty1,deliveryMode:DeliveryMode=RedeliverOnStart,action:暂停(A)->Unit)=stateFlow//通过对象获取属性的值。map{MavericksTuple1(prop1.get(it))}//只有当值改变时才会触发回调。distinctUntilChanged().resolveSubscription(owner,deliveryMode.appendPropertiesToId(prop1)){(a)->action(a)}主要是通过map将State转换为其属性值。通过distinctUntilChanged方法开启防抖,相同的值不会被回调,修改值时只会回调该值。需要注意的是,因为使用了KProperty1,所以State的承载数据类要避免混淆。以上就是小牛队的基本介绍。想了解更多的同学可以参考:https://github.com/airbnb/mavericks。LiveData实现了属性监控。上面介绍了Mavericks是如何实现局部刷新的,但是直接使用主要有两个问题。连接起来有点麻烦。比如Fragment必须实现MavericksView,它有一定的连接成本。Mavericks的局部刷新是通过Flow实现的,但是相信大部分人还是用LiveData,有一定的学习成本。我们来看看LiveData是如何实现属性监控的。//监控一个属性funLiveData.observeState(lifecycleOwner:LifecycleOwner,prop1:KProperty1,action:(A)->Unit){this.map{StateTuple1(prop1.get(it))}.distinctUntilChanged().observe(lifecycleOwner){(a)->action.invoke(a)}}//监听两个属性funLiveData.observeState(lifecycleOwner:LifecycleOwner,prop1:KProperty1,prop2:KProperty1,action:(A,B)->Unit){this.map{StateTuple2(prop1.get(it),prop2.get(它))}.distinctUntilChanged().observe(lifecycleOwner){(a,b)->action.invoke(a,b)}}内部数据类StateTuple1(vala:A)内部数据类StateTuple2(vala:A,valb:B)//更新StatefunMutableLiveData.setState(reducer:T.()->T){this.value=this.value?.reducer()}如上所示,主要是增加一个扩展方法,也是通过distinctUntilChanged来实现防抖的。如果需要监控多个属性,比如两个属性之一发生变化,就会触发刷新,也支持传入两个属性。需要注意的是,LiveData默认是不防抖的,所以改造后会防抖,所以不会回调同一个值。同时需要注意的是携带State的数据类需要进行反混淆。简单使用上面介绍了LiveData是如何实现属性监控的,下面看简单的使用。//页面状态,需要避免混淆数据>=MutableLiveData(MainViewState())//只需要暴露一个LiveData,包括页面的所有状态valviewStates=_viewStates.asLiveData()privatefunfetchNews(){//更新页面状态_viewStates.setState{copy(fetchStatus=FetchStatus.Fetching)}viewModelScope.launch{when(valresult=repository.getMockApiResponse()){//...是PageState.Success->{_viewStates.setState{copy(fetchStatus=FetchStatus.Fetched,newsList=result.data)}}}}}}//视图层类MainActivity:AppCompatActivity(){privatefuninitViewModel(){viewModel.viewStates.run{//监听新闻列表observeState(this@MainActivity,MainViewState::newsList){newsRvAdapter.submitList(it)}//监听网络状态observeState(this@MainActivity,MainViewState::fetchStatus){//..}}}}如上所示,其实使用起来非常简单方便。ViewModel只需要对外暴露一个ViewState,避免了定义多变量不可变LiveData的问题。View层支持监控LiveData的一个或多个属性,支持局部刷新。总结本文主要介绍如何在MVI架构下实现局部刷新,重点介绍Mavericks的基本使用和原理,并在此基础上使用LiveData实现属性监控和局部刷新。通过以上方法,解决了MVI架构的性能问题,实现了MVI架构的更好实践。如果你在你的ViewModel中定义了多个可变和不可变的LiveData,即使你不使用MVI架构,支持对LiveData属性的监控相信可以帮助你简化一定数量的代码。如果本文对您有帮助,请点赞关注Star~