MVI架构更佳实践:支持LiveData属性监听

前言

前面我们介绍了MVI架构的基本原理与使用:MVVM 进阶版:MVI 架构了解一下~

十载的临泽网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。成都全网营销推广的优势是能够根据用户设备显示端的尺寸不同,自动调整临泽建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联从事“临泽网站设计”,“临泽网站推广”以来,每个客户项目都认真落实执行。

MVI架构为了解决MVVM在逻辑复杂时需要写多个LiveData(可变+不可变)的问题,使用ViewState对State集中管理,只需要订阅一个 ViewState 便可获取页面的所有状态。

通过集中管理ViewState,只需对外暴露一个LiveData,解决了MVVM模式下LiveData膨胀的问题。

但页面的所有状态都通过一个LiveData来管理,也带来了一个严重的问题,即页面不支持局部刷新。

虽说如果是RecyclerView可以通过DifferUtil来解决,但毕竟不是所有页面都是通过RecyclerView写的,支持DifferUtil也有一定的开发成本。

因此直接使用MVI架构会带来一定的性能损耗,相信这是很多人不愿意用MVI架构的原因之一。

本文主要介绍如何通过监听LiveData的属性,来实现MVI架构下的局部刷新。

Mavericks框架介绍

Mavericks框架是Airbnb开源的一个MVI框架,Mavericks基于Android Jetpack与Kotlin Coroutines, 主要目标是使页面开发更高效,更容易,更有趣,目前已经在Airbnb的数百个页面上使用。

下面我们来看下Mavericks是怎么使用的。

// 1. 包含页面所有状态的data class
data class CounterState(val count: Int = 0) : MavericksState
// 2.负责处理业务逻辑的ViewModel,易于单元测试
class CounterViewModel(initialState: CounterState) : MavericksViewModel(initialState) {
// 通过setState更新页面状态
fun incrementCount() = setState { copy(count = count + 1) }
}
// 3. View层,必须实现MavericksView接口
class CounterFragment : Fragment(R.layout.counter_fragment), MavericksView {
private val viewModel: CounterViewModel by fragmentViewModel()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
counterText.setOnClickListener {
viewModel.incrementCount()
}
}
//4. 页面刷新回调,每当状态刷新时会回调这里
override fun invalidate() = withState(viewModel) { state ->
counterText.text = "Count: ${state.count}"
}
}

如上所示,看上去也很简单,主要包括几个模块:

  1. 包括页面所有状态的Model层,其中的状态全都是不可变的,并且有默认值。
  2. 负责处理业务逻辑的ViewModel,在其中通过setState来更新页面状态。
  3. View层,必须实现MavericksView接口,每当状态刷新时都会回调invalidate函数,在这里渲染UI。

可以看出,Mavericks中View层与Model层的交互,也并没有包装成Action,而是直接暴露的方法。

上篇文章也的确有很多同学说使用Action交互比较麻烦,看起来Action这层的确可要可不要,Airbnb也没有使用,主要看个人开发习惯吧。

支持局部刷新

上面介绍了Mavericks的简单使用,下面我们来看下Mavericks是怎么实现局部刷新的 。

data class UserState(
val score: Int = 0,
val previousHighScore: Int = 150,
val livesLeft: Int = 99,
) : MavericksState {
val pointsUntilHighScore = (previousHighScore - score).coerceAtLeast(0)
val isHighScore = score >= previousHighScore
}
class CounterFragment : Fragment(R.layout.counter_fragment), MavericksView {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
//直接监听State的属性,并且支持设置监听模式
viewModel.onEach(UserState::pointsUntilHighScore,deliveryMode = uniqueOnly()) {
//..
}
viewModel.onEach(UserState::score) {
//...
}
}
}

  1. 如上所示,Mavericks可以只监听State的其中一个属性来实现局部刷新,只有当这个属性发生变化时才触发回调。
  2. onEach也可以设置监听模式,主要是为了防止数据倒灌,例如Toast这些只需要弹一次,页面重建时不应该恢复的状态,就适合使用uniqueOnly的监听模式。

Mavericks实现属性监听的原理也很简单,我们一起来看下源码。

fun , S : MavericksState, A> VM._internal1(
owner: LifecycleOwner?,
prop1: KProperty1,
deliveryMode: DeliveryMode = RedeliverOnStart,
action: suspend (A) -> Unit
) = stateFlow
// 通过对象取出属性的值
.map { MavericksTuple1(prop1.get(it)) }
// 值发生变化了才会触发回调
.distinctUntilChanged()
.resolveSubscription(owner, deliveryMode.appendPropertiesToId(prop1)) { (a) ->
action(a)
}

  1. 主要是通过map将State转化为它的属性值。
  2. 通过distinctUntilChanged方法开启防抖,相同的值不会回调,只有值修改了才会回调。
  3. 需要注意的是因为使用了KProperty1,因此State的承载数据类必须避免混淆。

如上,就是Mavericks的基本介绍,想了解更多的同学可参考:https://github.com/airbnb/mavericks。

LiveData实现属性监听

上面介绍了Mavericks是怎么实现局部刷新的,但直接使用它主要有两个问题。

  1. 接入起来略微有点麻烦,例如Fragment必须实现MavericksView,有一定接入成本。
  2. Mavericks的局部刷新是通过Flow实现的,但相信大多数人用的还是LiveData,有一定学习成本。

下面我们就来看下LiveData怎么实现属性监听。

//监听一个属性
fun LiveData.observeState(
lifecycleOwner: LifecycleOwner,
prop1: KProperty1,
action: (A) -> Unit
) {
this.map {
StateTuple1(prop1.get(it))
}.distinctUntilChanged().observe(lifecycleOwner) { (a) ->
action.invoke(a)
}
}
//监听两个属性
fun LiveData.observeState(
lifecycleOwner: LifecycleOwner,
prop1: KProperty1,
prop2: KProperty1,
action: (A, B) -> Unit
) {
this.map {
StateTuple2(prop1.get(it), prop2.get(it))
}.distinctUntilChanged().observe(lifecycleOwner) { (a, b) ->
action.invoke(a, b)
}
}
internal data class StateTuple1(val a: A)
internal data class StateTuple2(val a: A, val b: B)
//更新State
fun MutableLiveData.setState(reducer: T.() -> T) {
this.value = this.value?.reducer()
}

  1. 如上所示,主要是添加一个扩展方法,也是通过distinctUntilChanged来实现防抖。
  2. 如果需要监听多个属性,例如两个属性有其中一个变化了就触发刷新,也支持传入两个属性。
  3. 需要注意的是LiveData默认是不防抖的,这样改造后就是防抖的了,所以传入相同的值是不会回调的。
  4. 同时需要注意下承载State的数据类需要防混淆。

简单使用

上面介绍了LiveData如何实现属性监听,下面看下简单的使用。

//页面状态,需要避免混淆
data class MainViewState(
val fetchStatus: FetchStatus = FetchStatus.NotFetched,
val newsList: List = emptyList()
)
//ViewModel
class MainViewModel : ViewModel() {
private val _viewStates: MutableLiveData = MutableLiveData(MainViewState())
//只需要暴露一个LiveData,包括页面所有状态
val viewStates = _viewStates.asLiveData()
private fun fetchNews() {
//更新页面状态
_viewStates.setState {
copy(fetchStatus = FetchStatus.Fetching)
}
viewModelScope.launch {
when (val result = repository.getMockApiResponse()) {
//...
is PageState.Success -> {
_viewStates.setState {
copy(fetchStatus = FetchStatus.Fetched, newsList = result.data)
}
}
}
}
}
}
//View层
class MainActivity : AppCompatActivity() {
private fun initViewModel() {
viewModel.viewStates.run {
//监听newsList
observeState(this@MainActivity, MainViewState::newsList) {
newsRvAdapter.submitList(it)
}
//监听网络状态
observeState(this@MainActivity, MainViewState::fetchStatus) {
//..
}
}
}
}

如上所示,其实使用起来也很简单方便。

  1. ViewModel只需对外暴露一个ViewState,避免了定义多个可变不可变LiveData的问题。
  2. View层支持监听LiveData的一个属性或多个属性,支持局部刷新。

总结

本文主要介绍了MVI架构下如何实现局部刷新,并重点介绍了Mavericks的基本使用与原理,并在其基础上使用LiveData实现了属性监听与局部刷新。

通过以上方式,解决了MVI架构的性能问题,实现了MVI架构的更佳实践。

如果你的ViewModel中定义了多个可变与不可变的LiveData,就算你不使用MVI架构,支持监听LiveData属性相信也可以帮助你精简一定的代码。

如果本文对你有所帮助,欢迎点赞关注Star~

分享题目:MVI架构更佳实践:支持LiveData属性监听
本文URL:http://www.hantingmc.com/qtweb/news40/505740.html

网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

广告

声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联