如何观察多个LiveData的状态?

How to observe the states of multiple LiveData?

我正在尝试观察页面的加载状态。但是,我对我的 ViewModel 进行了 2 API 次调用。我想在加载两个项目之前显示进度条。

我有一个密封的class来表示数据的加载状态,它是这样的:

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>()
}

我的视图模型:

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));

这是文档中的示例 - 这是一个非常简单的示例,当源 LiveDatas 的 任一 发布新值时,仅在 liveDataMerger 上设置一个值。

an example on the Android Developers blog 更接近您的要求:

    ...
    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 上获得新值时,都会将它们都传递给一个进行一些验证的函数和 returns 当前状态。

我还应该指出 Flows are recommended for a lot of things now,并且 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 视为聚合了 model1model2data 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
        }
    }

}