📜 ⬆️ ⬇️

RxJava2 + Retrofit 2. Modify the adapter to handle the absence of the Internet on Android



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:


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()) } } //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 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.

Source: https://habr.com/ru/post/442072/


All Articles