Android最佳架構:MVI + LiveData + ViewModel | ProAndroidDev

像程序那樣思考 發佈 2020-04-12T01:36:51+00:00

最後我自己從事Android 開發,從業這麼久,我也積累了一些珍藏的資料,分享出來,希望可以幫助到大家提升進階還分享一份由幾位大佬一起收錄整理的Android學習PDF+架構視頻+面試文檔+源碼筆記,高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料如果你有需

MVVM和MVI架構模式合併為一個最好的架構,為任何Android項目提供了完美的架構。

有太多可用的體系結構模式,每種模式都有其優缺點。所有這些模式都試圖實現相同的架構基本原理:

1、關注點分離(SoC):這是一種設計原則,用於將電腦程式分為不同的部分,以便每個部分都可以解決一個單獨的關注點。關注點是提供問題解決方案時重要的事情。

該原則與面向對象編程的「 單一責任原則」密切相關,後者 指出「每個模塊,類或功能都應對軟體提供的功能的一部分負責,而責任應由第三方完全封裝。類,模塊或功能。」

2、由模型驅動的UI :應用程式應從模型(最好是持久性模型)驅動UI。模型獨立於View對象和應用程式組件,因此它們不受應用程式生命周期和相關問題的影響。

讓我們來看看一些流行的架構模式的總結:

MVC架構

Trygve Reenskaug的模型-視圖-控制器體系結構是所有現代體系結構模式的基礎。讓我們看看在Wikipedia頁面上定義的每個組件的責任

  • 模型負責管理應用程式的數據。它從控制器接收用戶輸入。
  • 視圖表示以特定格式表示模型。
  • 控制器響應用戶輸入並在數據模型對象上執行交互。所述控制器接收輸入,任選驗證它,然後將輸入到模型。

因此,模型負責表示視圖的狀態,結構和行為,而視圖僅表示該模型。

MVVM架構

在Model-View-ViewModel體系結構中,視圖具有ViewModel的實例,並且它根據用戶輸入/操作調用相應的函數。此外,視圖會觀察ViewModel的不同可觀察屬性以進行更改。ViewModel根據業務邏輯處理用戶輸入,並修改各自的可觀察屬性。

MVI架構

在Model-View-Intent體系結構中,視圖公開視圖事件(用戶輸入/操作),並觀察模型以了解視圖狀態的變化。我們處理視圖事件,並將其轉換為各自的意圖,並將其傳遞給模型。模型層使用intent和previous view-state創建一個新的不可變視圖狀態。因此,這種方式遵循單向數據流原理,即數據僅在一個方向上流動:「視圖」>「意圖」>「模型」>「視圖」。

總而言之,MVVM體系結構的最佳部分是ViewModel,但我認為它不遵循MVC模式中定義的模型的概念,因為在MVVM中,本地資料庫會被視為模型,並且視圖從ViewModel觀察到狀態變化多個可觀察的屬性。視圖並不是直接由模型驅動的。此外,ViewModel這些多個可觀察屬性可能導致狀態重疊問題(兩個不同的狀態意外顯示)。

MVI模式通過添加一個實際的「模型」層來解決此問題,該層Intent可以通過視圖觀察狀態變化。由於此模型是當前視圖狀態的不變的單一真相來源,因此不會發生狀態重疊。

在以下架構中,我嘗試將最佳的MVVM和MVI模式結合起來,以獲得任何Android項目的更好架構,此外,我還通過為View和ViewModel創建基類來抽象出儘可能多的內容。

MVI + LiveData + ViewModel

在繼續之前,讓我們重新強調MVI體系結構的一些基本術語:ViewState:顧名思義,這是模型層的一部分,並且我們的視圖觀察該模型的狀態變化。ViewState應該表示任何給定時間的視圖的當前狀態。因此,此類應具有我們視圖所依賴的所有變量內容。每次有任何用戶輸入/操作時,我們都將公開此類的修改後的副本(以保持未修改的先前狀態)。我們可以使用Kotlin的數據 類創建此模型。

data <b>class</b> MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)

sealed <b>class</b> FetchStatus {
    object Fetching : FetchStatus()
    object Fetched : FetchStatus()
    object NotFetched : FetchStatus()
}

ViewEffect:在Android中,我們有某些更像是一勞永逸的操作,例如Toast,在這種情況下,我們無法使用ViewState來維護狀態。這意味著,如果我們使用ViewState顯示Toast,則它將在配置更改時或每次出現新狀態時再次顯示,除非並且直到我們通過傳遞「 toast show」事件重置其狀態為止。而且,如果您不希望這樣做,則可以使用ViewEffect,因為它基於 SingleLiveEvent 並且不維護狀態。ViewEffect也是我們模型的一部分,我們可以使用Kotlin的密封類來創建它。

sealed <b>class</b> MainViewEffect {
    data <b>class</b> ShowSnackbar(val message: String) : MainViewEffect()
    data <b>class</b> ShowToast(val message: String) : MainViewEffect()
}

ViewEvent:它表示用戶可以在視圖上執行的所有操作/事件。這用於將用戶輸入/操作傳遞給ViewModel。我們可以使用Kotlin的密封類創建此事件集。

sealed <b>class</b> MainViewEvent {
    data <b>class</b> NewsItemClicked(val newsItem: NewsItem) : MainViewEvent()
    object FabClicked : MainViewEvent()
    object OnSwipeRefresh : MainViewEvent()
    object FetchNews : MainViewEvent()
}

我建議您將這三個類保存在一個文件中,因為它將使您對目標視圖正在處理的所有可行操作和可變內容有一個總體了解。

現在,讓我們更深入地研究架構:

上圖可能為您提供了此體系結構的核心思想。此體系結構的核心思想是,我們將實際的不可變模型層包括在MVVM體系結構中,並且我們的視圖依賴於該模型進行狀態更改。這樣,ViewModel負責修改並公開此單個模型。

為了避免冗餘並簡化在多個地方使用此體系結構,我創建了兩個抽象類,一個用於我們的視圖(活動,片段,自定義視圖是獨立的),另一個是用於ViewModel。

AacMviViewModel:創建ViewModel的通用基類。它需要STATE,EFFECT和EVENT三個類。上面我們已經看到了這些類的示例。

open <b>class</b> AacMviViewModel<STATE, EFFECT, EVENT>(application: Application) :
    AndroidViewModel(application), ViewModelContract<EVENT> {

    <b>private</b> val _viewStates: MutableLiveData<STATE> = MutableLiveData()
    fun viewStates(): LiveData<STATE> = _viewStates

    <b>private</b> <b>var</b> _viewState: STATE? = <b>null</b>
    <b>protected</b> <b>var</b> viewState: STATE
        get() = _viewState
            ?: <b>throw</b> UninitializedPropertyAccessException(<font>"\"viewState\" was queried before being initialized"</font><font>)
        set(value) {
            Log.d(TAG, </font><font>"setting viewState : $value"</font><font>)
            _viewState = value
            _viewStates.value = value
        }


    <b>private</b> val _viewEffects: SingleLiveEvent<EFFECT> = SingleLiveEvent()
    fun viewEffects(): SingleLiveEvent<EFFECT> = _viewEffects

    <b>private</b> <b>var</b> _viewEffect: EFFECT? = <b>null</b>
    <b>protected</b> <b>var</b> viewEffect: EFFECT
        get() = _viewEffect
            ?: <b>throw</b> UninitializedPropertyAccessException(</font><font>"\"viewEffect\" was queried before being initialized"</font><font>)
        set(value) {
            Log.d(TAG, </font><font>"setting viewEffect : $value"</font><font>)
            _viewEffect = value
            _viewEffects.value = value
        }

    @CallSuper
    override fun process(viewEvent: EVENT) {
        Log.d(TAG, </font><font>"processing viewEvent: $viewEvent"</font><font>)
    }
}
</font>

它有viewModel,renderViewState()而且renderViewEffect()我們需要實現抽象的特性/功能。此外,它創建viewStateObserver,viewEffectObserver內部LiveData-觀察員並開始觀察viewStates(),並viewEffects()在由所述視圖模型暴露活動activity中onCreate()。因此,此抽象活動activity將執行我們必須在每個活動activity中進行的所有操作。此外,它記錄每個觀察到的viewState和viewEffect。

現在,為這個架構創建一個新的活動activity非常容易:

<b>class</b> MainActivity : AacMviActivity<MainViewState, MainViewEffect, MainViewEvent, MainActVM>() {
    override val viewModel: MainActVM by viewModels()

    override fun renderViewState(viewState: MainViewState) {
      <font><i>//Handle new viewState</i></font><font>
    }

    override fun renderViewEffect(viewEffect: MainViewEffect) {
      </font><font><i>//Show effects</i></font><font>
    }
}
</font>

僅此而已,我們就可以將所有內容準備就緒,無縫工作,記錄我們正在處理的每個動作和內容。狀態不會重疊,因為模型是視圖狀態更改的唯一事實來源。

注意:如果您不熟悉這種「模型驅動的用戶介面」,那麼您可能會認為我們比直接處理要增加更多的複雜性,因為對於某些複雜的視圖,ViesState數據類將具有如此多的屬性,因為每個小部件及其內容必須具有內容可見度等。但是請相信我,它將很有意義,因為追查任何問題/崩潰的原因將非常容易。

進一步的改進:

1、許多人建議使用 ConflatedBroadcastChannel, 因為它僅發布最新值,但仍處於試驗階段,因此我更喜歡使用LiveData。而且無論如何,即使我們將來決定使用ConflatedBroadcastChannel / Flow,也將只需要修改抽象類。

2、如果我們有一個活動和多個片段或自定義視圖,該怎麼辦?我們如何在活動到片段之間,片段到片段之間進行交流?歡迎大家評論區討論。


最後

我自己從事 Android 開發,從業這麼久,我也積累了一些珍藏的資料,分享出來,希望可以幫助到大家提升進階

還分享一份由幾位大佬一起收錄整理的Android學習PDF+架構視頻+面試文檔+源碼筆記高級架構技術進階腦圖、Android開發面試專題資料,高級進階架構資料

如果你有需要的話,可以私信我【進階】我發給你

喜歡本文的話,不妨給我點個小贊、評論區留言或者轉發支持一下唄~

關鍵字: