今天來講講前幾次也有出現的 LiveData,以下如有解釋不清或是描述錯誤的地方還請大家多多指教:
LiveData 是一個具有生命週期感知的 observable data holder class,只在依附的 LifecycleOwner
是 active 的狀態下才會進行 observer,只有在資料狀態改變時才會通知觀察者,可透過以下兩種方式來更新 LiveData。
setValue(T)
postValue(T)
以下分成 map 和 switchMap,這兩者的差別在於一個回傳物件,一個要回傳 LiveData 物件,那這會用在什麼地方呢,當你需要將監聽的 LiveData 值再轉換成另一個樣子動態通知另一個 view 去做變化:
val cityLiveData: LiveData<CityCard> = CityLiveData()
val cityName: LiveData<String> = Transformations.map(cityLiveData) {
city -> "${city.name}"
}
val cityLiveData: LiveData<CityCard> = CityLiveData()
val cityName: LiveData<String> = Transformations.switchMap(cityLiveData) { city ->
liveData {
emit(city.cityName)
}
}
合併多個 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 會以注入的方式取得