自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

MVI 架構更佳實踐:支持 LiveData 屬性監(jiān)聽

開發(fā) 架構
本文主要介紹如何通過監(jiān)聽LiveData的屬性,來實現MVI架構下的局部刷新。

前言

前面我們介紹了MVI架構的基本原理與使用:MVVM 進階版:MVI 架構了解一下~

MVI架構為了解決MVVM在邏輯復雜時需要寫多個LiveData(可變+不可變)的問題,使用ViewState對State集中管理,只需要訂閱一個 ViewState 便可獲取頁面的所有狀態(tài)。

通過集中管理ViewState,只需對外暴露一個LiveData,解決了MVVM模式下LiveData膨脹的問題。

但頁面的所有狀態(tài)都通過一個LiveData來管理,也帶來了一個嚴重的問題,即頁面不支持局部刷新。

雖說如果是RecyclerView可以通過DifferUtil來解決,但畢竟不是所有頁面都是通過RecyclerView寫的,支持DifferUtil也有一定的開發(fā)成本。

因此直接使用MVI架構會帶來一定的性能損耗,相信這是很多人不愿意用MVI架構的原因之一。

本文主要介紹如何通過監(jiān)聽LiveData的屬性,來實現MVI架構下的局部刷新。

Mavericks框架介紹

Mavericks框架是Airbnb開源的一個MVI框架,Mavericks基于Android Jetpack與Kotlin Coroutines, 主要目標是使頁面開發(fā)更高效,更容易,更有趣,目前已經在Airbnb的數百個頁面上使用。

下面我們來看下Mavericks是怎么使用的。

// 1. 包含頁面所有狀態(tài)的data class
data class CounterState(val count: Int = 0) : MavericksState
// 2.負責處理業(yè)務邏輯的ViewModel,易于單元測試
class CounterViewModel(initialState: CounterState) : MavericksViewModel<CounterState>(initialState) {
// 通過setState更新頁面狀態(tài)
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. 頁面刷新回調,每當狀態(tài)刷新時會回調這里
override fun invalidate() = withState(viewModel) { state ->
counterText.text = "Count: ${state.count}"
}
}

如上所示,看上去也很簡單,主要包括幾個模塊:

  1. 包括頁面所有狀態(tài)的Model層,其中的狀態(tài)全都是不可變的,并且有默認值。
  2. 負責處理業(yè)務邏輯的ViewModel,在其中通過setState來更新頁面狀態(tài)。
  3. View層,必須實現MavericksView接口,每當狀態(tài)刷新時都會回調invalidate函數,在這里渲染UI。

可以看出,Mavericks中View層與Model層的交互,也并沒有包裝成Action,而是直接暴露的方法。

上篇文章也的確有很多同學說使用Action交互比較麻煩,看起來Action這層的確可要可不要,Airbnb也沒有使用,主要看個人開發(fā)習慣吧。

支持局部刷新

上面介紹了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?) {
//直接監(jiān)聽State的屬性,并且支持設置監(jiān)聽模式
viewModel.onEach(UserState::pointsUntilHighScore,deliveryMode = uniqueOnly()) {
//..
}
viewModel.onEach(UserState::score) {
//...
}
}
}

  1. 如上所示,Mavericks可以只監(jiān)聽State的其中一個屬性來實現局部刷新,只有當這個屬性發(fā)生變化時才觸發(fā)回調。
  2. onEach也可以設置監(jiān)聽模式,主要是為了防止數據倒灌,例如Toast這些只需要彈一次,頁面重建時不應該恢復的狀態(tài),就適合使用uniqueOnly的監(jiān)聽模式。

Mavericks實現屬性監(jiān)聽的原理也很簡單,我們一起來看下源碼。

fun <VM : MavericksViewModel<S>, S : MavericksState, A> VM._internal1(
owner: LifecycleOwner?,
prop1: KProperty1<S, A>,
deliveryMode: DeliveryMode = RedeliverOnStart,
action: suspend (A) -> Unit
) = stateFlow
// 通過對象取出屬性的值
.map { MavericksTuple1(prop1.get(it)) }
// 值發(fā)生變化了才會觸發(fā)回調
.distinctUntilChanged()
.resolveSubscription(owner, deliveryMode.appendPropertiesToId(prop1)) { (a) ->
action(a)
}

  1. 主要是通過map將State轉化為它的屬性值。
  2. 通過distinctUntilChanged方法開啟防抖,相同的值不會回調,只有值修改了才會回調。
  3. 需要注意的是因為使用了KProperty1,因此State的承載數據類必須避免混淆。

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

LiveData實現屬性監(jiān)聽

上面介紹了Mavericks是怎么實現局部刷新的,但直接使用它主要有兩個問題。

  1. 接入起來略微有點麻煩,例如Fragment必須實現MavericksView,有一定接入成本。
  2. Mavericks的局部刷新是通過Flow實現的,但相信大多數人用的還是LiveData,有一定學習成本。

下面我們就來看下LiveData怎么實現屬性監(jiān)聽。

//監(jiān)聽一個屬性
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)
}
}
//監(jiān)聽兩個屬性
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)
}
}
internal data class StateTuple1<A>(val a: A)
internal data class StateTuple2<A, B>(val a: A, val b: B)
//更新State
fun <T> MutableLiveData<T>.setState(reducer: T.() -> T) {
this.value = this.value?.reducer()
}

  1. 如上所示,主要是添加一個擴展方法,也是通過distinctUntilChanged來實現防抖。
  2. 如果需要監(jiān)聽多個屬性,例如兩個屬性有其中一個變化了就觸發(fā)刷新,也支持傳入兩個屬性。
  3. 需要注意的是LiveData默認是不防抖的,這樣改造后就是防抖的了,所以傳入相同的值是不會回調的。
  4. 同時需要注意下承載State的數據類需要防混淆。

簡單使用

上面介紹了LiveData如何實現屬性監(jiān)聽,下面看下簡單的使用。

//頁面狀態(tài),需要避免混淆
data class MainViewState(
val fetchStatus: FetchStatus = FetchStatus.NotFetched,
val newsList: List<NewsItem> = emptyList()
)
//ViewModel
class MainViewModel : ViewModel() {
private val _viewStates: MutableLiveData<MainViewState> = MutableLiveData(MainViewState())
//只需要暴露一個LiveData,包括頁面所有狀態(tài)
val viewStates = _viewStates.asLiveData()
private fun fetchNews() {
//更新頁面狀態(tài)
_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 {
//監(jiān)聽newsList
observeState(this@MainActivity, MainViewState::newsList) {
newsRvAdapter.submitList(it)
}
//監(jiān)聽網絡狀態(tài)
observeState(this@MainActivity, MainViewState::fetchStatus) {
//..
}
}
}
}

如上所示,其實使用起來也很簡單方便。

  1. ViewModel只需對外暴露一個ViewState,避免了定義多個可變不可變LiveData的問題。
  2. View層支持監(jiān)聽LiveData的一個屬性或多個屬性,支持局部刷新。

總結

本文主要介紹了MVI架構下如何實現局部刷新,并重點介紹了Mavericks的基本使用與原理,并在其基礎上使用LiveData實現了屬性監(jiān)聽與局部刷新。

通過以上方式,解決了MVI架構的性能問題,實現了MVI架構的更佳實踐。

如果你的ViewModel中定義了多個可變與不可變的LiveData,就算你不使用MVI架構,支持監(jiān)聽LiveData屬性相信也可以幫助你精簡一定的代碼。

如果本文對你有所幫助,歡迎點贊關注Star~

責任編輯:龐桂玉 來源: 安卓開發(fā)精選
相關推薦

2018-11-06 12:32:02

多云云平臺云計算

2024-03-11 00:00:00

應用架構開發(fā)

2017-08-11 17:20:07

LinuxShell

2018-05-30 15:15:47

混合云公共云私有云

2017-07-18 16:40:31

AndroidLiveData

2022-03-02 15:31:32

架構網絡請求代碼

2018-05-24 09:00:45

2011-08-18 12:19:17

vSwitchVMNIC

2017-05-29 08:18:11

Serverless架構軟件系統(tǒng)

2013-09-23 11:35:46

戴爾

2022-06-08 08:45:46

Redis緩存代碼

2022-04-18 09:41:14

Go架構設計

2021-03-17 08:12:03

架構Dotnet洋蔥

2013-11-20 10:21:30

閃存

2019-11-15 15:03:34

AI工業(yè)4.0制造業(yè)

2010-06-28 17:30:34

數據中心制冷風冷液冷

2015-09-15 16:01:40

混合IT私有云IT架構

2023-11-14 20:51:08

2020-03-09 11:04:49

華為5G運營商

2021-08-26 05:27:57

Swift 監(jiān)聽系統(tǒng)泛型
點贊
收藏

51CTO技術棧公眾號