Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJava + Retrofit -> BaseObservable for API calls for centralized response handling

I am new to RxJava so please forgive me if this sounds too newbie :-).

As of now I have an abstract CallbackClass that implements the Retofit Callback. There I catch the Callback's "onResponse" and "onError" methods and handle various error types before finally forwarding to the custom implemented methods. I also use this centralized class to for request/response app logging and other stuff.

For example: for specific error codes from my sever I receive a new Auth token in the response body, refresh the token and then clone.enqueue the call. There are of course several other global behaviors to the responses from my server.

Current solution (Without Rx):

    public abstract void onResponse(Call<T> call, Response<T> response, boolean isSuccess);

    public abstract void onFailure(Call<T> call, Response<T> response, Throwable t, boolean isTimeout);

    @Override
    public void onResponse(Call<T> call, Response<T> response) {
        if (_isCanceled) return;

        if (response != null && !response.isSuccessful()) {
            if (response.code() == "SomeCode" && retryCount < RETRY_LIMIT) {
                TokenResponseModel newToken = null;
                try {
                    newToken = new Gson().fromJson(new String(response.errorBody().bytes(), "UTF-8"), TokenResponseModel.class);
                } catch (Exception e) {
                    e.printStackTrace();
                }

                    SomeClass.token = newToken.token;
                    retryCount++;
                    call.clone().enqueue(this);
                    return;
                }
            }
        } else {
            onResponse(call, response, true);
            removeFinishedRequest();
            return;
        }

        onFailure(call, response, null, false);
        removeFinishedRequest();
    }

    @Override
    public void onFailure(Call<T> call, Throwable t) {
        if (_isCanceled) return;

        if (t instanceof UnknownHostException)
            if (eventBus != null)
                eventBus.post(new NoConnectionErrorEvent());

        onFailure(call, null, t, false);
        removeFinishedRequest();
    }

My question is: Is there any way to have this sort of centralized response handling behavior before finally chaining (or retrying) back to the subscriber methods?

I found these 2 links which both have a nice starting point but not a concrete solution. Any help will be really appreciated.

Forcing request retry after custom API exceptions in RxJava

Retrofit 2 and RxJava error handling operators

like image 559
GuyZ Avatar asked Sep 11 '16 14:09

GuyZ


1 Answers

Two links you provided are a really good starting point, which I used to construct solution to react to accidental

  • network errors happen sometimes due to temporary lack of network connection, or switch to low throughtput network standard, like EDGE, which causes SocketTimeoutException
  • server errors -> happen sometimes due to server overload

I have overriden CallAdapter.Factory to handle errors and react appropriately to them.

  1. Import RetryWithDelayIf from the solution you found

  2. Override CallAdapter.Factory to handle errors:

    public class RxCallAdapterFactoryWithErrorHandling extends CallAdapter.Factory {
        private final RxJavaCallAdapterFactory original;
    
        public RxCallAdapterFactoryWithErrorHandling() {
            original = RxJavaCallAdapterFactory.create();
        }
    
        @Override
        public CallAdapter<?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
            return new RxCallAdapterWrapper(retrofit, original.get(returnType, annotations, retrofit));
        }
    
        public class RxCallAdapterWrapper implements CallAdapter<Observable<?>> {
            private final Retrofit retrofit;
            private final CallAdapter<?> wrapped;
    
            public RxCallAdapterWrapper(Retrofit retrofit, CallAdapter<?> wrapped) {
                this.retrofit = retrofit;
                this.wrapped = wrapped;
            }
    
            @Override
            public Type responseType() {
                return wrapped.responseType();
            }
    
            @SuppressWarnings("unchecked")
            @Override
            public <R> Observable<?> adapt(final Call<R> call) {
                return ((Observable) wrapped.adapt(call)).onErrorResumeNext(new Func1<Throwable, Observable>() {
                    @Override
                    public Observable call(Throwable throwable) {
                        Throwable returnThrowable = throwable;
                        if (throwable instanceof HttpException) {
                            HttpException httpException = (HttpException) throwable;
                            returnThrowable = httpException;
                            int responseCode = httpException.response().code();
                            if (NetworkUtils.isClientError(responseCode)) {
                                returnThrowable = new HttpClientException(throwable);
                            }
                            if (NetworkUtils.isServerError(responseCode)) {
                                returnThrowable = new HttpServerException(throwable);
                            }
                        }
    
                        if (throwable instanceof UnknownHostException) {
                            returnThrowable = throwable;
                        }
    
                        return Observable.error(returnThrowable);
                    }
                }).retryWhen(new RetryWithDelayIf(3, DateUtils.SECOND_IN_MILLIS, new Func1<Throwable, Boolean>() {
                    @Override
                    public Boolean call(Throwable throwable) {
                        return throwable instanceof HttpServerException
                                || throwable instanceof SocketTimeoutException
                                || throwable instanceof UnknownHostException;
                    }
                }));
            }
        }
    }
    

    HttpServerException is just a custom exception.

  3. Use it in Retrofit.Builder

    Retrofit retrofit = new Retrofit.Builder()
            .addCallAdapterFactory(new RxCallAdapterFactoryWithErrorHandling())
            .build();
    

Extra: If you wish to parse errors that come from API (error that don't invoke UnknownHostException, HttpException or MalformedJsonException or etc.) you need to override Factory and use custom one during building Retrofit instance. Parse the response and check if it contains errors. If yes, then throw error and error will be handled inside the method above.

like image 103
R. Zagórski Avatar answered Oct 03 '22 06:10

R. Zagórski