天天看點

Android Jetpack系列的MVI架構

Android Jetpack系列的MVI架構

1.差別:LiveData < T >更改為Flow< UIState >

關于LiveData的缺點:

LiveData隻能在主線程中接收;

LiveData發送的資料是一次性交易,不能多次發送;

LiveData發送資料的線程是固定的,無法切換線程。setValue/postValue本質上是在主線程上發送的。需要來回切換線程的時候,LiveData是無能為力的。

Flow可以完美解決LiveData遇到的問題。它不僅可以多次從上遊發送資料,還可以靈活地切換線程,是以如果涉及到來回切割線程,那麼使用Flow是一個比較好的解決方案。關于Flow的詳細用法,有興趣的同學可以參考:Android Kotlin的Flow資料流。

注意:如果在項目中沒有切換到Kotlin,仍然可以使用LiveData發送資料;如果已經改用Kotlin,更推薦使用Flow發送資料。

還有一個差別。在舊的架構中,LiveData傳輸單個實體資料,即每個資料将對應一個LiveData。顯然,如果頁面邏輯複雜,會導緻ViewModel中LiveData的擴充;在新架構中,Flow發送的UIState是統一的,UIState本質上是一個資料類。不同的是UIState會統一控制視圖層相關的實體狀态,是以在ViewModel中隻需要一個流來統一互動。

2.差異,互動規範

新架構中提出了單向資料流管理頁面狀态的概念:即資料流方向是固定的,整個資料流方向是視圖->視圖模型->模型資料層->視圖模型擷取資料->根據UiState重新整理視圖層。其中,事件Events向上流,狀态UiState向下流。整個過程如下:

ViewModel存儲并公開界面使用的狀态。界面是ViewModel轉換的應用資料。

該接口将向ViewModel發送使用者事件通知。

ViewModel處理使用者操作并更新狀态。

更新後的狀态将回報到界面進行展示。

系統将對導緻狀态變化的所有事件重複上述操作。

單向資料流提高了代碼的可讀性和修改的便利性。單向資料流有以下好處:

資料一緻性。該接口隻有一個可信來源。

可測試性。源代碼是獨立的,是以可以獨立于接口進行測試。

可維護性。的狀态變化遵循一個明确定義的模式,即狀态變化是使用者事件及其資料拉取源互動的結果。

MVI實戰

定義UIState & Write視圖模型

類MViewModel : BaseViewModel() {

//Repository中間層管理所有資料源,包括本地和網絡。

private val mwan repo = wan repository()

覆寫fun initUiState(): MviState {

傳回MviState(BannerUiState。INIT,DetailUiState。初始化)

}

//請求橫幅資料

fun loadBannerData() {

requestDataWithFlow(

showLoading = true,

request = { mwan repo . request wandata(" " },

success callback = { data-->

sendUiState {

copy(banner uistate = banner uistate。成功(資料))

}

},

failCallback = {}

)

}

//請求清單資料

fun loadDetailData() {

requestDataWithFlow(

showLoading = false,

request = { mwan repo . request rank data()},

success callback = { data-->

sendUiState {

copy(detail uistate = detail uistate。成功(資料))

}

},

)

}

有趣的showToast() {

Sendleustate (mvisinglestate("觸發了一次性消費事件!"))

}

}

/**

*在這裡定義UiState包含所有與視圖層相關的實體類,可以有效避免模闆代碼(StateFlow隻需要定義一個)

*/

資料類mvi state(val bannerUiState:bannerUiState,val detailUiState: DetailUiState?):IUiState

資料類mvISingleUiState(val消息:字元串):isingleistate

密封類BannerUiState {

對象初始化:BannerUiState()

資料類成功(val模型:清單):BannerUiState()

}

密封類DetailUiState {

對象初始化:DetailUiState()

資料類成功(val detail:rank model):DetailUiState()

}

複制代碼

MviState中定義的UIState是視圖層相關的資料類,而一次性消費事件,如吐司、頁面跳轉等。,在MViState中定義。是以,用于互動的通道在上一篇文章中已經提到,這裡不再贅述。

相關接口:

接口IUiState //重複事件可以多次使用。

界面白榴石//一次性事件,不支援多次消費。

對象EmptySingleState:isingleistate

//一次性事件,不支援多次消費。

密封類LoadUiState {

資料類加載(var isShow: Boolean) : LoadUiState()

對象ShowMainView : LoadUiState()

資料類錯誤(val msg: String) : LoadUiState()

}

複制代碼

LoadUiState定義了頁面加載的幾種狀态:加載中、ShowMainView加載成功、Error加載失敗。幾種狀态的使用和切換封裝在BaseViewModel的資料請求中。具體使用請參考樣本代碼。

如果頁面請求中沒有一次性消費事件,初始化ViewModel時可以直接傳入EmptySingleState。

BaseViewModel

/**

* ViewModel基類

*

* @param UiState循環事件,視圖層可以多次接收和重新整理。

* @ param SingleUiState一次性事件,視圖層不支援吐司、導航活動等多次消費。

*/

抽象類BaseViewModel : ViewModel() {

/**

*可重複消費的事件

*/

private val _ uiStateFlow = mutable Stateflow(initUiState())

val uiStateFlow:StateFlow = _ uiStateFlow

/**

*一次性事件和一對一訂閱關系

*例如:播放吐司、導航片段等。

*頻道功能

* 1.對于一對一通信,隻有一個使用者可以接收每條消息。

* 2.第一個訂閱者可以在收集之前接收事件。

*/

private val _ sUiStateFlow:Channel = Channel()

val suis tateflow:Flow = _ suis tateflow . receiveasflow()

private val _ loadUiStateFlow:Channel = Channel()

val loadUiStateFlow:Flow = _ loadUiStateFlow . receiveasflow()

受保護的抽象fun initUiState(): UiState

受保護的fun sendUiState(副本:UiState。()-> UiState) {

_ ui Stateflow . update { _ ui Stateflow . value . copy()}

}

受保護的資金發送SingleUiState(sui state:SingleUiState){

viewModelScope.launch {

_sUiStateFlow.send(sUiState)

}

}

/**

*發送目前加載狀态:加載、錯誤、正常

*/

私人融資sendLoadUiState(loadState:LoadUiState){

viewModelScope.launch {

_loadUiStateFlow.send(加載狀态)

}

}

/**

* @param showLoading顯示加載?

* @param request請求資料

* @ param success回調請求成功。

* @param failCallback請求失敗,處理異常邏輯。

*/

受保護的fun requestDataWithFlow(

showLoading: Boolean = true,

請求:suspend () -> BaseData,

successCallback: (T) ->機關,

fail callback:suspend(String)-> Unit = { errMsg-->

//預設異常處理,子類可以重寫。

sendLoadUiState(LoadUiState。錯誤(errMsg))

},

) {

viewModelScope.launch {

//顯示加載或不加載

if (showLoading) {

sendLoadUiState(LoadUiState。正在加載(真))

}

val基本資料:基本資料

嘗試{

baseData = request()

when (baseData.state) {

請求狀态。成功-> {

sendLoadUiState(LoadUiState。ShowMainView)

baseData.data?。讓{ successCallback(it) }

}

請求狀态。錯誤-> baseData.msg?。讓{

錯誤(it)

}

}

} catch (e: Exception) {

e .消息?。讓{ failCallback(it) }

}最後{

if (showLoading) {

sendLoadUiState(LoadUiState。加載(假))

}

}

}

}

}

複制代碼

基類中StateFlow的預設值由initUiState()定義,子類實作是強制性的:

覆寫fun initUiState(): MviState {

傳回MviState(BannerUiState。INIT,DetailUiState。初始化)

}

複制代碼

這樣,當你進入頁面時,你将會監聽這些初始化事件并對它們做出響應。如果你不需要處理它們,你可以跳過它們。整個請求邏輯封裝在datawithflow中,

知識庫資料支援

定義資料庫資料類:

類别基礎資料{

@SerializedName("errorCode ")

var代碼= -1

@SerializedName("errorMsg ")

var msg:字元串?=空

var資料:T?=空

變量狀态:ReqState = ReqState。錯誤

}

枚舉類請求狀态{

成功,錯誤

}

複制代碼

基類存儲庫:

打開類庫基本存儲庫{

暫停資金執行請求(

block: suspend () -> BaseData

):BaseData {

val baseData = block.invoke()

if (baseData.code == 0) {

//正确

baseData.state = ReqState。成功

}否則{

//錯誤

baseData.state = ReqState。錯誤

}

傳回基礎資料

}

}

複制代碼

請求邏輯在基類中定義,并直接在子類中使用:

WanRepository類:BaseRepository() {

val service = retro fitutil . get service(drink service::class . Java)

挂起fun request wandata(drink id:String):base data {

傳回executeRequest { service . get banner()}

}

挂起fun requestRankData(): BaseData {

傳回executeRequest { service . getranklist()}

}

}

複制代碼

視圖層

/**

* MVI的例子

*/

類MviExampleActivity:BaseMviActivity(){

private val mBtnQuest:按id按鈕(R.id.btn_request)

private val mToolBar:按id排序的工具欄(R.id.toolbar)

private val mContentView:view group by id(r . id . cl _ content _ view)

private val mview page 2:mvpage 2 by id(r . id . MVP _ page 2)

private val MRV rank:recycler按id檢視(R.id.rv_view)

私有val mViewModel:由viewModels()建立的mViewModel

覆寫fun getLayoutId(): Int {

return r . layout . activity _ wan _ Android _ mvi

}

覆寫fun initViews() {

initToolBar(mToolBar,“Jetpack MVI”,true,true,BaseActivity。TYPE_BLOG)

mrvrank . layout manager = GridLayoutManager(this,2)

}

覆寫fun initEvents() {

注冊事件()

mBtnQuest.setOnClickListener {

MViewModel.showToast() //一次性消費

mViewModel.loadBannerData()

mViewModel.loadDetailData()

}

}

私人基金注冊事件(){

/**

* Load Loading事件Loading,Error,ShowMainView

*/

mview model . loaduistateflow . flow with life cycle 2(this){ state-->

何時(狀态){

是LoadUiState。錯誤-> mstatusviewutil . showerrorview(state . msg)

是LoadUiState。ShowMainView-> mstatusviewutil . ShowMainView()

是LoadUiState。正在加載-> mstatusviewutil . showloadingview(state . is show)

}

}

/**

*一次性消費事件

*/

mview model . suis tateflow . flowithlifecycle 2(this){ data-->

showToast(data.message)

}

mview model . uistateflow . flow with life cycle 2(this,prop 1 = mvi state::banner uistate){ state-->

何時(狀态){

是BannerUiState。初始化-> {}

是BannerUiState。成功-> {

mview page 2 . visibility = View。看得見的

mBtnQuest.visibility = View。過去的

val imgs = mutableListOf()

對于(狀态中的模型.模型){

imgs.add(model.imagePath)

}

mview page 2 . setindicatorshow(true)。set models(img)。開始()

}

}

}

mview model . uistateflow . flow with Lifecycle 2(this,life cycle。狀态。開始,

prop 1 = mvi state::detailUiState){ state-->

何時(狀态){

是DetailUiState。初始化-> {}

是DetailUiState。成功-> {

mRvRank.visibility = View。看得見的

val list = state.detail.datas

mRvRank.adapter = RankAdapter()。應用{ setModels(list) }

}

}

}

}

覆寫fun retryRequest() {

//點選螢幕再試一次

MViewModel.showToast() //一次性消費

mViewModel.loadBannerData()

mViewModel.loadDetailData()

}

/**

*顯示加載、清空、錯誤視圖等。

*/

覆寫fun getStatusOwnerView(): View?{

傳回mContentView

}

}

複制代碼

讓我們回頭看看新的架構圖。當View->ViewModel請求資料時,它通過事件傳輸,這些事件可以像ViewModel中那樣封裝:

密封的類事件:IEvent {

對象橫幅:事件()

對象詳細資訊:事件()

}

覆寫資金排程事件(事件:事件){

何時(事件){

事件。橫幅--> { loadBannerData()}

事件。詳細資訊--> { loadDetailData()}

}

複制代碼

那麼視圖層可以如下調用:

mViewModel.dispatchEvent(事件。橫幅)

mViewModel.dispatchEvent(事件。詳情)

複制代碼

但是,在示例中,當資料請求在視圖層發送時,請求沒有封裝在ViewModel中,而是通過mViewModel.loadBannerData()直接請求。個人認為,把事件封裝起來有點多餘。

摘要

與舊的MVVM建築相比,更新後的MVI建築更加規範和有限制力。具體來說: