You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在LiveData协程架构中取消旧的Retrofit搜索请求?

Solution to Cancel Previous Retrofit Requests on Query Change

Hey there! Great job setting up your product search with LiveData and Coroutines—let's fix that request cancellation issue you're running into. The key here is leveraging the built-in coroutine lifecycle management of the liveData builder, which will automatically cancel old coroutines when a new query comes in.

The Problem with Your Current Code

Right now, you're using a custom CoroutineScope tied to a completableJob. While this works for running the request, it doesn't automatically cancel the old coroutine when a new query triggers the switchMap. Additionally, your isLoading state isn't properly handled if the request gets canceled mid-flight.

The Fix

Here's how to adjust your code to enable automatic cancellation of previous requests:

  1. Remove the custom CoroutineScope: The liveData builder provides its own coroutine scope that's tied to the LiveData's lifecycle. When a new query comes through switchMap, the old LiveData instance (and its coroutine) is discarded, and the scope is canceled automatically.
  2. Use try/finally for isLoading: This ensures we always reset the loading state, even if the coroutine is canceled.
  3. Let Retrofit handle cancellation: Retrofit's synchronous calls in a coroutine will automatically throw a CancellationException when the coroutine is canceled, which interrupts the network request.

Updated Code

class ProductSearchViewModel : ViewModel() {

    // 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 = Dispatchers.IO) {
            emit(emptyList())
            _isLoading.postValue(true)
            
            try {
                val service = MyApplication.getRetrofitService()
                val response = service?.searchProducts(q)
                
                if (response != null && response.isSuccessful && response.body()?.results != null) {
                    emit(response.body()!!.results)
                }
            } catch (e: CancellationException) {
                // This is expected when the coroutine is canceled—no need to handle specially
            } finally {
                _isLoading.postValue(false)
            }
        }
    }
}

Key Explanations

  • Automatic Coroutine Cancellation: When query changes, switchMap creates a new liveData instance. The old instance's coroutine scope is canceled, which immediately stops the ongoing Retrofit request.
  • try/finally Block: Guarantees that isLoading is set to false regardless of whether the request succeeds, fails, or is canceled. This prevents the UI from getting stuck in a loading state.
  • CancellationException Handling: We catch this explicitly (though you could omit the catch block if you don't need to log anything) because it's a normal part of the cancellation flow, not an error.

Bonus Tips

  • If you're using Retrofit's suspend functions instead of synchronous calls (which is recommended), cancellation works the same way—coroutine cancellation will interrupt the suspend call.
  • Avoid using postValue if you're already in the main dispatcher, but since we're in Dispatchers.IO, postValue is the right choice to update the LiveData on the main thread.

内容的提问来源于stack exchange,提问作者Yasir Ali

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 07:19:43