During the development of Open Event Orga Application (Github Repo), we have strived to minimize duplicate code wherever possible and make the wrappers and containers around data and views intelligent and generic. When it comes to loading the data into views, there are several common interactions and behaviours that need to be replicated in each controller (or presenter in case of MVP architecture as used in our project). These interactions involve common ceremony around data loading and setting patterns and should be considered as boilerplate code. Let’s look at some of the common interactions on views:
Loading Data
While loading data, there are 3 scenarios to be considered:
- Data loading succeeded – Pass the data to view
- Data loading failed – Show appropriate error message
- Show progress bar on starting of the data loading and hide when completed
If instead of loading a single object, we load a list of them, then the view may be emptiable, meaning you’ll have to show the empty view if there are no items.
Additionally, there may be a success message too, and if we are refreshing the data, there will be a refresh complete message as well.
These use cases present in each of the presenter cause a lot of duplication and can be easily handled by using Transformers from RxJava to compose common scenarios on views. Let’s see how we achieved it.
Generify the Views
The first step in reducing repetition in code is to use Generic classes. And as the views used in Presenters can be any class such as Activity or Fragment, we need to create some interfaces which will be implemented by these classes so that the functionality can be implementation agnostic. We broke these scenarios into common uses and created disjoint interfaces such that there is little to no dependency between each one of these contracts. This ensures that they can be extended to more contracts in future and can be used in any View without the need to break them down further. When designing contracts, we should always try to achieve fundamental blocks of building an API rather than making a big complete contract to be filled by classes. The latter pattern makes it hard for this contract to be generally used in all classes as people will refrain from implementing all its methods for a small functionality and just write their own function for it. If there is a need for a class to make use of a huge contract, we can still break it into components and require their composition using Java Generics, which we have done in our Transformers.
First, let’s see our contracts. Remember that the names of these Contracts are opinionated and up to the developer. There is no rule in naming interfaces, although adjectives are preferred as they clearly denote that it is an interface describing a particular behavior and not a concrete class:
Emptiable
A view which contains a list of items and thus can be empty
public interface Emptiable<T> { void showResults(List<T> items); void showEmptyView(boolean show); }
Erroneous
A view that can show an error message on failure of loading data
public interface Erroneous { void showError(String error); }
ItemResult
A view that contains a single object as data
public interface ItemResult<T> { void showResult(T item); }
Progressive
A view that can show and hide a progress bar while loading data
public interface Progressive { void showProgress(boolean show); }
Note that even though Progressive view can only be the one which is either ItemResult or Emptiable as they are the ones containing any data, but we have decoupled it, making it possible for a view to load data without progress or show progress for any other implementation other than loading data.
Refreshable
A view that can be refreshed and show the refresh complete message
public interface Refreshable { void onRefreshComplete(); }
There should also be a method for refresh failure, but the app is under development and will be added soon
Successful
A view that can show a success message
public interface Successful { void onSuccess(String message); }
Implementation
Now, we will implement the Observable Transformers for these contracts
Erroneous
public static <T, V extends Erroneous> ObservableTransformer<T, T> erroneous(V view) { return observable -> observable .doOnError(throwable -> view.showError(throwable.getMessage())); }
We simply call showError on a view implementing Erroneous on the call of doOnError of the Observable
Progressive
private static <T, V extends Progressive> ObservableTransformer<T, T> progressive(V view) { return observable -> observable .doOnSubscribe(disposable -> view.showProgress(true)) .doFinally(() -> view.showProgress(false)); }
Here we show the progress when the observable is subscribed and finally, we hide it whether it succeeded or failed
ItemResult
public static <T, V extends ItemResult<T>> ObservableTransformer<T, T> result(V view) { return observable -> observable.doOnNext(view::showResult); }
We call showResult on call of onNext
Refreshable
private static <T, V extends Refreshable> ObservableTransformer<T, T> refreshable(V view, boolean forceReload) { return observable -> observable.doFinally(() -> { if (forceReload) view.onRefreshComplete(); }); }
As we only refresh a view if it is a forceReload, so we check it before calling onRefreshComplete
Emptiable
public static <T, V extends Emptiable<T>> SingleTransformer<List<T>, List<T>> emptiable(V view, List<T> items) { return observable -> observable .doOnSubscribe(disposable -> view.showEmptyView(false)) .doOnSuccess(list -> { items.clear(); items.addAll(list); view.showResults(items); }) .doFinally(() -> view.showEmptyView(items.isEmpty())); }
Here we hide the empty view on start of the loading of data and finally we show it if the items are empty. Also, since we keep only one copy of a final list variable which is also used in view along with the presenter, we clear and add all items in that variable and call showResults on the view
Bonus: You can also merge the functions for composite usage as mentioned above like this
public static <T, V extends Progressive & Erroneous> ObservableTransformer<T, T> progressiveErroneous(V view) { return observable -> observable .compose(progressive(view)) .compose(erroneous(view)); } public static <T, V extends Progressive & Erroneous & ItemResult<T>> ObservableTransformer<T, T> progressiveErroneousResult(V view) { return observable -> observable .compose(progressiveErroneous(view)) .compose(result(view)); }
Usage
Finally we use the above transformers
eventsDataRepository .getEvents(forceReload) .compose(dispose(getDisposable())) .compose(progressiveErroneousRefresh(getView(), forceReload)) .toSortedList() .compose(emptiable(getView(), events)) .subscribe(Logger::logSuccess, Logger::logError);
To give you an idea of what we have accomplished here, this is how we did the same before adding transformers
eventsView.showProgressBar(true); eventsView.showEmptyView(false); getDisposable().add(eventsDataRepository .getEvents(forceReload) .toSortedList() .subscribeOn(Schedulers.computation()) .subscribe(events -> { if(eventsView == null) return; eventsView.showEvents(events); isListEmpty = events.size() == 0; hideProgress(forceReload); }, throwable -> { if(eventsView == null) return; eventsView.showEventError(throwable.getMessage()); hideProgress(forceReload); }));
Sure looks ugly as compared to the current solution.
Note that if you don’t provide the error handler in subscribe method of the observable, it will throw an onErrorNotImplemented exception even if you have added a doOnError side effect
Here are some resources related to RxJava Transformers:
- Chaining operators via Transformers
http://blog.danlew.net/2015/03/02/dont-break-the-chain/ - Implementing custom Operators
https://github.com/ReactiveX/RxJava/wiki/Implementing-Your-Own-Operators - ObservableTransformer API
http://reactivex.io/RxJava/javadoc/rx/Observable.Transformer.html