如何取消正在运行的 LiveData 协程块

Yas*_*Ali 5 android kotlin kotlin-extension android-livedata kotlin-coroutines

通过使用 LiveData 的最新版本“androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03”,我使用 LiveData 的新构建块(LiveData + Coroutine)为 ViewModel 中名为“搜索产品”的功能开发了代码使用 Retrofit 执行同步网络调用并相应地更新 ViewModel 中的不同标志(isLoading、isError)。我在“查询”LiveData 上使用 Transforamtions.switchMap,因此只要 UI 中的“查询”发生更改,“搜索产品”代码就会使用 Transformations.switchMap 开始执行。一切正常,除了我想在“查询”LiveData 发生更改时取消之前的改造调用。目前我看不到任何方法来做到这一点。任何帮助,将不胜感激。

class ProductSearchViewModel : ViewModel() {
    val completableJob = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)

    // Query Observable Field
    val query: MutableLiveData<String> = MutableLiveData()

    // IsLoading Observable Field
    private val _isLoading = MutableLiveData<Boolean>()
    val isLoading: LiveData<Boolean> = _isLoading


    val products: LiveData<List<ProductModel>> = query.switchMap { q ->
        liveData(context = coroutineScope.coroutineContext) {
            emit(emptyList())
            _isLoading.postValue(true)
            val service = MyApplication.getRetrofitService()
            val response = service?.searchProducts(q)
            if (response != null && response.isSuccessful && response.body() != null) {
                _isLoading.postValue(false)
                val body = response.body()
                if (body != null && body.results != null) {
                    emit(body.results)
                }
            } else {
                _isLoading.postValue(false)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Raf*_*Ali 6

您可以通过两种方式解决此问题:

方法#1(简单方法)

就像 Mel 在他的回答中解释的那样,您可以在 switchMap 之外保留对作业实例的引用,并在将新的 liveData 返回到 switchMap 之前取消该作业的实例化。

class ProductSearchViewModel : ViewModel() {

    // Job instance
    private var job = Job()

    val products = Transformations.switchMap(_query) {
        job.cancel() // Cancel this job instance before returning liveData for new query
        job = Job() // Create new one and assign to that same variable

        // Pass that instance to CoroutineScope so that it can be cancelled for next query
        liveData(CoroutineScope(job + Dispatchers.IO).coroutineContext) { 
            // Your code here
        }
    }

    override fun onCleared() {
        super.onCleared()
        job.cancel()
    }
}
Run Code Online (Sandbox Code Playgroud)

方法#2(不那么干净,但自包含和可重用)

由于liveData {}builder 块在协程范围内运行,因此您可以使用CompletableDeffered和 coroutine launchbuilder的组合来挂起该 liveData 块并query手动观察liveData 以启动网络请求的作业。

class ProductSearchViewModel : ViewModel() {

    private val _query = MutableLiveData<String>()

    val products: LiveData<List<String>> = liveData {
        var job: Job? = null // Job instance to keep reference of last job

        // LiveData observer for query
        val queryObserver = Observer<String> {
            job?.cancel() // Cancel job before launching new coroutine
            job = GlobalScope.launch {
                // Your code here
            }
        }

        // Observe query liveData here manually
        _query.observeForever(queryObserver)

        try {
            // Create CompletableDeffered instance and call await.
            // Calling await will suspend this current block 
            // from executing anything further from here
            CompletableDeferred<Unit>().await()
        } finally {
            // Since we have called await on CompletableDeffered above, 
            // this will cause an Exception on this liveData when onDestory
            // event is called on a lifeCycle . By wrapping it in 
            // try/finally we can use this to know when that will happen and 
            // cleanup to avoid any leaks.
            job?.cancel()
            _query.removeObserver(queryObserver)
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您可以在此演示项目中下载并测试运行这两种方法

编辑:更新方法 # 1 以在 onCleared 方法上添加作业取消,正如 yasir 在评论中指出的那样。