Using CoroutineContext to repeat failed HTTP request

Roman Kamyshnikov

A common scenario in Android applications is the need to repeat a request when something goes wrong, for example when there are problems with the users internet connection.

Let’s assume that we have a screen with several HTTP requests and we need to show a dialog that lets the user retry the request that failed.

If the screen had only one request — the task would be pretty straightforward: just call the method again. But if the screen has several requests that can be called at different times we need a way to identify the function that was called. Storing the last called function is a possible solution, but it’s not very flexible: if some requests don’t need to show a “Try again” dialog we must clear the variable after a success and forgetting to do this can lead to bugs.

A simple solution to the problem above can be to use CoroutineContext to save the function we need to call again later.

By default, CoroutineContext is composed of the elements Job, CoroutineDispatcher, CoroutineName and CoroutineExceptionHandler (you can read more about it in my previous article about Coroutines). But, as stated in the docs, CoroutineContext is an indexed set of Element instances, so we can create our own implementation of the Element interface, by extending the class AbstractCoroutineContextElement:

class RetryCallback(val callback: () -> Unit) :
AbstractCoroutineContextElement(RetryCallback) {
companion object Key : CoroutineContext.Key<RetryCallback>
}

Now, we can just add an instance of our custom RetryCallback element to the CoroutineContext of any coroutine we start:

fun someFunction(someParam: List<String>) {
viewModelScope.launch(exceptionHandler
+ RetryCallback { someFunction(someParam) }) {
// perform the request, update the view with the result
}
}

And if the request fails we can can access the callback in the CoroutineExceptionHandler:

private val exceptionHandler = CoroutineExceptionHandler {
coroutineContext, throwable ->
val callback: (() -> Unit)? = coroutineContext[RetryCallback.Key]
?.callback
// ...
}

From the CoroutineExceptionHandler we can just pass the function to our Activity or Fragment and invoke it once the user clicks the “Try again” button.