使用 ViewModel、LiveData 和 RxJava 在 recyclerview 中处理数据和加载指示器的正确方法
Correct way of handling data and loading indicator in recyclerview using ViewModel, LiveData and RxJava
从数据源搜索项目时,我有以下 UI 流程:
- 从源检索时显示进度指示器 -> 将实时数据分配给
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()
}
}
下面是我的结果class
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)
}
}
您不应将加载状态设置为 "double" 状态 (true/false)。
您的进度状态应该仅在加载时分派,然后您将进入成功或失败状态。最后永远不要回到加载状态。这样做你总是知道你的视图需要显示哪个状态。
- 如果正在加载 -> 显示加载器
- 如果成功 -> 隐藏加载程序,显示数据
- 如果错误 -> 隐藏加载程序,显示错误
这是从我的 Android Conductor + MVVM + Dagger project template 中摘录的示例,它使用了导体,但您可以用片段或 activity 替换导体控制器,这是相同的逻辑。
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()
}
}
基础数据控制器(fragment/activity/conductor):
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)
}
加载状态和加载数据应该严格分开,你应该维护两个实时数据和两个观察者。
这样,loading == false
您将收到有关重新订阅的最新数据。
想一想:加载状态并不是真正的结果。
从数据源搜索项目时,我有以下 UI 流程:
- 从源检索时显示进度指示器 -> 将实时数据分配给
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()
}
}
下面是我的结果class
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)
}
}
您不应将加载状态设置为 "double" 状态 (true/false)。 您的进度状态应该仅在加载时分派,然后您将进入成功或失败状态。最后永远不要回到加载状态。这样做你总是知道你的视图需要显示哪个状态。
- 如果正在加载 -> 显示加载器
- 如果成功 -> 隐藏加载程序,显示数据
- 如果错误 -> 隐藏加载程序,显示错误
这是从我的 Android Conductor + MVVM + Dagger project template 中摘录的示例,它使用了导体,但您可以用片段或 activity 替换导体控制器,这是相同的逻辑。
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()
}
}
基础数据控制器(fragment/activity/conductor):
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)
}
加载状态和加载数据应该严格分开,你应该维护两个实时数据和两个观察者。
这样,loading == false
您将收到有关重新订阅的最新数据。
想一想:加载状态并不是真正的结果。