Правильный способ обработки данных и индикатор загрузки в представлении реселлера с использованием ViewModel, LiveData и RxJava - PullRequest
0 голосов
/ 10 мая 2018

У меня следующий поток пользовательского интерфейса при поиске элементов из источника данных:

  1. Отображение индикатора прогресса при извлечении из источника -> назначить жизненные данные для 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)
    }
}

Ответы [ 2 ]

0 голосов
/ 10 мая 2018

Состояние загрузки и загруженные данные должны быть строго отделены друг от друга, и вы должны поддерживать два живых данных и двух наблюдателей.

Таким образом, loading == false, и вы получите последние данные о повторной подписке.

Подумайте об этом: состояние загрузки на самом деле не результат.

0 голосов
/ 10 мая 2018

Вы не должны делать состояние загрузки «двойным» (true / false).Ваше состояние прогресса должно отправляться только при загрузке, тогда вы переходите в состояние успеха или сбоя.Никогда не возвращайтесь в состояние загрузки в конце.При этом вы всегда знаете, в каком состоянии должен отображаться ваш вид.

  • при загрузке -> показать загрузчик
  • в случае успеха -> скрыть загрузчик, показать данные
  • еслиошибка -> скрыть загрузчик, показать ошибку

Вот пример извлечения из моего шаблона Android Conductor + MVVM + Dagger , он использует проводник, но вы можете заменить контроллер проводника фрагментомили деятельность, это та же логика.

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

ViewModel:

@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)
}
...