響應(yīng)式架構(gòu)最佳實(shí)踐——MVI

點(diǎn)擊上方藍(lán)字關(guān)注我,知識會給你力量

這個系列我做了協(xié)程和Flow開發(fā)者的一系列文章的翻譯,旨在了解當(dāng)前協(xié)程、Flow、LiveData這樣設(shè)計的原因,從設(shè)計者的角度,發(fā)現(xiàn)他們的問題,以及如何解決這些問題,pls enjoy it。
MVVM和MVI架構(gòu)模式的精華合二為一,為任何Android項目提供了完美的架構(gòu)。
?如果你已經(jīng)知道架構(gòu)模式的基本原則,以及MVVM和MVI模式的細(xì)節(jié),那么跳過基礎(chǔ)知識,跳到文章的MVI+LiveData+ViewModel(或第二部分)。
?
Preface
有這么多的架構(gòu)模式,每個模式都有一些優(yōu)點(diǎn)和缺點(diǎn)。所有這些模式都試圖實(shí)現(xiàn)相同的架構(gòu)基本原則。
Separation of concerns(SoC)。這是一個將計算機(jī)程序分離成不同部分的設(shè)計原則,使每個部分解決一個單獨(dú)的問題。關(guān)注點(diǎn)是指在提供問題的解決方案方面的任何事情。這一原則與面向?qū)ο缶幊痰膯我回?zé)任原則密切相關(guān),該原則指出:"每個模塊、類或函數(shù)都應(yīng)該對軟件所提供的功能的單一部分負(fù)責(zé),而且該責(zé)任應(yīng)該完全由類、模塊或函數(shù)封裝。" -維基百科 Drive UI from a model。應(yīng)用程序應(yīng)該從一個Model中驅(qū)動用戶界面,最好是一個持久性Model。Model獨(dú)立于視圖對象和應(yīng)用程序組件,所以它們不受應(yīng)用程序的生命周期和相關(guān)關(guān)注點(diǎn)的影響。
讓我們也來看看一些流行的架構(gòu)模式的總結(jié)。
? MVC Architecture:
Trygve Reenskaug的Model-視圖-控制器架構(gòu)是所有現(xiàn)代架構(gòu)模式的基礎(chǔ)。讓我們來看看維基百科上定義的每個組件的職責(zé)。
Model負(fù)責(zé)管理應(yīng)用程序的數(shù)據(jù)。它接收來自controller的輸入。 View意味著以特定的格式展示Model。 controller對用戶的輸入做出反應(yīng),并對數(shù)據(jù)Model對象進(jìn)行交互。controller接收輸入,選擇性地驗證它,然后將輸入傳遞給Model。所以,Model負(fù)責(zé)表示狀態(tài)、結(jié)構(gòu)和視圖的行為,而視圖只不過是該Model的代表。
? MVVM Architecture:
在Model-View-ViewModel架構(gòu)中,視圖擁有ViewModel的實(shí)例,它根據(jù)用戶的輸入/動作調(diào)用相應(yīng)的函數(shù)。同時,視圖觀察ViewModel的不同可觀察屬性的變化。ViewModel根據(jù)業(yè)務(wù)邏輯處理用戶輸入并修改各自的可觀察屬性。
? MVI Architecture:
在Model-View-Intent架構(gòu)中,視圖暴露了視圖-事件(用戶輸入/行動),并觀察Model的視圖狀態(tài)變化。我們處理視圖事件,將其轉(zhuǎn)換為各自的意圖,并將其傳遞給Model。Model層使用意圖和先前的視圖狀態(tài)創(chuàng)建一個新的不可變的視圖狀態(tài)。因此,這種方式遵循單向數(shù)據(jù)流原則,即數(shù)據(jù)只在一個方向流動。View>Intent>Model>View。
總之,MVVM架構(gòu)最好的部分是ViewModel,但我認(rèn)為它沒有遵循MVC模式中定義的Model概念,因為在MVVM中,DAO(數(shù)據(jù)訪問對象)的抽象被認(rèn)為是Model,視圖觀察來自ViewModel的多個可觀察屬性的狀態(tài)變化,視圖不是由Model直接驅(qū)動。另外,這些來自ViewModel的多個可觀察屬性會導(dǎo)致狀態(tài)重疊問題(兩個不同的狀態(tài)被意外顯示)。
MVI模式通過添加一個實(shí)際的 "Model "層來解決這個問題,該層由視圖觀察狀態(tài)變化。由于這個Model是不可改變的,并且是當(dāng)前視圖狀態(tài)的單一真理來源,所以狀態(tài)重疊不會發(fā)生。
在下面的架構(gòu)中,我試圖結(jié)合MVVM和MVI模式的優(yōu)點(diǎn),為任何Android項目提供更好的架構(gòu),在此基礎(chǔ)上,我通過為View和ViewModel創(chuàng)建基類,盡可能多地抽象出一些東西。
?? MVI + LiveData + ViewModel = ?? Architecture:
在繼續(xù)之前,讓我們重新強(qiáng)調(diào)一下MVI架構(gòu)中的一些基本術(shù)語。
ViewState:顧名思義,這是Model層的一部分,我們的視圖要觀察這個Model的狀態(tài)變化。ViewState應(yīng)該代表視圖在任何給定時間的當(dāng)前狀態(tài)。所以這個類應(yīng)該有我們的視圖所依賴的所有變量內(nèi)容。每次有任何用戶的輸入/動作,我們都會暴露這個類的修改過的副本(以保持之前沒有被修改的狀態(tài))。我們可以使用Kotlin的Data Class來創(chuàng)建這個Model。
data class MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)
sealed class FetchStatus {
object Fetching : FetchStatus()
object Fetched : FetchStatus()
object NotFetched : FetchStatus()
}
ViewEffect:在Android中,我們有一些動作更像是fire-and-forget,例如Toast,在這些情況下,我們不能使用ViewState,因為它保持狀態(tài)。這意味著,如果我們使用ViewState來顯示Toast,它將在配置改變或每次有新的狀態(tài)時再次顯示,除非我們通過 "toast is shown "事件來重置其狀態(tài)。如果你不希望這樣做,你可以使用ViewEffect,因為它是基于SingleLiveEvent的,不需要維護(hù)狀態(tài)。ViewEffect也是我們Model的一部分,我們可以使用Kotlin的密封類來創(chuàng)建它。
sealed class MainViewEffect {
data class ShowSnackbar(val message: String) : MainViewEffect()
data class ShowToast(val message: String) : MainViewEffect()
}
ViewEvent:它表示用戶可以在視圖上執(zhí)行的所有動作/事件。它用于將用戶的輸入/動作傳遞給ViewModel。我們可以使用Kotlin的Sealed Class來創(chuàng)建這個事件集。
sealed class MainViewEvent {
data class NewsItemClicked(val newsItem: NewsItem) : MainViewEvent()
object FabClicked : MainViewEvent()
object OnSwipeRefresh : MainViewEvent()
object FetchNews : MainViewEvent()
}
我建議你,把這三個類放在一個文件里,因為它能讓你對目標(biāo)視圖處理的所有可做動作和變量內(nèi)容有一個整體的概念。
現(xiàn)在,讓我們更深入地了解這個架構(gòu)。

上面的圖可能已經(jīng)給了你這個架構(gòu)的核心思想。如果沒有,這個架構(gòu)的核心思想是,我們在MVVM架構(gòu)中包括一個實(shí)際的不可變的Model層,我們的視圖依賴于這個Model的狀態(tài)變化。這樣一來,ViewModel就必須修改和公開這個單一的Model。
為了避免冗余和簡化這種架構(gòu)在多個地方的使用,我創(chuàng)建了兩個抽象類,一個用于我們的視圖(為Activity、Fragment、自定義視圖分開),一個用于ViewModel。
AacMviViewModel。一個通用的基類來創(chuàng)建ViewModel。它需要三個類STATE、EFFECT和EVENT。我們已經(jīng)在上面看到了這些類的一個例子。
open class AacMviViewModel<STATE, EFFECT, EVENT>(application: Application) :
AndroidViewModel(application), ViewModelContract<EVENT> {
private val _viewStates: MutableLiveData<STATE> = MutableLiveData()
fun viewStates(): LiveData<STATE> = _viewStates
private var _viewState: STATE? = null
protected var viewState: STATE
get() = _viewState
?: throw UninitializedPropertyAccessException("\"viewState\" was queried before being initialized")
set(value) {
Log.d(TAG, "setting viewState : $value")
_viewState = value
_viewStates.value = value
}
private val _viewEffects: SingleLiveEvent<EFFECT> = SingleLiveEvent()
fun viewEffects(): SingleLiveEvent<EFFECT> = _viewEffects
private var _viewEffect: EFFECT? = null
protected var viewEffect: EFFECT
get() = _viewEffect
?: throw UninitializedPropertyAccessException("\"viewEffect\" was queried before being initialized")
set(value) {
Log.d(TAG, "setting viewEffect : $value")
_viewEffect = value
_viewEffects.value = value
}
@CallSuper
override fun process(viewEvent: EVENT) {
Log.d(TAG, "processing viewEvent: $viewEvent")
}
}
如你所見,我們有viewState。STATE和viewEffect。EFFECT和兩個私有的LiveData容器_viewStates。MutableLiveData
這就是我們?nèi)绾螢槲覀兊娜魏蜛ctivity/Fragment/視圖創(chuàng)建一個ViewModel。
class MainActVM(application: Application) :
AacMviViewModel<MainViewState, MainViewEffect, MainViewEvent>(application) {
init {
viewState = MainViewState(fetchStatus = FetchStatus.NotFetched, newsList = emptyList())
}
override fun process(viewEvent: MainViewEvent) {
super.process(viewEvent)
}
}
我們所要做的就是在init{}塊中初始化viewState,并在需要時使用數(shù)據(jù)類的copy()函數(shù)進(jìn)一步修改viewState。請不要修改viewState的同一個實(shí)例,而要修改它的副本,這樣我們就能保持viewState的不可改變性。如果你修改了viewState的同一個實(shí)例,你可能會遇到意外的行為,因為你可能會改變一些正在被視圖處理的屬性。
viewState = viewState.copy(fetchStatus = FetchStatus.Fetched, newsList = result.data)
就是這樣,其余部分(即_viewStates.setValue())由AacMviViewModel類處理。
AacMviActivity。一個通用的抽象類,用于為這個架構(gòu)創(chuàng)建兼容的Activity。
(請參考這個資源庫,了解Fragment和自定義視圖所需的通用類:https://github.com/RohitSurwase/AAC-MVI-Architecture)
abstract class AacMviActivity<STATE, EFFECT, EVENT, ViewModel : AacMviViewModel<STATE, EFFECT, EVENT>> :
AppCompatActivity() {
abstract val viewModel: ViewModel
private val viewStateObserver = Observer<STATE> {
Log.d(TAG, "observed viewState : $it")
renderViewState(it)
}
private val viewEffectObserver = Observer<EFFECT> {
Log.d(TAG, "observed viewEffect : $it")
renderViewEffect(it)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.viewStates().observe(this, viewStateObserver)
viewModel.viewEffects().observe(this, viewEffectObserver)
}
abstract fun renderViewState(viewState: STATE)
abstract fun renderViewEffect(viewEffect: EFFECT)
}
它有viewModel、renderViewState()和renderViewEffect()的抽象屬性/函數(shù),我們需要實(shí)現(xiàn)它們。另外,它在內(nèi)部創(chuàng)建了viewStateObserver、viewEffectObserver LiveData-Observers,并開始觀察ViewModel在Activity的onCreate()中暴露的viewStates()和viewEffects()。因此,這個抽象的Activity做了所有我們必須在每個Activity中做的事情。此外,它還記錄了每個觀察到的viewState和viewEffect。
現(xiàn)在,為這個架構(gòu)創(chuàng)建一個新的Activity是非常容易的。
class MainActivity : AacMviActivity<MainViewState, MainViewEffect, MainViewEvent, MainActVM>() {
override val viewModel: MainActVM by viewModels()
override fun renderViewState(viewState: MainViewState) {
//Handle new viewState
}
override fun renderViewEffect(viewEffect: MainViewEffect) {
//Show effects
}
}
就這樣,我們有了一切,無縫工作,記錄我們正在處理的每個動作和內(nèi)容。由于Model是視圖狀態(tài)變化的單一真相來源,所以沒有可能出現(xiàn)狀態(tài)重疊。
注意:如果你是這個 "Model驅(qū)動的用戶界面 "的新手,你可能會認(rèn)為我們增加了比直接處理更多的復(fù)雜性,因為對于一些復(fù)雜的視圖,ViesState數(shù)據(jù)類會有很多屬性,因為它必須有每個小部件的內(nèi)容和它的可見性等等。但相信我,這將會得到回報,因為追蹤任何問題/崩潰的原因?qū)⒆兊梅浅H菀住?/p>
原文鏈接:https://proandroiddev.com/best-architecture-for-android-mvi-livedata-viewmodel-71a3a5ac7ee3
向大家推薦下我的網(wǎng)站 https://xuyisheng.top/ 點(diǎn)擊原文一鍵直達(dá)
專注 Android-Kotlin-Flutter 歡迎大家訪問
往期推薦
更文不易,點(diǎn)個“三連”支持一下??
