Почему сопрограмма ViewModelScoped непригодна для использования после вызова метода ViewModel onCleared () - PullRequest
0 голосов
/ 11 октября 2019

Я делю ViewModel ActivityScoped между несколькими Фрагментами в моем текущем приложении Android.

ViewModel использует Coroutine Scope viewModelScope.launch{}

Моя проблема заключается в том, что .launch{} работает только до владенияViewModel onCleared() метод вызывается.

Так ли должны работать сопрограммы ViewModel?

Есть ли подход, который я могу использовать, чтобы "Сбросить" viewModelScope, чтобы .launch{} работает после вызова метода onCleared ()?

Вот мой код ::

Фрагмент

RxSearchView.queryTextChangeEvents(search)
        .doOnSubscribe {
            compositeDisposable.add(it)
        }
        .throttleLast(300, TimeUnit.MILLISECONDS)
        .debounce(300, TimeUnit.MILLISECONDS)
        .map { event -> event.queryText().toString() }
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe { charactersResponse ->
            launch {
                viewModel.search(charactersResponse.trim())
            }
        }

. ,.

override fun onDetach() {
    super.onDetach()
    viewModel.cancelSearch()
    compositeDisposable.clear()
}

ViewModel

suspend fun search(searchString: String) {
    cancelSearch()

    if (TextUtils.isEmpty(searchString)) {
        return
    }

    job = viewModelScope.launch {
        repository.search(searchString)
    }
}

fun cancelSearch() {
    job?.cancelChildren()
}

. ,.

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

Что я делаю не так?

ОБНОВЛЕНИЕ

Если я изменю свой код запуска на этот

job = GlobalScope.launch {
    repository.search(searchString)
}

Это решает мою проблему, но разве это единственный способ достичь желаемого результата?

У меня сложилось впечатление GlobalScope было "Плохо"

Ответы [ 2 ]

2 голосов
/ 14 октября 2019

после вызова onCleared () моя функция viewModelScoped Launch прекращает выполнение

Это функция, а не ошибка.

После очистки ViewModel,Вы не должны делать что-либо в этом ViewModel или чем бы то ни было LifecycleOwner. Все это теперь несущественно и больше не должно использоваться.

однако это единственный способ достичь желаемого результата?

Правильное решение - избавитьсякода из ViewModel. Если вы ожидаете, что некоторые фоновые работы пройдут время жизни действия или фрагмента, то этот код не принадлежит ни активности / фрагменту, ни связанным с ним моделям представления. Он принадлежит тому, что соответствует времени жизни той работе, которую вы пытаетесь сделать.

1 голос
/ 14 октября 2019
repository.onCleared()

Этот метод не должен принадлежать хранилищу.

На самом деле хранилище не должно быть с состоянием.

Если вы проверяете образцы Google, Репозиторий создает LiveData, который содержит Resource, и причина, по которой это важно, заключается в том, что фактическая механика загрузки и кэширования данных находится внутри этого ресурса, вызванного LiveData.onActive (в данном примере MediatorLiveData.addSource, но технически это семантически одно и то же).

    .subscribe { charactersResponse ->
        launch {
            viewModel.search(charactersResponse.trim())

Фрагмент не должен запускать сопрограммы. Он должен сказать что-то вроде

.subscribe {
    viewModel.updateSearchText(charactersResponse.trim())
}

, а также

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    viewModel = ViewModelProviders.of(this).get(MyViewModel::class.java, factory)
    viewModel.searchResults.observe(viewLifecycleOwner, Observer { results ->
        searchAdapter.submitList(results)
    })
}

Тогда ViewModel будет

class MyViewModel(
    private val repository: MyRepository
): ViewModel() {
    private val searchText = MutableLiveData<String>()

    fun updateSearchText(searchText: String) {
        this.searchText.value = searchText
    }

    val searchResults: LiveData<List<MyData>> = Transformations.switchMap(searchText) {
        repository.search(searchText)
    }
}

И это все, что должно быть в ViewModel, так что тогдавопрос "кому принадлежит область сопрограмм"? Это зависит от того, когда задача должна быть отменена.

Если «больше не наблюдается» отменяет задачу, то для отмены задачи должно быть LiveData.onInactive().

Если «больше не наблюдается»но не очищенный »должен сохранить задачу, тогда OnCleared ViewModel должен действительно управлять SupervisorJob внутри ViewModel, который будет отменен в onCleared(), и search должен быть запущен в этой области, что, вероятно, возможно только если вы передадитеCoroutineScope для search метода.

suspend fun search(scope: CoroutineScope, searchText: String): LiveData<List<T>> =
    scope.launch {
        withContext(Dispatchers.IO) { // or network or something
            val results = networkApi.fetchResults(searchText)
            withContext(Dispatchers.MAIN) {
                MutableLiveData<List<MyData>>().apply { // WARNING: this should probably be replaced with switchMap over the searchText
                    this.value = results
                }
            }
        }
    }

Будет ли это работать? Не уверен, я на самом деле не использую сопрограммы, но я думаю, что это должно. Этот пример, однако, не обрабатывает эквивалент switchMap -ing внутри LiveData и сопрограмм.

...