日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
MVI架構(gòu)更佳實(shí)踐:支持LiveData屬性監(jiān)聽(tīng)

前言

前面我們介紹了MVI架構(gòu)的基本原理與使用:MVVM 進(jìn)階版:MVI 架構(gòu)了解一下~

十載的臨澤網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開(kāi)發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。成都全網(wǎng)營(yíng)銷推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整臨澤建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)從事“臨澤網(wǎng)站設(shè)計(jì)”,“臨澤網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。

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

通過(guò)集中管理ViewState,只需對(duì)外暴露一個(gè)LiveData,解決了MVVM模式下LiveData膨脹的問(wèn)題。

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

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

因此直接使用MVI架構(gòu)會(huì)帶來(lái)一定的性能損耗,相信這是很多人不愿意用MVI架構(gòu)的原因之一。

本文主要介紹如何通過(guò)監(jiān)聽(tīng)LiveData的屬性,來(lái)實(shí)現(xiàn)MVI架構(gòu)下的局部刷新。

Mavericks框架介紹

Mavericks框架是Airbnb開(kāi)源的一個(gè)MVI框架,Mavericks基于Android Jetpack與Kotlin Coroutines, 主要目標(biāo)是使頁(yè)面開(kāi)發(fā)更高效,更容易,更有趣,目前已經(jīng)在Airbnb的數(shù)百個(gè)頁(yè)面上使用。

下面我們來(lái)看下Mavericks是怎么使用的。

// 1. 包含頁(yè)面所有狀態(tài)的data class
data class CounterState(val count: Int = 0) : MavericksState
// 2.負(fù)責(zé)處理業(yè)務(wù)邏輯的ViewModel,易于單元測(cè)試
class CounterViewModel(initialState: CounterState) : MavericksViewModel(initialState) {
// 通過(guò)setState更新頁(yè)面狀態(tài)
fun incrementCount() = setState { copy(count = count + 1) }
}
// 3. View層,必須實(shí)現(xiàn)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. 頁(yè)面刷新回調(diào),每當(dāng)狀態(tài)刷新時(shí)會(huì)回調(diào)這里
override fun invalidate() = withState(viewModel) { state ->
counterText.text = "Count: ${state.count}"
}
}

如上所示,看上去也很簡(jiǎn)單,主要包括幾個(gè)模塊:

  1. 包括頁(yè)面所有狀態(tài)的Model層,其中的狀態(tài)全都是不可變的,并且有默認(rèn)值。
  2. 負(fù)責(zé)處理業(yè)務(wù)邏輯的ViewModel,在其中通過(guò)setState來(lái)更新頁(yè)面狀態(tài)。
  3. View層,必須實(shí)現(xiàn)MavericksView接口,每當(dāng)狀態(tài)刷新時(shí)都會(huì)回調(diào)invalidate函數(shù),在這里渲染UI。

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

上篇文章也的確有很多同學(xué)說(shuō)使用Action交互比較麻煩,看起來(lái)Action這層的確可要可不要,Airbnb也沒(méi)有使用,主要看個(gè)人開(kāi)發(fā)習(xí)慣吧。

支持局部刷新

上面介紹了Mavericks的簡(jiǎn)單使用,下面我們來(lái)看下Mavericks是怎么實(shí)現(xiàn)局部刷新的 。

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)聽(tīng)State的屬性,并且支持設(shè)置監(jiān)聽(tīng)模式
viewModel.onEach(UserState::pointsUntilHighScore,deliveryMode = uniqueOnly()) {
//..
}
viewModel.onEach(UserState::score) {
//...
}
}
}

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

Mavericks實(shí)現(xiàn)屬性監(jiān)聽(tīng)的原理也很簡(jiǎn)單,我們一起來(lái)看下源碼。

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

  1. 主要是通過(guò)map將State轉(zhuǎn)化為它的屬性值。
  2. 通過(guò)distinctUntilChanged方法開(kāi)啟防抖,相同的值不會(huì)回調(diào),只有值修改了才會(huì)回調(diào)。
  3. 需要注意的是因?yàn)槭褂昧薑Property1,因此State的承載數(shù)據(jù)類必須避免混淆。

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

LiveData實(shí)現(xiàn)屬性監(jiān)聽(tīng)

上面介紹了Mavericks是怎么實(shí)現(xiàn)局部刷新的,但直接使用它主要有兩個(gè)問(wèn)題。

  1. 接入起來(lái)略微有點(diǎn)麻煩,例如Fragment必須實(shí)現(xiàn)MavericksView,有一定接入成本。
  2. Mavericks的局部刷新是通過(guò)Flow實(shí)現(xiàn)的,但相信大多數(shù)人用的還是LiveData,有一定學(xué)習(xí)成本。

下面我們就來(lái)看下LiveData怎么實(shí)現(xiàn)屬性監(jiān)聽(tīng)。

//監(jiān)聽(tīng)一個(gè)屬性
fun LiveData.observeState(
lifecycleOwner: LifecycleOwner,
prop1: KProperty1,
action: (A) -> Unit
) {
this.map {
StateTuple1(prop1.get(it))
}.distinctUntilChanged().observe(lifecycleOwner) { (a) ->
action.invoke(a)
}
}
//監(jiān)聽(tīng)兩個(gè)屬性
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. 如上所示,主要是添加一個(gè)擴(kuò)展方法,也是通過(guò)distinctUntilChanged來(lái)實(shí)現(xiàn)防抖。
  2. 如果需要監(jiān)聽(tīng)多個(gè)屬性,例如兩個(gè)屬性有其中一個(gè)變化了就觸發(fā)刷新,也支持傳入兩個(gè)屬性。
  3. 需要注意的是LiveData默認(rèn)是不防抖的,這樣改造后就是防抖的了,所以傳入相同的值是不會(huì)回調(diào)的。
  4. 同時(shí)需要注意下承載State的數(shù)據(jù)類需要防混淆。

簡(jiǎn)單使用

上面介紹了LiveData如何實(shí)現(xiàn)屬性監(jiān)聽(tīng),下面看下簡(jiǎn)單的使用。

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

如上所示,其實(shí)使用起來(lái)也很簡(jiǎn)單方便。

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

總結(jié)

本文主要介紹了MVI架構(gòu)下如何實(shí)現(xiàn)局部刷新,并重點(diǎn)介紹了Mavericks的基本使用與原理,并在其基礎(chǔ)上使用LiveData實(shí)現(xiàn)了屬性監(jiān)聽(tīng)與局部刷新。

通過(guò)以上方式,解決了MVI架構(gòu)的性能問(wèn)題,實(shí)現(xiàn)了MVI架構(gòu)的更佳實(shí)踐。

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

如果本文對(duì)你有所幫助,歡迎點(diǎn)贊關(guān)注Star~


分享文章:MVI架構(gòu)更佳實(shí)踐:支持LiveData屬性監(jiān)聽(tīng)
轉(zhuǎn)載來(lái)于:
http://www.5511xx.com/article/cdcjocc.html