上一篇,我們透過DataBinding的方式讓View與資料來源自動繫結。這篇要來介紹在Android Jetpack裡的ViewModel與LiveData。
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。
Android 架構設計 | 用 Architecture Components 打造易維護、可測試的App
首先在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()
}
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()
}
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 架構設計 | 用 Architecture Components 打造易維護、可測試的App
3堂組合:Android 架構設計 + 動畫入門到進階 + UI 進階實戰