iT邦幫忙

2022 iThome 鐵人賽

DAY 24
0
Mobile Development

【Kotlin Notes And JetPack】Build an App系列 第 24

Day 24.【Architecture】LiveData 的介紹與應用

  • 分享至 

  • xImage
  •  

今天來講講前幾次也有出現的 LiveData,以下如有解釋不清或是描述錯誤的地方還請大家多多指教:

什麼?

LiveData 是一個具有生命週期感知的 observable data holder class,只在依附的 LifecycleOwner 是 active 的狀態下才會進行 observer,只有在資料狀態改變時才會通知觀察者,可透過以下兩種方式來更新 LiveData。

  • MainThread 時:setValue(T)
  • WorkerThread 時:postValue(T)

| Transform LiveData

以下分成 map 和 switchMap,這兩者的差別在於一個回傳物件,一個要回傳 LiveData 物件,那這會用在什麼地方呢,當你需要將監聽的 LiveData 值再轉換成另一個樣子動態通知另一個 view 去做變化:

  • Transform.map
val cityLiveData: LiveData<CityCard> = CityLiveData()
val cityName: LiveData<String> = Transformations.map(cityLiveData) {
    city -> "${city.name}"
}
  • Transform.switchMap
val cityLiveData: LiveData<CityCard> = CityLiveData()
val cityName: LiveData<String> = Transformations.switchMap(cityLiveData) { city -> 
		liveData {
        emit(city.cityName)
    }
}

| MediatorLiveData

合併多個 LiveData,只要其中一個有變化就會發通知出去,使用情境在於,假如今天你的介面的資料來源有很多個,這時就可以使用 MediatorLiveData 去監聽多個來源的資料變化:

val cityList = MediatorLiveData<List<CityCard>>()

fun setData() {
	cityList.addSource(firstLiveData) { value ->
        cityList.value = value
    }
    cityList.addSource(secondLiveData) { value ->
        cityList.value = value
    }
}

如何?

library 的部分依樣在 lifecycle 那邊就已經加好了,直接來設置 LiveData,這邊會使用 private 的 mutableLiveData 來進行 setValue,由外部監聽 LiveData,確保只能由 ViewModel 來變動值,外部只能取得值不能改變值,並包覆之前所寫的 Resource:

// 新增城市
private val _card = MutableLiveData<Resource<CityCard>>()
val card: LiveData<Resource<CityCard>> = _card

// 取得 db 城市列表
private val _cardList = MutableLiveData<Resource<List<CityCard>>>()
val cardList: LiveData<Resource<List<CityCard>>> = _cardList

透過 Resource 傳達出去的狀態,可以顯示資料讀取狀態的 layout

class MainViewModel: ViewModel() {
    private val _isCardInserted = MutableLiveData<Resource<Boolean>>()
    val isCardInserted: LiveData<Resource<Boolean>> = _isCardInserted

    private val _cardList = MutableLiveData<Resource<List<CityCard>>>()
    val cardList: LiveData<Resource<List<CityCard>>> = _cardList

    fun getForecast(country: String) {
        _isCardInserted.value = Resource.Loading()
        val service = WeathbyRetrofit.makeRetrofitService()
        viewModelScope.launch {
            runCatching {
                service.getForecast(query = country)
            }.onSuccess {
                it.body()?.apply {
                   setCardDB(CityEntities(...))
                }
            }.onFailure {
                _isCardInserted.value = Resource.Error(it.message ?: "連線逾時請稍後再試")
            }
        }
    }

    private fun setCardDB(city: CityEntities) {
				val db: CityDAO = CityDatabase.getInstance(WeathApplication.instance.applicationContext).cityDao() 
        viewModelScope.launch {
            runCatching {
                db.insertAll(city)
            }.onSuccess {
                _isCardInserted.value = Resource.Success(true)
            }.onFailure {
                _isCardInserted.value = Resource.Error(it.message ?: "連線逾時請稍後再試")
            }
        }
    }

    fun getCardDB(db: CityDAO) {
        _cardList.value = Resource.Loading()
				val db: CityDAO = CityDatabase.getInstance(WeathApplication.instance.applicationContext).cityDao() 
        viewModelScope.launch {
            runCatching {
                db.getAll()
            }.onSuccess {
                _cardList.value = Resource.Success(
                    it.map { 
                        CityCard(
                            ...
                        )
                    }
                )
            }.onFailure {
                _cardList.value = Resource.Error(it.message ?: "連線逾時請稍後再試")
            }
        }
    }
}

View 可以這樣監聽:

private fun setupViewModel() {
    viewModel.isCardInserted.observe(viewLifecycleOwner) {
        when(it) {
            is Resource.Loading -> {
                showLoading()
            }
            is Resource.Success -> {
                hideLoading()
                if (it.data == true) viewModel.getCardDB()
            }
            is Resource.Error -> {
                hideLoading()
            }
        }
    }

    viewModel.cardList.observe(viewLifecycleOwner) {
        when(it) {
            is Resource.Loading -> {
                showLoading()
            }
            is Resource.Success -> {
                hideLoading()
                it.data?.apply {
                    cardAdapter.submitList(this)
                }
            }
            is Resource.Error -> {
                hideLoading()
            }
        }
    }
}

之後 db 會以注入的方式取得

Reference

Android LiveData


上一篇
Day 23.【Architecture】ViewModel 的介紹與應用
下一篇
Day 25.【UI】App Widget 新體驗
系列文
【Kotlin Notes And JetPack】Build an App30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言