I\'m calling an API using Retrofit 2 and RxJava2. If a call fails, in some cases (e.g. no Internet connection), I want to display an error dialog to the user and let him retry.<
final PublishSubject<Object> retrySubject = PublishSubject.create();
disposable.add(getData()
.doOnError(throwable -> enableButton())
.retryWhen(observable -> observable.zipWith(retrySubject, (o, o2) -> o))
.subscribeWith(/* do what you want with the result*/)
}));
When button is clicked you trigger this event:
retrySubject.onNext(new Object());
As you can see in this Marble diagram:
the error is not propagated. The retryWhen
operator will indeed handle it and do a proper action. This is the reason why you have to enable (or for example show a Dialog) in doOnError
, before retryWhen
operator.
When you don't want to listen anymore for successive retry attempts, you just need to unsubscribe:
disposable.dispose();
As per your question:
What should I do if I want to retry only on a specific Exception but not on the other ones?
You can modify your retryWhen
in this way:
.retryWhen(throwableObservable -> throwableObservable.flatMap(throwable -> {
if (throwable instanceof TargetException) {
return Observable.just(throwable).zipWith(retrySubject, (o, o2) -> o);
} else {
throw Throwables.propagate(throwable);
}
}))
Where Throwables.propagate(throwable)
is a Guava util that can be replaced with throw new RuntimeException(throwable);
for me i am using clean architecture and it was hard to put android code to show dialog in usecase or repository. so since i was using kotlin i passed in a lamba to take care of things for me. something like this:
here is my full presenter:
class WeatherDetailsPresenter @Inject constructor(var getCurrentWeatherWithForecastUsecase: GetCurrentWithForcastedWeatherUsecase) : BaseMvpPresenter<WeatherDetailsView>() {
fun presentCurrentAndForcastedWeather(country: String?) {
getCurrentWeatherWithForecastUsecase.takeUnless { country?.isBlank() == true }?.apply {
this.countyName = country
execute(object : DefaultSubscriber<Pair<CurrentWeatherModel, ForecastedWeatherModel>>() {
override fun onSubscribe(d: Disposable) {
super.onSubscribe(d)
showLoading(true)
}
override fun onNext(t: Pair<CurrentWeatherModel, ForecastedWeatherModel>) {
super.onNext(t)
view?.showCurrentWeather(t.first)
view?.showForcastWeather(t.second)
}
override fun onError(e: Throwable) {
super.onError(e)
//if i get an error pass in lambda to the dialog
myErrorDialog.setretryAction( {this@WeatherDetailsPresenter.presentCurrentAndForcastedWeather(country)})
}
override fun onComplete() {
super.onComplete()
showLoading(false)
}
})
} ?: run { view?.showCountryUnavailable() }
}
then in then errordialog i do this assuming its a dialogfragment:
lateinit var retryAction: () -> Unit
override fun onStart() {
super.onStart()
dialog.window.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
button.setOnClickListener { dismiss();retryAction() }
}
my code for on error is not acutally like that but just to give you an idea on how to pass in a lambda. im sure you can pass in a function of your own. in this case the lambda was the method itself to retry.