Как я могу синхронно подписаться на наблюдаемое, чтобы не пропустить выбросы из этого наблюдаемого? - PullRequest
0 голосов
/ 02 ноября 2018

У меня есть приложение для Android с архитектурой MVVM.

Слой вида (фрагмент) подписывается на наблюдаемую, которая отображается с помощью ViewModel в onStart(). Сразу после того, как я вызову subscribe() для этой наблюдаемой, я делаю прямой вызов на ViewModel, которая запускает вещи. С этим прямым вызовом происходят две вещи. Во-первых, наблюдаемое, на которое была подписана, генерирует событие, представляющее, что приложение находится в состоянии загрузки. Затем ViewModel извлекает некоторые данные и затем отправляет эти данные.

Проблема в том, что я не получаю первую эмиссию. Однако, если я перенесу свой вызов, чтобы подписаться дальше по цепочке жизненного цикла, например, в onCreate() (и оставлю свой вызов в onStart()), я получу эмиссию. Ясно, что вызов subscribe() является асинхронным, как я могу убедиться, что могу подписаться на наблюдаемое, прежде чем начать его излучение?

Вот случай, когда первое излучение не получено.

//The fragment
 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(OverviewFragmentViewModel::class.java)
    }

override fun onStart() {
    super.onStart()
    allSubscriptions.add(viewModel.uiStateChanged
        .subscribeOn(AndroidSchedulers.mainThread())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe({ uiState ->
            when (uiState) {
                is UiState.Loading -> showLoadingView()
                is UiState.ListReady -> showList(uiState)
                is UiState.Error -> showErrorView()
            }
        }, { error ->
            Log.e(TAG, error.message, error)
        })
    )
    viewModel.loadMovies()
}
}


//The ViewModel

class OverviewFragmentViewModel : ViewModel(){

    val uiStateChanged = PublishSubject.create<UiState>()
    val model = OverviewFragmentRepo()

    companion object {
        val TAG = OverviewFragmentViewModel::class.java.simpleName
    }

    override fun onCleared() {
        super.onCleared()
    }

    fun loadMovies(){
        //This is the emission that happens to fast for the fragment to receive it!
        uiStateChanged.onNext(UiState.Loading())
        model.getMovies()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({response ->
                uiStateChanged.onNext(UiState.ListReady(response.results))
            }, { error ->
                uiStateChanged.onNext(UiState.Error())
                Log.e(TAG, error.message, error)
            })
    }
}

Теперь, если я просто переместу подписку вверх, эмиссия будет получена. Однако я не хочу надеяться, что все завершится вовремя, я хочу быть в этом уверен, и поэтому я хочу быть в состоянии гарантировать, что я уже подписан, прежде чем делать прямой вызов loadMovies(). Вот то же самое: подписка переместилась вверх, а эмиссия получена.

 //The fragment
     override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            viewModel = ViewModelProviders.of(this).get(OverviewFragmentViewModel::class.java)

allSubscriptions.add(viewModel.uiStateChanged
            .subscribeOn(AndroidSchedulers.mainThread())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe({ uiState ->
                when (uiState) {
                    is UiState.Loading -> showLoadingView()
                    is UiState.ListReady -> showList(uiState)
                    is UiState.Error -> showErrorView()
                }
            }, { error ->
                Log.e(TAG, error.message, error)
            })
        )}
        }

    override fun onStart() {
        super.onStart()
        viewModel.loadMovies()
    }


    //The ViewModel

    class OverviewFragmentViewModel : ViewModel(){

        val uiStateChanged = PublishSubject.create<UiState>()
        val model = OverviewFragmentRepo()

        companion object {
            val TAG = OverviewFragmentViewModel::class.java.simpleName
        }

        override fun onCleared() {
            super.onCleared()
        }

        fun loadMovies(){
            //This is the emission that happens to fast for the fragment to receive it!
            uiStateChanged.onNext(UiState.Loading())
            model.getMovies()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe({response ->
                    uiStateChanged.onNext(UiState.ListReady(response.results))
                }, { error ->
                    uiStateChanged.onNext(UiState.Error())
                    Log.e(TAG, error.message, error)
                })
        }
    }

1 Ответ

0 голосов
/ 09 января 2019

С моей точки зрения, вместо создания PublishSubject и вызова loadMovies(), вы можете создавать наблюдаемые во ViewModel, например:

val uiStateChanged = model.getMovies()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())    
    .compose(ResponseOrError.toResponseOrErrorObservable())
    .map { if (it.isData) UiState.ListReady(it.data().results) else UiState.Error() }
    .startWith(UiState.Loading())

тогда вы подписываетесь на это наблюдаемое во фрагменте и можете удалить viewModel.loadMovies()

Подробнее о ResponseOrError

...