Reputation: 21237
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
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