Retrofit with Rxjava Handling Network Exceptions Globally

retrofit with rxjava handling network exceptions globally

You need to use the operator onErrorResumeNext(Func1 resumeFunction), better explained in the official wiki:

The onErrorResumeNext( ) method returns an Observable that mirrors the behavior of the source Observable, unless that Observable invokes onError( ) in which case, rather than propagating that error to the Subscriber, onErrorResumeNext( ) will instead begin mirroring a second, backup Observable

In your case I would put something like this:

getCurrentUser = userApi.getCurrentUser()
.onErrorResumeNext(refreshTokenAndRetry(userApi.getCurrentUser()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...)

where:

    private <T> Func1<Throwable,? extends Observable<? extends T>> refreshTokenAndRetry(final Observable<T> toBeResumed) {
return new Func1<Throwable, Observable<? extends T>>() {
@Override
public Observable<? extends T> call(Throwable throwable) {
// Here check if the error thrown really is a 401
if (isHttp401Error(throwable)) {
return refreshToken().flatMap(new Func1<AuthToken, Observable<? extends T>>() {
@Override
public Observable<? extends T> call(AuthToken token) {
return toBeResumed;
}
});
}
// re-throw this error because it's not recoverable from here
return Observable.error(throwable);
}
};
}

Note also that this function can be easily used in other cases, because it's not typed with the actual values emitted by the resumed Observable.

retrofit2 rxjava2 handling network exceptions globally and auto authentification

I found a solution.

if (error.isNotAuthorized()) {
UserManager userManager = Application.getAppComponent().getUserManager();
String email = userManager.getEmail();
String password = userManager.getPassword();
return userManager.getLoginObservable(email, password)
.flatMap(new Function<User, ObservableSource<R>>() {
@Override
public ObservableSource<R> apply(User user) throws Exception {
return ((Observable<R>) wrappedCallAdapter.adapt(call));
}
});

Retrofit 2 and RxJava error handling operators

Here is the solution I came up with.
If I will improve it I will post the changes here.

The solution to my problem (exception swallowed by Retrofit and not handled by RxJava) is the Observable.error method that creates a new observable that only emits the error, so I can "rethrow" the exception.

I created an observable transformer to append to every rest call that emits a retrofit.Result.
This transformer takes an Observable> and, if the response has no errors, transforms it into an Observable>. If there are errors it returns an Observable.error with custom Http*Exceptions that I can later handle in my Observer in the onError callback.
I put it as a static method of an utility class called ObservableTransformations.resultToResponseWithHttpErrorHandling.

Here it is:

public class ObservableTransformations {

public static <T> Observable.Transformer<Result<T>, Response<T>> resultToResponseWithHttpErrorHandling() {
return observable -> observable.flatMap(r -> {
Observable<Response<T>> returnObservable = Observable.just(r.response());
if (r.isError()) {
Throwable throwable = r.error();
if (throwable instanceof IOException) {
Timber.e(throwable, "Retrofit connection error.");
// TODO Check this cases
if (throwable instanceof java.net.ConnectException) {
returnObservable = Observable.error(new HttpNoInternetConnectionException());
} else if (throwable instanceof SocketTimeoutException) {
returnObservable = Observable.error(new HttpServerDownException());
} else {
returnObservable = Observable.error(new HttpNoInternetConnectionException());
}
} else {
Timber.e(throwable, "Retrofit general error - fatal.");
returnObservable = Observable.error(new HttpGeneralErrorException(r.error()));
}
} else {
Response<T> retrofitResponse = r.response();
if (!retrofitResponse.isSuccess()) {
int code = retrofitResponse.code();
String message = "";
try {
message = retrofitResponse.errorBody().string();
} catch (IOException e) {
Timber.e(e, "Error reading errorBody from response");
}
Timber.i("Server responded with error. Code: " + code + " message: " + message);
Throwable t = null;
if (NetworkUtils.isClientError(code)) {
t = new HttpClientException(retrofitResponse.code(), message);
} else if (NetworkUtils.isServerError(code)) {
t = new HttpServerErrorException(retrofitResponse.code(), message);
}
returnObservable = Observable.error(t);
}
}
return returnObservable;
}).retryWhen(new RetryWithDelayIf(3, 1000, t -> {
return (t instanceof HttpNoInternetConnectionException) || (t instanceof HttpServerDownException);
}));
}

}

The retry is made 3 times using an exponential backoff, and only if the exception is HttpNoInternetConnectionException or HttpServerDownException.

The RetryWithDelayIf class is here. It takes the condition to be met for retry as the last argument of the constructor (a function taking a throwable and returning true if this throwable should trigger the retry and false if not).

public class RetryWithDelayIf implements
Func1<Observable<? extends Throwable>, Observable<?>> {

private final int maxRetries;
private final int retryDelayMillis;
private int retryCount;
private Func1<Throwable, Boolean> retryIf;

public RetryWithDelayIf(final int maxRetries, final int retryDelayMillis, Func1<Throwable, Boolean> retryIf) {
this.maxRetries = maxRetries;
this.retryDelayMillis = retryDelayMillis;
this.retryCount = 0;
this.retryIf = retryIf;
}

@Override
public Observable<?> call(Observable<? extends Throwable> attempts) {
return attempts.zipWith(Observable.range(1, maxRetries + 1), (n, i) -> {
return new Tuple<Throwable, Integer>(n, i);
})
.flatMap(
ni -> {
if (retryIf.call(ni.getFirst()) && ni.getSecond() <= maxRetries) {
return Observable.timer((long) Math.pow(2, ni.getSecond()), TimeUnit.SECONDS);
} else {
return Observable.error(ni.getFirst());
}
});
}

}

Finally, here is the usage with a restService call:

restService.login(new LoginRestRequest(username, password))
.compose(ObservableTransformations.resultToResponseWithHttpErrorHandling());

In the onError of your observer you can finally handle the Http*Exceptions.

How to handle different kinds of errors in Retrofit Rx onError without ugly instanceof

The compiler determines which method to call, rather than the VM. So the class you've described won't solve the problem unless you check instanceof first and cast the paramter to the correct type. Otherwise you're going to get handleError(Exception e) every time.

But I wanted to create an answer not for that reason, but to argue that having only one error handler is actually preferable in many cases, not a liability. Oftentimes in java we end up in awful situations like this:

    catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("No such algorithm: RSA?", e);
}
catch (NoSuchProviderException e) {
throw new IllegalStateException("No such provider: " + ANDROID_KEYSTORE_ID, e);
}
catch (InvalidAlgorithmParameterException e) {
throw new IllegalStateException("Bug setting up encryption key for user credentials: ", e);
}
catch (KeyStoreException e) {
throw new IllegalStateException("Bug setting up encryption key for user credentials: ", e);
}
catch (IOException e) {
Log.w(TAG, "Exception setting up keystore for user creds. They won't be stored.", e);
}
catch (CertificateException e) {
Log.w(TAG, "Exception setting up keystore for user creds. They won't be stored.", e);
}

Having only one error handler gives us the ability to lump many types of exceptions together. You can see in this code, there are exceptions that should never be thrown, exceptions that can really only be the result of a bug in the code, and legitimate exceptional states that we need to handle. I find this messy, and would prefer to say:

if (e instanceof NoSuchAlgorithmException || e instanceof NoSuchProviderException)  { 
Log.wtf(TAG, "What the heck is this?", e);
throw new IllegalStateException("This is some kind of weird bug", e);
}
else if (e instanceof IOException || e instanceof CertificateException) {
// This can happen sometimes, track event in analytics and perhaps
// try some alternative means of credential storage.
}
else {
// At least here the app won't crash if some unexpected exception occurs,
// since we're trapping everything.
}

I don't think it's such a bad thing to be able to lump unexpected failures together and handle them in a more user friendly way than crashing the app. Even if it's just a bug, better to track it in your analytics framework behind the scenes than bomb the user out of the app. So many crashes in Android apps are actually completely recoverable, but we don't go around catching Throwable in every try/catch statement because it's a lot of extra code.

Calling independent network calls in parallel with RxJava and handling error

Yes, there is, you can use onErrorResumeNext. example of my code :

primaryMenuFetcher.getMenu()
.observeOn(uiScheduler)
.flatMap { menuItems ->
onView {
primaryMenu = menuItems
setPrimaryMenuList(primaryMenu)
}
return@flatMap model.getPromotions()
}
.onErrorResumeNext { return@onErrorResumeNext model.getPromotions() }
.observeOn(uiScheduler)
.doFinally { onView { hideProgressBar() } }
.subscribe({ fetchedLeagues ->
onView {
featuredLeagues = fetchedLeagues
showPopularLeagues()
setPopularLeaguesList(featuredLeagues)
}
}, {
showError()
})

There is also other Rx2 error handling options. Refer documentation



Related Topics



Leave a reply



Submit