iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 15
2

LiveData

LiveData 是一個用於持有數據並可以監聽數據變動的元件,通常搭配 ViewModel 使用。
除此之外,LiveData 還可以感知生命週期,因此觀察者可以指定某一個具有生命週期的元件給 LiveData,例如 Activity, Fragment,並在生命週期是 活躍 的時候進行更新,也就是生命週期裡的 onStartonResume

除了傳統的觀察者模式中的功能之外,LiveData 還有很多優點:

  • UI 與數據保持一致:觀察者模式最基本的功能。
  • 節省資源:當 Activity 不在活躍時期時是不會收到 LiveData 的任何事件的,能讓系統省去不必要的接受事件。
  • 避免內存泄露:因為 LiveData 能夠感知生命週期,所以當 Activity 被銷毀時,LiveData 也會隨之銷毀,避免不必要的引用而造成的內存泄露。
  • 更好的使用者體驗:如果今天有一個功能是:數據更新完成後跳出 Toast 告訴使用者更新完成,但如果這時候應用是處在後台,也就是非活躍狀態,突然跳出一個 Toast,反而會讓使用者覺得奇怪。
    若是使用 LiveData 則會讓應用處在 活躍狀態時 才跳 Toast,能帶來更好的使用者體驗。

使用

創建 LiveData 類

class MyViewModel : ViewModel() {

    val name = MutableLiveData<String>()
}

只要把 ViewModel 裡面的數據全部改成用 MutableLiveData 包起來就可以了,MutableLiveData 裡面接的是泛型 T,所以可以接受任何的型態。

在 View 的 onCreate 裡面觀察這些數據

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activtiy_live_data)

        viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java)

        viewModel.name.observe(this, object : Observer<String> {
            override fun onChanged(name: String) {
                userName.text = name// or do anything you want.
            }
        })

當 viewModel 裡面的變數 name 變動時,就會傳進 onChanged,裡面再去實作數據變更後要做的事情就可以了。

這邊我曾經犯下一個錯誤 特地紀錄一下

我把 LiveData 和 DataBinding 一起使用的時候,因為 DataBinding 的 UI 更新是跟著 ViewModel 內的變數,所以我在 onChanged 裡面去改變 viewModel 裡面的值

  viewModel.name.observe(this, object : Observer<String> {
            override fun onChanged(name: String) {
                viewModel.updateName(name)
            }
        })

這樣會造成什麼問題呢?因為 ViewModel 裡的 name 被改變了,在 View 這邊的 observe 觀察到他的改變,所以又進來 onChanged 這裏,又再把 ViewModel 裡的 name 更新了一次,View 這邊的 observe 又觀察到他的改變....

變成無窮迴圈了!

所以這邊其實可以把 observe 拿掉,讓 DataBinding 去更新,或是把 這個元件在 xml 的 binding 拿掉,然後在 onChanged 做正常的 settext,兩者擇一就可以了。

更改 LiveData 內變數的值

class MyViewModel : ViewModel() {

    val name = MutableLiveData<String>()
    
    fun updataName(name: String){
    
        this.name.value = name// UI Thread
        this.name.postValue(name)// Worker Thread
    }
}

跟 DataBinding 一樣,在 View 那邊呼叫 updateName 把值丟進來,在 UI Thread 的更新可以直接 setValue,在 Worker Thread 的更新就只能用 postValue

到這邊為止就算完成了 LiveData 的基本使用,非常的簡單!

Transformations LiveData

如果今天 UI 接回來的 LiveData 不是自己想要的,而是想要做出一些組合,或是要將 LiveData 傳遞給另一個 LiveData,就要用到 Transformations 這個類別。

舉例來說:
有一個 data class User 包在一個 LiveData 裡面

data class User(
    val firstName: String,
    val lastName: String
)
val user = MutableLiveData<User>()

如果我想要有一個 firstName + lastName 的組合,就可以這樣做:

val userFullName = Transformations.map(user) { user ->
    user.firstName + user.lastName
}

大括號內取到這個 User 類別後,裡面的事就跟 LiveData 無關了,就可以去做一些自己想要的操作,操作完後返回的也是一個 LiveData 型別,所以要記得在 View 對這個 userFullName 做 observe 就可以了。

MediatorLiveData

先來看這張圖
https://ithelp.ithome.com.tw/upload/images/20190901/20119398AeN2cyscmL.png

MediatorLiveData 是 LiveData 的子類,可以通過 MediatorLiveData 合併多個 LiveData 數據。其中任意一個 LiveData 數據發生變化,MediatorLiveData 都會通知觀察他的對象。

如果今天有一個需求是有很多項資料變動時都要做同一件事,比如說購物車內的任一商品有更動時、或是使用折價券時都要去更新總價,但又不想要在每個觀察事件內寫同樣的程式碼,這時候就可以使用 MediatorLiveData 來合併多個 LiveData

使用上也很簡單,宣告時的型態是 MediatorLiveData,接著在適當的時機(通常是初始化時)把想要合併的 LiveData 利用 addSource 的方式加進去,並定義好當這個 LiveData 改變時,要做什麼事。

    val name = MutableLiveData<String>()
    val id = MutableLiveData<Int>()

    val mediatorLiveData = MediatorLiveData<String>()
    
fun addSourceToMediatorLiveData() {

        mediatorLiveData.addSource(name) {

            //do something when name changed
        }
        mediatorLiveData.addSource(id) {

            //do something when id changed
        }
    }

addSource 想要合併幾個都可以,記得在 View 觀察這個 mediatorLiveData 對象即可。

LiveData 小結

LiveData 篇幅不多,使用簡單,主要的功能其實都在跟 ViewModel 搭配的時候包在一起了,當然最大的賣點還是能夠感知生命週期,能夠讓 data 跟著 View 的生命週期在運作,就好像真的活生生的有生命一樣,重新定義了以前對數據類都是靜態的等著被呼叫、賦值的這種想法,使用了 LiveData 後,數據類變的動態,也更貼近了使用者,我想這也是 LiveData 的命名由來吧!

有任何問題或講得不清楚的地方歡迎留言和我討論。

更歡迎留言糾正我任何說錯的地方!

下一篇:Room (一) 介紹與基本使用


上一篇
Day 14 Data Binding (Last) 雙向綁定 InverseBindingAdapter
下一篇
Day 16 Room (一) 介紹與基本使用
系列文
Android Architecture Components 學習心得筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言