Retrofit with Kotlin Coroutines: handling network issues and operations cancellation

Alex Zaitsev
2 min readJan 23, 2020

--

Since Retrofit supports Kotlin Coroutines it’s so convenient to use it with suspend functions and viewModelScope.

The problem

But the real pain can be to properly cancel Retrofit operations (e.g. when network connection disappears). I mean the case when user started some network operation with Retrofit and then connection broke. By default you’ll get some connection exception that cannot be handled (at least in my case).

I have authentication interceptor inside Koin network module and it throws an exception on the last line chain.proceed(request) when timeout is over after connection brake. I tried to wrap that part of the code in try-catch and it didn’t help — app was failing at that line. Anyway, even if it worked this had to be only temporary solution because it can prevent the crash but how to react in the UI?

There are some answers at the stackoverflow but the offered solution is to create interceptor or custom client that checks network connection before performing network call. Whether that can sound similar, it would not work since it doesn’t solve the cancellation problem.

Similar issue registered on Github. It leads to the PR that adds coroutines support to Retrofit. So there should be a way. And it is.

Solution

At first, in ViewModel you should somehow be notified that network connection was broken.

var isNetworkAvailable = MutableLiveData<Boolean>()

Let’s consider that this LiveData can be changed from activity. Then, let’s observe it in ViewModel.

init {
isNetworkAvailable.observeForever(networkObserver)
}

override fun onCleared() {
super.onCleared()
isNetworkAvailable.removeObserver(networkObserver)
}

The main point here is not to forget to remove the observer. But where it is?

private val networkObserver = Observer<Boolean> {
if (!it && ::job.isInitialized && job.isActive) {
job.cancelChildren(CancellationException("No network"))
// change UI - hide progress / show Retry button etc.
_error.value = UiError.Resource(R.string.error_network)
_state.value = UiState.SHOW_RETRY
}
}

Here we cancel the job and react appropriately in the UI.

In case you don’t know what is the job see the below code. It also covers another interesting topic — how to prevent network operation if the is no connection.

private lateinit var job: Jobfun runAsync(callback: suspend () -> Unit) {
if (isNetworkAvailable.value == true) {
_state.value = UiState.IN_PROGRESS
job = viewModelScope.launch {
callback.invoke()
_state.value = UiState.LOADED
}
} else {
_error.value = UiError.Resource(R.string.error_network)
_state.value = UiState.SHOW_RETRY
}
}

I suggest to keep all these code in BaseViewModel.

Conclusion

That’s all! Thanks for your attention.

Also you may consider reading my another publication about how to properly change isNetworkAvailable from activity.

--

--

Alex Zaitsev

Migrating to https://alexzaitsev.substack.com, follow me there! #android #mobile #kmp #kmm #kotlin #multiplatform #flutter #crossplatform