为什么在调用ViewModel onCleared()方法后,无法使用ViewModelScoped协程

时间:2019-10-11 13:09:47

标签: android dagger-2 kotlin-coroutines android-viewmodel

我正在当前Android应用程序的多个Fragment之间共享一个ActivityScoped viewModel。

viewModel使用协程范围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 个答案:

答案 0 :(得分:2)

  

将cal设置为onCleared()之后,我的viewModelScoped cororoutine启动将停止执行

这是一个功能,而不是错误。

一旦清除ViewModel,您就不应在该ViewModel中进行任何操作,无论其LifecycleOwner是什么。所有这些现在都已失效,不再应该使用。

  

但这是达到我期望的结果的唯一方法吗?

正确的解决方案是摆脱ViewModel中的代码。如果您期望某些背景工作超出活动或片段的生命周期,则该代码不属于活动/片段或其关联的视图模型。它属于可以与您要完成的工作相匹配的生命周期的事物。

答案 1 :(得分:1)

repository.onCleared()

此方法不应属于存储库。

实际上,存储库不应是有状态的。

如果您查看Google的示例the Repository creates a LiveData that contains a Resource,并且发现与之相关的原因是因为the actual data loading and caching mechanic is inside this resource, triggered by LiveData.onActive(在此示例中,MediatorLiveData.addSource,但从语义上讲,这是同一件事)。 / p>

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

如果“不再观察但不再清除”应保留该任务,则ViewModel的onCleared实际上应控制将在onCleared()中取消的ViewModel中的SupervisorJob,并且应在其中启动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
                }
            }
        }
    }

这项工作吗?不确定,我实际上并不使用协程,但我认为应该使用。但是,此示例无法处理与LiveData内部的switchMap相同的操作,也无法使用协程。