【问题标题】:Correct way of handling data and loading indicator in recyclerview using ViewModel, LiveData and RxJava使用 ViewModel、LiveData 和 RxJava 在 recyclerview 中处理数据和加载指示器的正确方法
【发布时间】:2018-10-20 15:00:45
【问题描述】:

从数据源搜索项目时,我有以下 UI 流程:

  1. 从源检索时显示进度指示器 -> 将 livedata 分配给 Outcome.loading(true)
  2. 显示结果 -> 分配 LiveData Outcome.success(results)
  3. 隐藏进度指示器 -> 分配 LiveData Outcome.loading(false)

现在的问题是当应用程序在后台时调用#2 和#3。恢复应用程序时,LiveData 观察者只被通知 #3 而不是 #2 导致未填充的 RecyclerView。

处理这种情况的正确方法是什么?

class SearchViewModel @Inject constructor(
    private val dataSource: MusicInfoRepositoryInterface, 
    private val scheduler: Scheduler, 
    private val disposables: CompositeDisposable) : ViewModel() {

    private val searchOutcome = MutableLiveData<Outcome<List<MusicInfo>>>()
    val searchOutcomLiveData: LiveData<Outcome<List<MusicInfo>>>
        get() = searchOutcome

    fun search(searchText: String) {
        Timber.d(".loadMusicInfos")
        if(searchText.isBlank()) {
            return
        }

        dataSource.search(searchText)
                .observeOn(scheduler.mainThread())
                .startWith(Outcome.loading(true))
                .onErrorReturn { throwable -> Outcome.failure(throwable) }
                .doOnTerminate { searchOutcome.value = Outcome.loading(false) }
                .subscribeWith(object : DisposableSubscriber<Outcome<List<MusicInfo>>>() {
                    override fun onNext(outcome: Outcome<List<MusicInfo>>?) {
                        searchOutcome.value = outcome
                    }

                    override fun onError(e: Throwable) {
                        Timber.d(e, ".onError")
                    }

                    override fun onComplete() {
                        Timber.d(".onComplete")
                    }
                }).addTo(disposables)
    }

    override fun onCleared() {
        Timber.d(".onCleared")
        super.onCleared()
        disposables.clear()
    }
}

下面是我的结果类

sealed class Outcome<T> {
    data class Progress<T>(var loading: Boolean) : Outcome<T>()
    data class Success<T>(var data: T) : Outcome<T>()
    data class Failure<T>(val e: Throwable) : Outcome<T>()

    companion object {
        fun <T> loading(isLoading: Boolean): Outcome<T> = Progress(isLoading)

        fun <T> success(data: T): Outcome<T> = Success(data)

        fun <T> failure(e: Throwable): Outcome<T> = Failure(e)
    }
}

【问题讨论】:

    标签: android kotlin rx-java2 android-livedata android-mvvm


    【解决方案1】:

    您不应将加载状态设置为“双重”状态(真/假)。 您的进度状态应仅在加载时发送,然后您将进入成功或失败状态。最后永远不要回到加载状态。这样做你总是知道你的视图需要显示哪个状态。

    • 如果加载 -> 显示加载器
    • 如果成功 -> 隐藏加载器,显示数据
    • 如果错误 -> 隐藏加载器,显示错误

    这是我的Android Conductor + MVVM + Dagger project template 的示例摘录,它使用导体,但您可以将导体控制器替换为片段或活动,这是相同的逻辑。

    sealed class DataRequestState<T> {
        class Start<T> : DataRequestState<T>()
        class Success<T>(var data: T) : DataRequestState<T>()
        class Error<T>(val error: Throwable) : DataRequestState<T>()
    }
    

    视图模型:

    @ControllerScope
    class HomeControllerViewModel
    @Inject
    constructor(homeRepositoryManager: HomeRepositoryManager) : BaseControllerViewModel(),
        DataFetchViewModel<Home> {
        private val _dataFetchObservable: DataRequestLiveData<Home> =
            DataRequestLiveData(homeRepositoryManager.home())
        override val dataFetchObservable: LiveData<DataRequestState<Home>> = _dataFetchObservable
    
        override fun refreshData() {
            _dataFetchObservable.refresh()
        }
    }
    

    基础数据控制器(片段/活动/导体):

    abstract class BaseDataFetchController<VM, D> :
        BaseViewModelController<VM>() where VM : BaseControllerViewModel, VM : DataFetchViewModel<D> {
        override fun onViewCreated(view: View) {
            super.onViewCreated(view)
    
            viewModel.dataFetchObservable.observe(this, Observer {
                it?.let {
                    when (it) {
                        is DataRequestState.Start -> dataFetchStart()
                        is DataRequestState.Success -> {
                            dataFetchSuccess(it.data)
                            dataFetchTerminate()
                        }
                        is DataRequestState.Error -> {
                            dataFetchError(it.error)
                            dataFetchTerminate()
                        }
                    }
                }
            })
        }
    
        protected abstract fun dataFetchStart()
        protected abstract fun dataFetchSuccess(data: D)
        protected abstract fun dataFetchError(throwable: Throwable)
    }
    

    【讨论】:

    • 感谢您的回答。一个代码 sn-p 真的很有帮助。
    【解决方案2】:

    加载状态和加载数据应该严格分开,你应该维护两个实时数据和两个观察者。

    这样,loading == false,您将收到有关重新订阅的最新数据。

    想一想:加载状态并不是真正的结果。

    【讨论】:

    • 谢谢。更好的类名是 State。我在几个 github 存储库中看到了这种模式,但有些没有很好地处理进度指示器。不确定仅出于进度指示器的目的而使用额外的 livedata 对象是否有效,但这可能是一种解决方法。
    • 优化正确性,而不是最小化 MutableLiveData imo 的数量
    猜你喜欢
    • 2018-02-10
    • 2023-03-17
    • 2019-02-04
    • 1970-01-01
    • 2018-10-14
    • 2016-10-29
    • 2021-03-07
    • 1970-01-01
    • 2017-04-23
    相关资源
    最近更新 更多