AndroidDev
AndroidDev

Reputation: 21237

Kotlin Coroutine wait for Retrofit Response

I'm trying to use the Android MVVM pattern with a repository class and Retrofit for network calls. I have the common problem that I can't get the coroutine to wait for the network response to return.

This method is in my ViewModel class:

private fun loadConfigModel() {
    val model = runBlocking {
        withContext(Dispatchers.IO) {
            configModelRepository.getConfigFile()
        }
    }
    configModel.value = model
}

In ConfigModelRepository, I have this:

suspend fun getConfigFile(): ConfigModel {
    val configString = prefs.getString(
        ConfigViewModel.CONFIG_SHARED_PREF_KEY, "") ?: ""

    return if (configString.isEmpty() || isCacheExpired()) {
        runBlocking { fetchConfig() }
    } else {
        postFromLocalCache(configString)
    }
}

private suspend fun fetchConfig(): ConfigModel {
    return suspendCoroutine { cont ->
         dataService
            .config()  // <-- LAST LINE CALLED
            .enqueue(object : Callback<ConfigModel> {
                 override fun onResponse(call: Call<ConfigModel>, response: Response<ConfigModel>) {
                    if (response.isSuccessful) {
                        response.body()?.let {
                            saveConfigResponseInSharedPreferences(it)
                            cont.resume(it)
                        }
                    } else {
                        cont.resume(ConfigModel(listOf(), listOf()))
                    }
                }

                override fun onFailure(call: Call<ConfigModel>, t: Throwable) {
                    Timber.e(t, "config fetch failed")
                    cont.resume(ConfigModel(listOf(), listOf()))
                }
            })
    }
}

My code runs as far as dataService.config(). It never enters onResponse or onFailure. The network call goes and and returns properly (I can see this using Charles), but the coroutine doesn't seem to be listening for the callback.

So, my question is the usual one. How can I get the coroutines to block such that they wait for this callback from Retrofit? Thanks.

Upvotes: 3

Views: 3009

Answers (1)

Enselic
Enselic

Reputation: 4912

The problem must be that response.body() returns null since that is the only case that is missing a call to cont.resume(). Make sure to call cont.resume() also in that case and your code should at least not get stuck.

But like CommonsWare points out, even better would be to upgrade to Retrofit 2.6.0 or later and use native suspend support instead of rolling your own suspendCoroutine logic.

You should also stop using runBlocking completely. In the first case, launch(Dispatchers.Main) a coroutine instead and move configModel.value = model inside of it. In the second case you can just remove runBlocking and call fetchConfig() directly.

Upvotes: 4

Related Questions