如何观察多个LiveData的状态?



我正在尝试观察页面的加载状态。然而,我在我的ViewModel上做了2个API调用。我想显示一个进度条,直到两个项目都加载完成。

我有一个密封类来指示数据的加载状态,如下所示:

sealed class DataState<out R> {
data class Success<out T>(val data: T) : DataState<T>()
data class Error(val exception: Exception) : DataState<Nothing>()
object Loading : DataState<Nothing>()
}

My view model:

init {
getData1()
getData2()
}
val data1 = MutableLiveData<List<model1>>()
val data2 = MutableLiveData<List<model2>>()
private fun getData1() {
viewModelScope.launch {
data1.postValue(DataState.Loading)
val result = try {
repository.getData1().data!!
}
catch (e: Exception) {
data1.postValue(DataState.Error(e))
return@launch
}

data1.postValue(DataState.Success(result))
}
}
private fun getData2() {
viewModelScope.launch {
data2.postValue(DataState.Loading)
val result = try {
repository.getData2().data!!
}
catch (e: Exception) {
data2.postValue(DataState.Error(e))
return@launch
}
data2.postValue(DataState.Success(result))
}
}

我想观察一个实时数据,这样我就可以看到两个状态都是成功的。这可能吗?

您可能需要一个MediatorLiveData:

LiveData liveData1 = ...;
LiveData liveData2 = ...;
MediatorLiveData liveDataMerger = new MediatorLiveData<>();
liveDataMerger.addSource(liveData1, value -> liveDataMerger.setValue(value));
liveDataMerger.addSource(liveData2, value -> liveDataMerger.setValue(value));

这是来自文档的例子-这是一个非常简单的一个,只是在liveDataMerger上设置一个值,当源livedata的发布一个新值。

Android开发者博客上有一个更接近你想要的例子:

...
result.addSource(liveData1) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
result.addSource(liveData2) { value ->
result.value = combineLatestData(liveData1, liveData2)
}
...
private fun combineLatestData(
onlineTimeResult: LiveData<Long>,
checkinsResult: LiveData<CheckinsResult>
): UserDataResult {
val onlineTime = onlineTimeResult.value
val checkins = checkinsResult.value
// Don't send a success until we have both results
if (onlineTime == null || checkins == null) {
return UserDataLoading()
}
// TODO: Check for errors and return UserDataError if any.
return UserDataSuccess(timeOnline = onlineTime, checkins = checkins)
}

因此,每次在其中一个livedata上获得新值时,您都将它们传递给一个函数,该函数执行一些验证并返回当前状态。

我还应该指出,Flows现在被推荐用于许多事情,combine函数(与MediatorLiveData做同样的事情)更容易阅读(在那篇文章中#5)。只是想让你知道!这里都可以

您不需要为每个API调用单独设置LiveDataDataState

而不是:

val data1 = MutableLiveData<List<model1>>()
val data2 = MutableLiveData<List<model2>>()

你可以这样:

private val _stateLiveData = MutableLiveData<DataStates<model3>>()
val statesLiveData: LiveData<DataStates<model3>>
get() = _stateLiveData

考虑model3作为model1model2的聚合data class。它可以用来聚合其他将来可以使用的数据字段。

data class model3(
var data1: model1?,
var data2: model2?
)

这样,当您观察状态时,维护状态变得更简单:

viewModel.statesLiveData.observe(this) { states ->
when(states) {
is DataStates.Error -> // Handle Error here.
DataStates.Loading -> // Handle Loading here.
is DataStates.Success -> // Handle Success here.
}
}

还有一件事,因为getData1()getData2()方法分别启动协程,所以REST API调用不是按彼此的顺序进行的。而且不能保证哪个调用会首先完成,这可能会导致额外的代码来维护进程,直到两个调用都完成。这可以通过对REST API调用本身进行排序,通过将数据获取操作合并到一个CoroutineScope下的一个方法中来轻松解决。

总结:

class YourViewModel : ViewModel() {
private val _stateLiveData = MutableLiveData<DataStates<model3>>()
val statesLiveData: LiveData<DataStates<model3>>
get() = _stateLiveData
init {
getData()
}
private fun getData() {
viewModelScope.launch(Dispatchers.IO) {
notifyState(DataStates.Loading)
try {
val result1 = repository.getData1().data!!
val result2 = repository.getData2().data!!
notifyState(
DataStates.Success(
model3(
result1,
result2
)
)
)
} catch (e: Exception) {
notifyState(DataStates.Error(e))
}
}
}
private fun notifyState(state: DataStates<model3>) {
viewModelScope.launch(Dispatchers.Main) {
_stateLiveData.value = state
}
}
}

最新更新