Android MVP, Retrofit & Rx
In this article we’ll discuss the benefits of using Rx over Callbacks in (but not limited to) a Model-View-Presenter application architecture.
It is assumed you are already familiar with Retrofit and the concepts of Android MVP and RxJava.
You can find the example app from which the code examples in this article are taken over here on our GitHub repo.
The Retrofit Part
(Using Retrofit 1.9.0, we will discuss Retrofit 2.0.0 in a future article.)
For the sake of the example we’re using the GitHub api call to https://api.github.com/users/google/repos
to get a list of Google’s repos.
public interface GitHubAPI {
String URI = "https://api.github.com";
@GET("/users/google/repos")
List<githubrepo> getReposSync();
@GET("/users/google/repos")
void getReposAsync(Callback<list<githubrepo>> result);
@GET("/users/google/repos")
Observable<list<githubrepo>> getReposRx();
}
Now let’s go over these methods one-by-one…
1 - Synchronously
List<GitHubRepo> getReposSync();
While there is nothing wrong with using the Retrofit synchronous api on a separate thread, using this directly on the main thread is a no-no…
This blocks the UI until the request execution has finished. From Ice Cream Sandwich onwards, Android’s Strict Mode is going to keep us from being that silly… (and it will throw a NetworkOnMainThreadException).
2 - Asynchronously
void getReposAsync(Callback<List<GitHubRepo>> result);
This is the most commonly used way to perform the request asynchronously. You will handle the result in the success / failure methods of a retrofit.Callback<T>
implementation.
3 - Asynchronously with RxJava
Observable<List<GitHubRepo>> getReposRx();
Retrofit supports Rx out-of-the-box. Awesome-sauce…
The MVP Part
Our interfaces RepoPresenter
and RepoView
are straightforward enough.
The View (in this case an Activity) isn’t aware of how the result is handled. The Presenter is directing the show (pun intended).
There are a few good libraries that facilitate implementing an MVP architecture, and the one we’re using in this example is Mosby.
Presenter implementation options
Before we get to the awesome-sauce, let’s first discuss an implementation in our Presenter to fetch the results asynchronously with the Callback.
By-the-book
public void loadRepoList
gitHubAPI.getReposAsync(new Callback<list<githubrepo>>() {
@Override
public void success(List<githubrepo> gitHubRepos, Response response) {
getView().showRepos(gitHubRepos);
}
@Override
public void failure(RetrofitError e) {
getView().showMessage(e.getMessage());
}
});
}
Great! That works and you go home, but in the morning the statistics tell you there were quite some crashes of you app.
Consider the following scenario: the request is (pick one) taking more time the user can handle / user gets a call / user switches apps and the OS decides to destroy the Activity to free up resources, before the result was processed…
By the time the success or failure methods are invoked, the View has gone and the app will crash (in the background) because of a NullPointerException…
You will actually be warned of this by the compiler if your Presenter extends Mosby’s MVPBasePresenter, as getView() has a @Nullable annotation (uses a weak reference on the view).
Improved
Well… you could solve this by using try / catch statements.
public void loadRepoList() {
gitHubAPI.getReposAsync(new Callback < List < GitHubRepo >> () {
@Override
public void success(List < GitHubRepo > gitHubRepos, Response response) {
try {
getView().showRepos(gitHubRepos);
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void failure(RetrofitError e) {
try {
getView().showMessage(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
Or we could check MVPBasePresenter.isViewAttached()
.
public void loadRepoList() {
gitHubAPI.getReposAsync(new Callback < List < GitHubRepo >> () {
@Override
public void success(List < GitHubRepo > gitHubRepos, Response response) {
if (isViewAttached()) {
getView().showRepos(gitHubRepos);
}
}
@Override
public void failure(RetrofitError e) {
if (isViewAttached()) {
getView().showMessage(e.getMessage());
}
}
});
}
Or… we could avoid these checks which makes our code simpler.
Observable Subscribe / Unsubscribe
Using Rx, we have a very clean approach to have our request executed on an IO thread and to subscribe / unsubscribe depending on how interested we are in the result on the main (UI) thread. Rx has built-in mechanisms using a Transformer to achieve this, which can be applied through Observable.compose()
The Presenter implementation below is already making use of some under-the-hood stuff we’ll explain next:
public void loadRepoList() {
new RxIOSubscription < List < GitHubRepo >> ().add(
gitHubAPI.getReposRx(),
new Subscriber < List < GitHubRepo >> () {
@Override
public void onNext(List < GitHubRepo > gitHubRepos) {
getView().showRepos(gitHubRepos);
}
@Override
public void onCompleted() {
getView().showMessage("Yihar!");
}
@Override
public void onError(Throwable e) {
getView().showMessage(e.getMessage());
}
}
);
}
RxIOSubscription.add(...)
takes Observable
and Subscriber
arguments and makes sure a couple of things happen:
- subscribe on
Schedulers.io()
- observe on
AndroidSchedulers.mainThread()
using RxAndroid (Have a look at SimpleRxUtil) - after scheduling, that observable is subscribed to and added to a
CompositeSubscription
detachView(...)
is called when the Activity is destroyed, which unsubscribes all…
Our code is now simplified again without the risk of nullpointer exceptions.
Conclusion
Retrofit in combination with RxJava and RxAndroid is an awesome stack to use behind your Presenter pattern… this article covers a very specific use-case but the possibilities go far beyond this example.
Notes
- An insightful article using a similar approach can be found here.
- More on Observable.compose() and Transformers.
- Mosby used to have its own MvpRxPresenter baseclass (which worked a little differently), but in version 2 all third party dependencies were removed, promoting to write your own base classes.