使用 ViewModel、LiveData 和 RxJava 在 recyclerview 中处理数据和加载指示器的正确方法

Posted

技术标签:

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

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

    从源检索时显示进度指示器 -> 将 livedata 分配给 Outcome.loading(true) 显示结果 -> 分配 LiveData Outcome.success(results) 隐藏进度指示器 -> 分配 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)
    

【问题讨论】:

【参考方案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 的数量

以上是关于使用 ViewModel、LiveData 和 RxJava 在 recyclerview 中处理数据和加载指示器的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

JetpackLiveData 架构组件 ( LiveData 简介 | LiveData 使用方法 | ViewModel + LiveData 示例 )

使用 ViewModel 和 LiveData 多次改造执行 API

MVVM 架构,ViewModel和LiveData

MVVM 架构,ViewModel和LiveData

Android Jetpack 学习之旅--> ViewModel & LiveData 的使用

MVVM 架构,ViewModel和LiveData