Hot questions for Handling exception in Retrofit

Top 10 Java Open Source / Retrofit / Handling exception

retrofit with rxjava handling network exceptions globally

Question: I am trying to handle exceptions in app on global level, so that retrofit throws an error i catch it in some specific class with logic for handling those errors.

I have an interface

@POST("/token")
AuthToken refreshToken(@Field("grant_type") String grantType, @Field("refresh_token") String refreshToken);

and observables

public Observable<AuthToken> refreshToken(String refreshToken) {
    return Observable.create((Subscriber<? super AuthToken> subscriber) -> {
        try {
            subscriber.onNext(apiManager.refreshToken(REFRESH_TOKEN, refreshToken));
            subscriber.onCompleted();
        } catch (Exception e) {
            subscriber.onError(e);
        }
    }).subscribeOn(Schedulers.io());
}

When i get 401 from server (invalid token or some other network related error) i want to refresh the token and repeat the rest call. Is there a way to do this with rxjava for all rest calls with some kind of observable that will catch this error globally, handle it and repeat the call that throw-ed it?

For now i am using subject to catch the error on .subscribe() like this

private static BehaviorSubject errorEvent = BehaviorSubject.create();

public static BehaviorSubject<RetrofitError> getErrorEvent() {
    return errorEvent;
}

and in some call

getCurrentUser = userApi.getCurrentUser().observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                    (user) -> {
                        this.user = user;
                    },
                    errorEvent::onNext
            );

then in my main activity i subscribe to that behaviour subject and parse the error

SomeApi.getErrorEvent().subscribe(
            (e) -> {
                //parse the error
            }
    );

but i cant repeat the call for the observable that throw the error.

Answer: You need to use the operator onErrorResumeNext(Func1 resumeFunction), 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.


OkHttpClient throws exception after upgrading to OkHttp3

Question: I'm using following lines of code to add a default header to all of my requests sent using Retrofit2:

private static OkHttpClient defaultHttpClient = new OkHttpClient();
static {
    defaultHttpClient.networkInterceptors().add(new Interceptor() {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request().newBuilder()
                    .addHeader("Accept", "Application/JSON").build();
            return chain.proceed(request);
        }
    });
}

After upgrading retrofit to beta-3 version, I had to upgrade OkHttp to OkHttp3 also (actually I just changed package names from okhttp to okhttp3, the library is included inside retrofit). After that I get exceptions from this line:

defaultHttpClient.networkInterceptors().add(new Interceptor());
Caused by: java.lang.UnsupportedOperationException at java.util.Collections$UnmodifiableCollection.add(Collections.java:932)

What is the problem here?

Answer: You have to use builder if you want to create OkHttp(3)Client object.

Try change this:

private static OkHttpClient defaultHttpClient = new OkHttpClient();

To something like this:

OkHttpClient defaultHttpClient = new OkHttpClient.Builder()
       .addInterceptor(
           new Interceptor() {
             @Override
             public Response intercept(Interceptor.Chain chain) throws IOException {
                   Request request = chain.request().newBuilder()
                   .addHeader("Accept", "Application/JSON").build();
               return chain.proceed(request);
              }
           }).build();

How to get response body to retrofit exception?

Question: I am trying to connect to rest service via retrofit in android application. I am getting responses. But when there is some error response from the service, conversion exception occurs and now I want to do some actions based on the response body. But I am getting response body as NULL. But retrofit log has a error message. Why is this happening.

D/Reftofit log(24856): OkHttp-Received-Millis: 1397527055676
D/Reftofit log(24856): OkHttp-Response-Source: NETWORK 200
D/Reftofit log(24856): OkHttp-Sent-Millis: 1397527055492
D/Reftofit log(24856): Server: Apache/2.2.22 (Ubuntu)
D/Reftofit log(24856): X-Powered-By: PHP/5.3.10-1ubuntu3.10
D/Reftofit log(24856): {"result":"Invalid Token ID"}

Code:

public void failure(RetrofitError retrofitError) {
    String response = null;
    TokenError tokenError = (TokenError) retrofitError.getBodyAs(TokenError.class);
    response = tokenError.getErrorDetails();
    Log.e(TAG, response);
    if (response != null && response.contains("Invalid Token ID")) {
        GroupDataProvider.getInstance().onFailure();
    }

}

Here I am getting tokenError as null. I don't know why? Do I need to set something with rest adapter so that the response will be passed to retrofit error object.

Answer: Try this code:

@Override
public void failure(RetrofitError error) {
    String json =  new String(((TypedByteArray)error.getResponse().getBody()).getBytes());
    Log.v("failure", json.toString());
}

with Retrofit 2.0

@Override
public void onFailure(Call<Example> call, Throwable t) {
    String message = t.getMessage();
    Log.d("failure", message);
}