Retrofit and RxJava, Android multi-threaded REST requests
Introduction
Making a RESTful API call in software is as common-place as eating cereal at breakfast in the modern era. The Android library currently does not have an elegant solution to perform this simple task. There are two key tasks that you want to abstract
- Write async processing code to push the REST call off the main thread
- Concise reusable code to make HTTP requests and parse the result
The standard procedures are costly in terms of code readability and maintainability. You either need to write AsyncTask or spin up a Thread/Runnable to perform async processing. And you need to deal with Java datastream to parse contents from HttpClient. Of course you need to write a page of try/catch blocks to captures all the possible errors.
Fear not! Open source community have solved these two problems for us!
- RxJava and RxAndroid libraries allow us to easily do async processing using principles of functional reactive programming
- Square's Retrofit project allows us to easily handle HTTP requests and responses
The two libraries acting in tandem works like magic!
The Example
p.s. all the code used in this post are on my github: https://github.com/ruler88/GithubDemo
I wrote an Android app that makes requests to the Github API to retrieve public repo count and blog URL from various Github accounts. The result is displayed in a cute cardview UI that I built based off of this blog.
The FETCH button kicks off a series of HTTP requests to Github REST API. The requests are built via Retrofit. The calls are made asynchronously through RxJava. Notice that the cards are laid out in different order each time the button is pressed. You are seeing async threading at work! Each card is rendered when the result comes back from a GET request.
The Setup
Let's take care of the depency injection for retrofit and RxJava/RxAndroid:
dependencies {
compile 'io.reactivex:rxjava:1.0.+'
compile 'io.reactivex:rxandroid:0.23.+'
compile 'com.squareup.retrofit:retrofit:1.9.+'
}
Don't forget Android App Permissions in AndroidManifest:
<uses-permission android:name="android.permission.INTERNET" />
Retrofit Service/Model
Retrofit uses a Java interface as proxy for the REST API calls. All we have to do is to define the @GET method and the url/path. The following code makes a GET request to the Github URL and returns an Observable. The Observable object is used by RxJava to do stream processing (I'll explain this later).
public interface GithubService {
String SERVICE_ENDPOINT = "https://api.github.com";
@GET("/users/{login}")
Observable<Github> getUser(@Path("login") String login);
}
Hang on! GithubService needs a RestAdapter implementation. In the spirit of good programming practice, I created a generic factory class to do the implementation:
static <T> T createRetrofitService(final Class<T> clazz, final String endPoint) {
final RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint(endPoint)
.build();
T service = restAdapter.create(clazz);
return service;
}
The Github REST API returns the following JSON. You can try it yourself!
curl https://api.github.com/users/linkedin
{
"login": "linkedin",
"blog": "http://engineering.linkedin.com",
"public_repos": 73,
//...truncated JSON
}
We will define the model in a separate Java file. The field variables in the model are automatically parsed from the JSON response. So you don't need to worry about writing the parsing code. Make sure that the variable names are exactly the same as API definition:
public class Github {
private String login;
private String blog;
private int public_repos;
public String getLogin() {
return login;
}
public String getBlog() {
return blog;
}
public int getPublicRepos() {
return public_repos;
}
}
And you are done! Other than Java's boilerplate stuff (boo), the code is very concise and readable. If you have more than one endpoint you want to access, simply add it to your service interface at little additional cost!
RxJava Async Stream
The Observable object from our GithubService streams data when it becomes available. We need to have an Subscriber (sometimes called Observer) to watch for the data stream changes. Conceptually, the Subscriber subscribes to an Observable. The following block of code performs the entire process described.
GithubService service = ServiceFactory.createRetrofitService(GithubService.class, GithubService.SERVICE_ENDPOINT);
for(String login : Data.githubList) {
service.getUser(login)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<Github>() {
@Override
public final void onCompleted() {
// do nothing
}
@Override
public final void onError(Throwable e) {
Log.e("GithubDemo", e.getMessage());
}
@Override
public final void onNext(Github response) {
mCardAdapter.addData(response);
}
});
}
That was probably a bit confusing. Let's break the code down line by line.
service.getUser(login)
GithubService Interface has the getUser method which returns an Observable. We are chaining method calls on this Observable to get the REST call response.
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
These two lines specify that the REST call will be made in a new thread (YES!). And when the call response returns, the onNext, onError, and onComplete methods are called on the mainThread. The reason we need to call them on the mainThread is that only the mainThread can update the UI. If you have data that do not need to be displayed immediately, you would not need to observe on main thread. The difference between observeOn and subscribeOn is best explained in this stackoverflow.
new Subscriber<Github>() {
@Override
public final void onCompleted() {
// do nothing
}
@Override
public final void onError(Throwable e) {
Log.e("GithubDemo", e.getMessage());
}
@Override
public final void onNext(Github response) {
mCardAdapter.addData(response);
}
}
This Subscriber responds to the Observable's stream. onNext is called when the REST call receives data. In this Github example, there is only 1 item, so it is only called once. When the REST response is a list, the code can be called each time an item is received. onComplete and onError behave exactly as the name implies.
We are done
Viola! We have just made our non-blocking HTTP calls on Android. Special thanks to the folks at Square and ReactiveX for making our lives easier!
Once you get this code working, I highly encourage you to read these two docs previously mentioned. Functional reactive programming and Observable. They are great at explaining why and how to do async programing through streaming data.
Reference:
Code on github: https://goo.gl/DGMF2F
Square Retrofit Doc: http://goo.gl/UwksBu
RxJava Doc: https://goo.gl/5AqMNi
Github API: https://goo.gl/7nsdh0
CardView/RecycleView UI Reference: http://goo.gl/stNj2J