0

Context:

I've an Android App that is using Retrofit2 with RxJava to handle the server-side responses. The server-side knows how to handle etags so the server can return the request responses as 200 (Success) or 304 (Not Modified). The thing is that we want to be able to handle those responses differently in the Subscriber#onNext(). For example we have a local database implemented with SQLlite, so when a server-side request returns 200 we want to store that response in our local database, but if the request response returns 304 we don't want to do it, because it's the same data that we already have stored in our database and it's a waste of time saving it again.

We're using a Fernando Cejas Clean Architecture as our App architecture.

Our Interactors are using Repositories, which are using the Retrofit layer to perform the server-side request and returns the data like rx.Observable<List<SomeModelClass>>. Our Interactors are also using DAOs that returns database data like rx.Observable<List<SomeModelClass>>. So by doing this we can do some pretty cools stuff such as:

public void GetDogsInteractor {
    ...
    public void execute (Callback callback) {
        mDogsRepository.getAllDogs()
            .onErrorNext(ObservableUtils.ifExceptionIs(SomeServerSideException.class, mDogsDao.getDatabaseDogs()))
            .subscribe(
                new Subscriber<List<Dog>>() {
                    ...
                    public void onError() {
                        callback.showError("Sorry no doggos on server-side nor database");
                    }

                    public void onNext(List<Dog> dogs) {
                        callback.showDogsOnUI(dogs);
                    }
                    ...
                }
            );
    }
    ...
}

As you can see having the same interface for both our Repositories and our DAOs makes pretty easily plug one with another in case that something goes wrong.

The workaround:

Let's imagine that we want to store all the Dog objects in the database only if the request response is 200, but we don't if the request response is 304. The only solution that I could came with is filtering the server-side responses and throwing an Exception if the response is a 304, that way I can handle 200 request in the Subscriber#onNext() and 304s in the Subscriber#onError(). This is how the Repository internal implementation looks:

public class DogRepository {
    ...
    public rx.Observable<List<Dog>> getAllDogs() {
        mRetrofitService.getAllDogsFromServerSide() //Returns rx.Observable<Response<List<Dog>>>
            .flatMap(new Func1<Response<List<Dog>>, Observable<List<Dog>>>() {
                @Override
                public Observable<List<Dog>> call(Response<List<Dog>> serverSideResponse) {
                    //Check the response code, if its 200 just pass the response through, if its 304 throw an exception
                    if (serverSideResponse.getCode() == 304) {
                        return Observable.error(new NotModifiedException(serverSideResponse.getBody()));
                    } else {
                        return Observable.just(serverSideResponse.getBody());
                    }
                }
            })
    }
    ...
}

Then I create a custom subscriber that overrides the Subscriber#onError() method:

public class MySubscriber<T> extends rx.Subscriber<T> {
    ...
    public void onError(Throwable e) {
        if (e instance of NotModifiedException) {
            onNotModified(((NotModifiedException) e).getBody()); //onNotModified() is a hook method
        }
    }

    public void onNotModified(T model) {
        //Optional to implement
    }
    ...
}

Then finally I use it this way:

public void GetDogsInteractor {
    ...
    public void execute (Callback callback) {
        mDogsRepository.getAllDogs()
            .onErrorNext(ObservableUtils.ifExceptionIs(SomeServerSideException.class, mDogsDao.getDatabaseDogs()))
            .subscribe(
                new MySubscriber<List<Dog>>() {
                    ...
                    public void onError() {
                        callback.showError("Sorry no doggos on server-side nor database");
                    }

                    public void onNotModified(List<Dog> dogs) {
                        callback.showDogsOnUI(dogs);
                    }

                    public void onNext(List<Dog> dogs) {
                        mDogDao.saveAllDogs(dogs)
                        callback.showDogsOnUI(dogs);
                    }
                    ...
                }
            );
    }
    ...
}

This way I can even hook some other stream with the NotModifiedException using onErrorNext().

The problem:

Some of my coworkers are not quite happy of handling this throwing exceptions and handling the 304 responses as somethign "unexpected". Can't blame them, this is not the nicest solution. But I tried many things and this is the only approach that worked. My question is; Is there some way of achieving this without using Exceptions? Some cleaner way?

I simplify this as much as I could, but let me know if there's something that you don't get.

1 Answer 1

1

I use a Data<T> wrapper class for passing things between the different layers of the application. For your case I'd have something like:

public class Data<T> {

    public static final int LOADING = 0;
    public static final int SUCCESS = 1;
    public static final int NO_CHANGE = 2;
    public static final int ERROR = 3;

    public final int status;
    public final T result;
    public final DataError error;

    public Data(int status, T result, DataError error) {
        this.status = status;
        this.result = result;
        this.error = error;
    }
}

You could even add static syntactic sugar constructors:

public static <R> Data<R> success(R result) {
    return new Data<>(SUCCESS, result, null);
}

public static <R> Data<R> noChange(R result) {
    return new Data<>(NO_CHANGE, result , null);
}

Then just change the return type of your Repository method to use Data and just map rather than flatMap:

public Observable<Data<List<Dog>>> getAllDogs() {
    mRetrofitService.getAllDogsFromServerSide()
        .map(new Func1<Response<List<Dog>>, Data<List<Dog>>>() {
            @Override
            public Data<List<Dog>> call(Response<List<Dog>> serverSideResponse) {

                if (serverSideResponse.getCode() == 304) {
                    return Data.noChange(serverSideResponse.getBody());
                } else {
                    return Data.success(serverSideResponse.getBody());
                }
            }
        })
}

Now in your Interactor onNext you can just do a simple check of the status:

public void onNext(Data<List<Dog>> data) {

    if (result.status == SUCCESS)
        mDogDao.saveAllDogs(data.result)
        callback.showDogsOnUI(data.result);
        return;
    }

    if (result.status == NO_CHANGE) {
        callback.showDogsOnUI(data.result);
        return;
    }

    if (result.status == ERROR) {
        //...
    }
}

Using a Data wrapper class allows you to still pass required metadata through the different layers of your application whilst still not leaking implementation details.

Sign up to request clarification or add additional context in comments.

3 Comments

Ok is a good idea, but my DAOs should start returning Data<List<Dog>> right? That's kinda an issue because I have several DAOs across my App and I can't refactor all of them. I'd like to keep the same interfaces for DAOs and Repositories. Both of them returning rx.Observable<List<Dog>>
It would be helpful but not entirely compulsory. I'd actually say your Repository should be a facade in front of your Http/Db classes rather than the implementation of Http...but that's a separate matter. Just stick an adapter class in front of your DAO which converts to Data<T> until you have time to refactor more thoroughly.
After struggling a long time with this I ended up implementing your solution, mostly because I have several data sources and I must keep track for all of them. I have the network layer with Retrofit which can return a Data<> with status 200 or 304, I have a Database Data<> and I have a memory cache. So thanks for your time!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.