How can I wait the withContext in suspend function completes?

2k Views Asked by At

I am reading about Kotlin coroutine in Google 's documentation. I'm adviced to use withContext(Dispacher.IO) to a different thread to main-safety. But I have a problem , fetchData() done before response from server so fetchData() return null result. Any help that I appreciate.

https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe

class GameRemoteDataSource @Inject constructor(val api : GameApi) {
    val IODispatcher: CoroutineDispatcher = Dispatchers.IO

    suspend fun fetchData() : Resource<ListGameResponse> {
        var resource : Resource<ListGameResponse> = Resource.loading(null)
        withContext(IODispatcher){
            Log.d("AAA Thread 1", "${Thread.currentThread().name}")
            api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse>{
                override fun onResponse(
                    call: Call<ListGameResponse>,
                    response: Response<ListGameResponse>
                ) {
                    if(response.code()==200){
                        resource = Resource.success(response.body())
                    }else{
                        resource = Resource.success(response.body())
                    }
                    Log.d("AAA code",response.code().toString())
                }
                override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
                    resource = Resource.error(t.message.toString(),null)
                    Log.d("AAA Thread", "${Thread.currentThread()}")
                }

            })
            Log.d("AAA Thread", "${Thread.currentThread()}")
            Log.d("AAA resource",resource.data.toString()+ resource.status.toString())
        }
        return resource
    }
}
2

There are 2 best solutions below

6
On

withContext is not helpful for converting an asynchronous function with callback into suspending code that can be used in a coroutine. It is more applicable to converting synchronous blocking code. Your non-working strategy of creating an empty variable and trying to fill it in the callback to synchronously return is described in the answers to this question.

For an asynchronous function with callback, if it returns a single value like your code above, this is typically converted to a suspend function using suspendCoroutine or suspendCancellableCoroutine. If it returns a series of values over time (calls the callback multiple times), it would be fitting to use callbackFlow to convert it to a Flow that can be collected in a coroutine.

But it looks like you're using Retrofit, which already has a suspend function alternatives to enqueue so you don't need to worry about all this. You can use the await() or awaitResponse() functions instead. In this case, await() would return ListGameResponse and awaitResponse() would return Response<ListGameResponse>. So awaitResponse() is better if you need to check the response code.

Awaiting returns the response and throws an exception if there's an error, so you can use try/catch instead of adding a failure listener.

class GameRemoteDataSource @Inject constructor(val api : GameApi) {

    suspend fun fetchData(): Resource<ListGameResponse> {
        return try {
            val response = api.getAllGame(page = 1).awaitResponse()
            Log.d("AAA code", response.code().toString())
            Resource.success(response.body())
        } catch (exception: Exception) {
            Resource.error(exception.message.toString(),null)
        }
    }

}
0
On

You should use suspendCancellableCoroutine to convert asynchronous API into a coroutine flow, like this

suspend fun fetchData(): ListGameResponse = withTimeout(Duration.seconds(60)) {
  suspendCancellableCoroutine<ListGameResponse> { cont ->
    api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse> {
      override fun onResponse(
          call: Call<ListGameResponse>,
          response: Response<ListGameResponse>
      ) {
        Log.d("AAA code", response.code().toString())
        cont.resume(response.body())
      }

      override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
        cont.resumeWithException(t)
      }
    })
  }
}