Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxJava and Retrofit - Raising custom exceptions depending on server response

I would like Retrofit to raise custom exceptions depending on the server response. For example in the following structure:

{
"code":0,
"message":"OK",
"data":{....}
}

I would like to raise an exception for subscribers if code is anything other than 0. How is it possible using Retrofit and Rx? I would much prefer to write this logic only once and have it applied to all observables returned by retrofit.

like image 584
Nima G Avatar asked May 10 '15 17:05

Nima G


2 Answers

I would like to raise an exception for subscribers if code is anything other than 0. How is it possible using Retrofit and Rx?

You can use a Observable.flatMap operator:

api.request().flatMap(response -> {
    if (response.getCode() != 0) {
        return Observable.error(new Exception("Remote error occurred"));
    }

    return Observable.just(response);
});

I would much prefer to write this logic only once and have it applied to all observables returned by retrofit.

Unfortunately, there is not way to do it using retrofit and rx-java. You have to write the code above for every retrofit call. The only thing you can do is to use Observable.compose method and reduce the amount of boilerplate you actually have to write.

api.request().compose(new ResponseTransformer<Response>());

And here is the ResponseTransformer class:

public static class ResponseTransformer<T extends Response> implements Observable.Transformer<T, T> {
    @Override
    public Observable<T> call(final Observable<T> observable) {
        return observable.flatMap(response -> {
            if (response.getCode() != 0) {
                return Observable.error(new Exception("Remote error occurred"));
            }

            return Observable.just(response);
        });
    }
}

UPDATE

Well, as I said, there is no way to avoid boilerplate code using only retrofit and rxjava, but you can workaround it with dynamic proxies (note that you don't need to call compose anymore):

final Api api = restAdapter.create(Api.class);

final ClassLoader loader = api.getClass().getClassLoader();
final Class<?>[] interfaces = api.getClass().getInterfaces();

final Api proxy = (Api) Proxy.newProxyInstance(loader, interfaces, new ResponseInvocationHandler(api));

proxy.request().subscribe(response -> {
    System.out.println("Success!");
});

ResponseInvocationHandler class:

public static class ResponseInvocationHandler implements InvocationHandler {
    private final Object target;

    public ResponseInvocationHandler(final Object target) {
        this.target = target;
    }

    @Override
    @SuppressWarnings({"unchecked"})
    public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
        final Object result = method.invoke(target, args);

        if (result instanceof Observable) {
            return Observable.class.cast(result).compose(new ResponseTransformer<>());
        }

        return result;
    }
}
like image 155
Vladimir Mironov Avatar answered Oct 09 '22 20:10

Vladimir Mironov


I would suggest a different approach.

You will need to implement a custom OkHttp client with custom Interceptor.

        OkHttpClient client = new OkHttpClient();
        client.interceptors().add(new MyInterceptor());
        mAdapter = new RestAdapter.Builder().setEndpoint(Consts.ENDPOINT).setClient(new OkClient(client))
                .setLogLevel(RestAdapter.LogLevel.BASIC).build();

In your interceptor depending on the code returned you can proceed normally or throw an exception.

Something like this:

public class MyInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());

        if(response.code() == 0) {
            throw new RuntimeException("Something went wrong!");
        }

        return response;
    }
}
like image 40
LordRaydenMK Avatar answered Oct 09 '22 20:10

LordRaydenMK