【问题标题】:How to observe the states of multiple LiveData?如何观察多个 LiveData 的状态?
【发布时间】:2021-12-11 02:02:16
【问题描述】:

我正在尝试观察页面的加载状态。但是,我在 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>()
}

我的视图模型:

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 {

        data1.postValue(DataState.Loading)

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

        data2.postValue(DataState.Success(result))
    }
}

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

【问题讨论】:

    标签: android android-studio kotlin mvvm


    【解决方案1】:

    您不需要为每个 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
            }
        }
    
    }
    

    【讨论】:

      【解决方案2】:

      你可能想要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 上获得新值时,您都将它们都传递给执行一些验证并返回当前状态的函数。

      我还应该指出 Flows are recommended for a lot of things nowcombine 函数(与 MediatorLiveData 做同样的事情)更容易阅读(那篇文章中的#5)。只是让你知道!哪一个都好

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-02-21
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多