Search code examples
androidkotlinretrofit2kotlin-coroutines

Is it better to call .enqueue in a normal function or .execute in a kotlin suspend function?


Here is what I already know:
Retrofit has the enqueue function and the execute function. The enqueue function executes on a different (background) thread and then returns the response using the callback. The execute function executes on the calling Thread and returns the response directly. enqueue can be called on the UI Thread while execute shouldn't be called on the UI Thread.

But I am now wondering, which of the following two options is better.

Call enqueue in a normal function:

fun makeRequest() {
    getCall().enqueue(
        object : Callback<ResponseBody> {
            override fun onResponse(
                call: Call<ResponseBody>,
                response: Response<ResponseBody>
            ) {
                
                if (response.isSuccessful) {
                    //unsuccessful
                }
                //successful
            }
            
            override fun onFailure(call: Call<ResponseBody>, t: Throwable) {
                //failed
            }
            
        }
    )
}

or call execute in a suspend function on a background thread:

suspend fun makeRequest() = withContext(Dispatchers.IO) {
    val call = getCall()
    
    try {
        val response = call.execute()
        
        if (!response.isSuccessful) {
            //unsuccessful
        }
        
        //successful
    } catch (t: Throwable) {
        //failed
    }
}

Which one of these is preferable?


Solution

  • Coroutines have cleaner syntax, so that's a plus. And if you're familiar with coroutine SupervisorJob, you can more easily cancel groups of requests. Other than that, they are largely the same except for which background thread is getting used for the request. But Retrofit already has built-in coroutine support, so your second version can be cleaner than what you have:

    suspend fun makeRequest() { // Can be called from any dispatcher
        try {
            val response = getCall().awaitResponse()
            
            if (!response.isSuccessful) {
                //unsuccessful
            }
            
            //successful
        } catch (e: IOException) {
            //failed
        }
    }
    

    Don’t catch all types by catching the Throwable type. That will swallow CancellationExceptions which would prevent your coroutines from cooperating with cancellation.