iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 20
2
Mobile Development

Android TDD 測試驅動開發系列 第 20

Day20 - Android MVVM 架構:ViewModel & LiveData

  • 分享至 

  • xImage
  •  

上一篇,我們透過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。
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"

同樣以上一篇的範例。
https://ithelp.ithome.com.tw/upload/images/20191004/201118961CgJ8gn2Q5.png

在原本的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的作用。

https://ithelp.ithome.com.tw/upload/images/20191004/201118964ApuSY8NNn.png

圖片來源 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 架構設計 | 用 Architecture Components 打造易維護、可測試的App

3堂組合:Android 架構設計 + 動畫入門到進階 + UI 進階實戰


上一篇
Day19 - Android MVVM 架構:DataBinding
下一篇
Day21 - Android MVVM 架構的單元測試
系列文
Android TDD 測試驅動開發30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言