iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 23
0
Mobile Development

Android 十全大補系列 第 23

[Android 十全大補] MVVM

我們介紹了 SOLID、clean architecture、dependency injection 之後,相信大家寫 code 的時候,都會多想二分鐘怎麼讓程式碼寫得更有架構吧。

差不多是時候回頭來探討 Android 本身的架構問題了,Android 有很多種不同的架構可適用,不論是 MVP、MVI、MVVM,切多少層或是怎麼切、每層之間怎麼互動都有些許不同,但不論哪種架構本質上都是把邏輯從 UI 層獨立出來,讓每一層可以專注於某一種權責,Android 官方並沒有明確訂定什麼才是標準的做法,但我們可以發現在各個場合,MVVM 這個名字一直不斷的被提出來,我們今天就一起來看看 MVVM 到底有什麼過人之處吧!

MVVM

大家可以想像一下我們的目標是想要把邏輯從 UI 層抽離。
假設抽離出來的這一層叫做 X ,UI 會直接跟 X 互動,可能是跟 X 拿資料或其他需求,有些情境 X 也會需要跟 UI 互動,比如說 api 資料回傳了要通知 UI 更新。
這樣相互依賴的關係恰恰違反了 clean architecture 裡,外層只會依賴內層這種單一方向的原則。

把 X 跟 UI 互動的介面定義一層 interface 出來,就變成 MVP 架構囉!

而在 MVVM 的架構裡,外層只會依賴內層這種守則是必須被嚴格遵守的。

MVVM stands for Model、View、ViewModel,相信 Model 跟 View 大家都不陌生,而 ViewModel 則是 Android 提供的一套 library ,主要用途是讓我們暫存跟管理讓 UI 層使用的資料,ViewModel 不會知道 UI 層的存在,通常是透過 LiveData 或其他類似機制給 UI 層,讓 UI 層註冊得到資料更新的事件。

ViewModel 的另一個優點是因為 Activity 層常會有螢幕旋轉等事件會重建 Activity,資料必須不斷的儲存再拿出來其實有點麻煩,而透過 ViewModel 可以讓我們安全方便的保存較龐大的資料。

LivData

LiveData 是個 lifecycle aware 的 observable 物件。
UI 層透過 LiveData 註冊資料更新的事件通知,當資料有變化的時候就會主動通知註冊的 UI 物件,這種反轉主被動的手法是不是似曾相似呢。

另一個重點是 lifecycle aware,當綁定的 Activity 已經被 destroy 的時候,LiveData 也會自動釋放註冊的物件引用,不會導致潛在的 memory leak 風險。

MVVM Architecture

資料來源:https://developer.android.com/jetpack/docs/guide

MVVM 的架構可以用這張圖來說明,Activity/Fragment 所在的 UI 層只會知道 ViewModel 的存在,而 ViewModel 則依據不同需求依賴不同的 Repository 獲取資料或是進行其他互動,Repository 則可以依據自己的商業邏輯決定從 Retrofit 跟後端要資料或是直接透過 Room 拿本地端的資料回傳給 ViewModel,而 UI 層會直接跟 ViewModel 註冊資料更新的通知。

簡而言之,UI 就是控制畫面,而 ViewModel 就是控制資料,藉此達到 UI 與邏輯的切分。
讓我們一起來看個例子吧:

Install

跟以往一樣我們需要宣告 dependency 在 build.gradle 裡:

dependencies {
    implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
}

Usage

以下是一個簡單的 ViewModel 範例:

class NameViewModel : ViewModel() {

    // Create a LiveData with a String
    val currentName: MutableLiveData<String> by lazy {
        MutableLiveData<String>()
    }

    // Rest of the ViewModel...
}

currentName 是一個 MutableLiveData,可以透過 setValue 設值,當值發生變化時,會主動通知已註冊的物件。

而在 Activity 這一層關於 ViewModel 的使用方法如下:

class NameActivity : AppCompatActivity() {

    private lateinit var model: NameViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // Other code to setup the activity...

        // Get the ViewModel.
        model = ViewModelProviders.of(this).get(NameViewModel::class.java)


        // Create the observer which updates the UI.
        val nameObserver = Observer<String> { newName ->
            // Update the UI, in this case, a TextView.
            nameTextView.text = newName
        }

        // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer.
        model.currentName.observe(this, nameObserver)
    }
}

我們透過 ViewModelProviders.of(this).get(NameViewModel::class.java) 拿到 NameViewModel 的實體,Android 會保證當螢幕旋轉等時候,我們所拿到的 ViewModel 物件會是正確的,透過 observe function 來等待資料的更新,當資料有更新的時候我們就改變我們的畫面。

這樣就達成了 Activity 知道 ViewModel 的存在、 ViewModel 不知道 Activity 的存在,但是 ViewModel 資料的更新,可以安全的透過 LiveData 通知給 Activity。

挑戰:要如何透過 dependency injection 提供 ViewModel 呢?

挑戰:試著把 Room 的介面改成 LiveData,就可以更無縫的接軌 ViewModel 囉!

參考資料:
https://developer.android.com/topic/libraries/architecture/livedata
https://developer.android.com/topic/libraries/architecture/viewmodel

Android 十全大補已經正式出書上架囉!
有興趣的讀者歡迎參考:
https://www.tenlong.com.tw/products/9789864345786


上一篇
[Android 十全大補] Koin
下一篇
[Android 十全大補] Modularization
系列文
Android 十全大補30

尚未有邦友留言

立即登入留言