Android TDD 系列 — 20 Android MVVM 架構:ViewModel & LiveData

Evan Chen
Evan Android Note
Published in
11 min readOct 7, 2019

上一篇,我們透過DataBinding的方式讓View與資料來源自動繫結。這篇要來介紹在Android Jetpack裡的ViewModelLiveData

ViewModel是屬於Android Jetpack裡的lifecycle類,可以有效的解決記憶體洩漏,及難以處理的Activity生命週期問題。以往我們會把取得的資料存在Activity裡,用來應付各種情況所需。但當你的螢幕旋轉畫面上,會發現上面的資料不見了。這是因為旋轉時Activity會先被銷毀(destoryed)再重新產生(onCreated)。所以之前放在Activity裡的資料就會因為這樣而不見了。

另外,即使你的App不讓使用者旋轉,Activities、Fragments 和 views 還是可能在任何時候被destroyed。例如當你開啟App後,離開到別的App,隔了一天再回來app時。你的Activity可能已經被回收了,這時候你開App即會重新產生一個Activity,這時資料就會不見。或是你的app可能會閃退。這種錯誤通常不容易測試。因為你不會把App放一天再開起來測試。但對使用者來說,這可能是經常會發生的錯誤。

所以我們需要另一個比Activity生命週期更長的地方來存放資料,ViewModel正可以為我們解決這個問題。

LiveData是一個可觀察的資料持有類別,比起一般的observable類別,LiveData具有生命週期感知的功能,也就是LiveData確保Activity、Fragment只在活耀的狀態才會收到資料的變化。

MVVM 線上課程

我開設了一門教MVP、MVVM 架構的線上課程,搭配Android Architecture Components。

👇點下方連結有medium優惠價👇
Android 架構設計 | 用 Architecture Components 打造易維護、可測試的App

👇另有3堂課一起的組合包更划算👇
Android 架構設計 + 動畫入門到進階 + UI 進階實戰

👇Android自動化測試組合課程👇
單元測試、TDD、CICD持續整合與佈署

首先在build.gradle加上

def archLifecycleVersion = '1.1.1'
implementation "android.arch.lifecycle:extensions:$archLifecycleVersion"
implementation "android.arch.lifecycle:reactivestreams:$archLifecycleVersion"
annotationProcessor "android.arch.lifecycle:compiler:$archLifecycleVersion"

同樣以上一篇的範例。

在原本的Product 繼承至 ViewModel()

class ProductViewModel(private val productRepository: IProductRepository) : ViewModel(){
}

把ObservableField改為LiveData

class ProductViewModel(private val productRepository: IProductRepository) : ViewModel(){
var productName: MutableLiveData<String> = MutableLiveData()
var productDesc: MutableLiveData<String> = MutableLiveData()
var productPrice: MutableLiveData<Int> = MutableLiveData()
var productItems: MutableLiveData<String> = MutableLiveData()
}

更新LiveData物件

getProduct裡從Repository取得資料後,更新LiveData物件。

fun getProduct(productId: String) {
productRepository.getProduct(productId, object : IProductRepository.LoadProductCallback {
override fun onProductResult(productResponse: ProductResponse) {
productName.value = productResponse.name
productDesc.value = productResponse.desc
productPrice.value = productResponse.price
}
})
}

回到Activity,我們要開始把DataBinding與ViewModel放在一起使用。

由於ProductViewModel需要在建構子傳入IProductRepository,原本的寫法很難傳入參數到ViewModel裡,這裡需要建立一個ProductViewModelFactory,傳入IProductRepository回傳ProductViewModel

class ProductViewModelFactory(private val productRepository: IProductRepository) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(ProductViewModel::class.java)) {
return ProductViewModel(productRepository) as T
}
throw IllegalArgumentException("Unknown ViewModel class")
}
}

修改原本的Activity,改由ProductViewModelFactory產生viewModel。這樣就可以在這裡把ProductRepository傳入了。

class ProductActivity : AppCompatActivity() {    private val productId = "pixel3"    private lateinit var productViewModel: ProductViewModel    override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_product)
val dataBinding = DataBindingUtil.setContentView<ActivityProductBinding>(this, R.layout.activity_product)// 改用LiveData後,註解這段
// val productAPI = ProductAPI()
// val productRepository = ProductRepository(productAPI)
// val productViewModel = ProductViewModel(productRepository)
val productAPI = ProductAPI()
val productRepository = ProductRepository(productAPI)
productViewModel =
ViewModelProviders.of(this, ProductViewModelFactory(productRepository)).get(ProductViewModel::class.java)
dataBinding.productViewModel = productViewModel dataBinding.lifecycleOwner = this productViewModel.getProduct(productId)
}

}

這樣就完成了這個範例把LiveData加進來了。

來看這張ViewModel的生命週期,左邊是Activity的生命週期,在Activity旋轉時會被銷毀再重新產生,也因為這樣,當我們放在Activity的資料會消失。所以我們需要一個比Activity的生命週期更久的ViewModel來儲存資料。這也就是ViewModel的作用。

圖片來源 Android ViewModel

使用ViewModel,資料就可以從UI中分離出來,讓每個元件的職責更清礎,在Activity或Fragment重新產生時,ViewModel仍會保留資料給Activity與Fragment使用。這也是為什麼LiveData要放在ViewModel,而不是放在Activity。Activity 只負責顯示資料,而不負責保持著資料。

class ProductViewModel(private val productRepository: IProductRepository) : ViewModel(){
var productName: MutableLiveData<String> = MutableLiveData()
var productDesc: MutableLiveData<String> = MutableLiveData()
var productPrice: MutableLiveData<Int> = MutableLiveData()
var productItems: MutableLiveData<String> = MutableLiveData()
}

小結LiveData 的優點:

UI和資料保持一致
LiveData是使用觀察者模式,當LIfeCycle的狀態改變,LiveData會通知觀察者,以便更新UI。

避免Memory Leak 及 Activity處於stop狀態而造成閃退
LiveData被綁定到LifeCycle的生命周期上,當Activity被銷毀時,觀察者會自動被清除。
如果Activity不是在活躍的狀態,例如Activity在背景時,是不會收到LiveData的通知的。那麼什麼是活躍動態,就是指Started與Resumed,只有在這兩個狀態下LiveData才會通知資料有變化。

不需要手動處理生命週期的問題
LiveData 可以感知生命週期,只要有活耀的狀態才會收到資料的變化。

解決Configuration Change的問題
在螢幕發生旋轉或被回收使得Activity再次啟動時,立刻就能收到最新的數據。

範例下載:
https://github.com/evanchen76/mvvmlivedatasample

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

介紹完MVVM、ViewMode、LiveData,下一篇我們就要來講怎麼在MVVM寫單元測試了。

實體書,內容更豐富完整!
Android TDD 測試驅動開發:從UnitTest、TDD到DevOps實踐

Android 線上課程 —動畫入門到進階

用動畫提升你的App體驗!!!

--

--