
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. Good practice is to show the user a specific UI, which would explain to him what happened and allow him to re-throw the request. Adding this logic can be quite painful, especially when we have a huge variety of ViewModel classes. Of course, you can implement the logic of re-queries in each ViewModel class, but this is not convenient and there is a huge possibility of errors.
Is there a way to do this processing only once?
Fortunately, RxJava2 and Retrofit2 allow you to do this.
There are already several solutions on Stackoverflow:
')
1. Create your own CallAdapterFactory (read more
here )
2. Repeat chaining using PublishSubject (read more
here )
In the first solution, RxJava1 is used, it is already outdated and it just repeats the chain several times, without reacting, to the occurrence of an event. The second solution is good, but we need to use the operator retryWhen 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 items are loaded by the API. If an error occurs during execution, we will 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 repeating a request in one place and avoiding duplication of this code. Create two fragments that will extend the BaseFragment. Each posted fragment has its own ViewModel and independently makes requests to the API. I created these fragments to show that when an error occurs, each request will be repeated. Then create a RxRetryCallAdapterFactory factory that extends CallAdapter.Factory. After that, create an instance of RxJava2CallAdapterFactory. We need this instance to gain access to 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<Annotation>, 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 a CallAdapter instance to the constructor. In fact, it should be an instance of RxJava2CallAdapter, which is returned by the original factory.
Next, we need to implement the following things:
- retryWhen operator is used to implement repetition functionality.
- A doOnError () statement that handles errors. It is used to send a broadcast that is processed in BaseActivity and shows the SnackBar to the user.
- PublishSubject is used as an event trigger that rewrites the chain.
- observeOn (Schedulers.io ()) operator that needs to be applied to the PublishSubject (if you do not add this line, the subscription will occur in the main thread and we will receive a NetworkOnMainThreadException
- Transform PublishSubject to Flowable and install BackpressureStrategy.LATEST, since we only need the last error.
Note: To provide PublishSubject, I created a simple singleton class that provides all the singleton dependencies in a project. In a real project, you will probably use a dependency injection framework, such as Dagger2
class RxRetryCallAdapter<R>(private val originalAdapter : CallAdapter<R, *>) : CallAdapter<R, Any> { override fun adapt(call : Call<R>) : 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()) } }
When the user clicks the button Try again we call onNext PublishSubject. After that, we reassign to the rx chain.
Testing
Turn off the Internet and run the application. The number of loaded items is zero on each tab and the 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 is
here.