
RxJava2 + Retrofit 2. We modify the adapter to handle the lack of Internet status on Android
- Transfer

Quite often it is necessary to make repeated requests to the network, for example, when the user did not have the Internet and wanted to receive data from the Internet. It would be nice to re-throw the request when it appears. It’s good practice to show the user a specific UI that would explain to him what happened and allow the request to be thrown again. Adding such logic can be quite painful, especially when we have a huge number of ViewModel classes. Of course, you can implement the re-query logic in each ViewModel class, but this is not convenient and there is a huge chance of errors.
Is there a way to do this processing only once?
Fortunately, RxJava2 and Retrofit2 allow this to be done.
There are already several solutions on Stackoverflow:
1. Creating your own CallAdapterFactory (more info here )
2. Repeating the chain using PublishSubject (more info here )
The first solution uses RxJava1, it is already outdated and just repeats it chain several times without reacting to the occurrence of an event. The second solution is good, but we need to use the retryWhen operator in each chain. And so, I combined the two solutions into one.
Implementation
Let's create a simple project. Place two tabs on the main screen. Each of them displays text that will show how many elements are loaded by API. If an error occurs during execution, we display a SnackBar with a Try Again button.

We define such base classes as BaseActivity, BaseFragment, BaseViewModel, they are necessary to implement the logic of request repetition in one place and avoid duplication of this code. Create two fragments that will extend BaseFragment. Each placed fragment has its own ViewModel and independently makes requests to the API. I created these snippets to show that if an error occurs, each request will be repeated. Next, create an RxRetryCallAdapterFactory factory that extends CallAdapter.Factory. After that, create an instance of RxJava2CallAdapterFactory. We need this instance to access the RxJava2CallAdapter, since we do not want to duplicate the code that is already in the Retrofit library. Also, let's create a static method that will return an instance of our factory. Sample code below:
class RxRetryCallAdapterFactory : CallAdapter.Factory() {
companion object {
fun create() : CallAdapter.Factory = RxRetryCallAdapterFactory()
}
private val originalFactory = RxJava2CallAdapterFactory.create()
override fun get(returnType : Type, annotations : Array, retrofit : Retrofit) : CallAdapter<*, *>? {
val adapter = originalFactory.get(returnType, annotations, retrofit) ?: return null
return RxRetryCallAdapter(adapter)
}
}
Next, create an RxRetryCallAdapter that implements the CallAdapter interface and we need to pass the CallAdapter instance to the constructor. In fact, it must be an instance of RxJava2CallAdapter, which returns the original factory.
Next, we need to implement the following things:
- retryWhen statement used to implement repetition functionality
- doOnError () statement that handles errors. It is used to send the broadcast, which is processed in BaseActivity and shows the SnackBar to the user.
- PublishSubject is used as an event trigger that re-signs the chain.
- observeOn (Schedulers.io ()) operator that must be applied to PublishSubject (if this line is not added, the subscription will happen in the main thread and we will get a NetworkOnMainThreadException
- We transform PublishSubject into Flowable and set BackpressureStrategy.LATEST, since we need only the last error
Note: To provide PublishSubject, I created a simple singleton class that exposes all the singleton dependencies in the project. In a real project, you are likely to use a dependency injection framework like Dagger2
class RxRetryCallAdapter(private val originalAdapter : CallAdapter) : CallAdapter {
override fun adapt(call : Call) : Any {
val adaptedValue = originalAdapter.adapt(call)
return when (adaptedValue) {
is Completable -> {
adaptedValue.doOnError(this::sendBroadcast)
.retryWhen {
AppProvider.provideRetrySubject().toFlowable(BackpressureStrategy.LATEST)
.observeOn(Schedulers.io())
}
}
is Single<*> -> {
adaptedValue.doOnError(this::sendBroadcast)
.retryWhen {
AppProvider.provideRetrySubject().toFlowable(BackpressureStrategy.LATEST)
.observeOn(Schedulers.io())
}
}
//same for Maybe, Observable, Flowable
else -> {
adaptedValue
}
}
}
override fun responseType() : Type = originalAdapter.responseType()
private fun sendBroadcast(throwable : Throwable) {
Timber.e(throwable)
LocalBroadcastManager.getInstance(AppProvider.appInstance).sendBroadcast(Intent(BaseActivity.ERROR_ACTION))
}
}
When the user clicks the Try again button, we call onNext PublishSubject. After that, we re-subscribe to the rx chain.
Testing
Turn off the Internet and run the application. The number of loaded items is zero on each tab and SnackBar displays an error. Turn on the Internet and click on Try Adain. After a few seconds, the number of loaded items changes on each of the tabs.

If anyone needs it, then the source code is here