REST API on Android Made Simple or: How I Learned to Stop Worrying and Love the RxJava

If you’re an Android developer you probably remember, with no small amount of nostalgia, the first time you encountered the NetworkingOnMainThread exception. Asynchronicity is one of the main driving forces behind the Android platform, and yet, the libraries provided by the SDK always were somewhat lacking when it comes to dealing with it. Combine that with tedious work of manually dealing with any kind of RESTful API, and you soon find yourself in a hell made of disjointed code fragments, repeated loops and confusing callbacks.

This article will show you, step-by-step, how to make a simple API call on Android the easy way — using 3rd party libraries. In the first part we’ll deal with the API call itself using Retrofit and Gson. In the second part we’ll see how we can make our lives even simpler and deal with asynchronicity in a concise and elegant way using RxJava.

Disclaimer — This article does not cover the subject of RESTful APIs in great detail. It is not an in-depth Retrofit or RxJava tutorial. Rather, it’s a short and simple guide on dealing with an API request using said two libraries in conjunction.

For this part we will be using Retrofit2 and it’s Gson adapter so let’s start by adding their respective dependencies to our build.gradle file (also add the RxJava dependencies, as we’ll need them later):

implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
implementation 'io.reactivex.rxjava2:rxjava:2.1.13'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'

By the way, let’s not forget to add the Internet permission to our manifest file:

<uses-permission android:name="android.permission.INTERNET" />

Retrofit makes it really easy to consume APIs by doing a lot of heavy lifting for us. It won’t do all the work by itself though, so let’s see what we have to do to get things rolling:

  1. Configure and build Retrofit

We use builder pattern to configure and build a Retrofit instance. For more details about Retrofit configuration see the documentation, for now just make sure to include API’s base URL, GsonConverterFactory and RxJava2CallAdapterFactory. These are necessary if we want to use Retrofit with Gson and RxJava, as we will be doing in this example.

Retrofit retrofit = new Retrofit.Builder()
.baseUrl(https://api.themoviedb.org/3/)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();

If you’re more familiar with some other JSON parsing library or just don’t feel like using Gson you can check out the list of supported converters here.

2. Create a model class

Gson uses model (or POJO) classes to convert JSON data to objects. One thing to remember when it comes to model classes is this: they have to reflect the structure of JSON response we want them to represent. So let’s say we want to get some data about a famous movie director from the popular and free-to-use The Movie Database API — if we consult the API documentation, we can see that our JSON response would look something like this (shortened for the sake of simplicity):

{
"id": 287,
"imdb_id": "nm0000040",
"name": "Stanley Kubrick",
"place_of_birth": "New York City - USA"
}

We could write the model class by hand but there are many convenient converter tools to save us some precious time, for example this one. Copy the response code, paste it into the converter, select Gson, click on preview and, if all went well, what comes out should very much resemble a simple POJO class:

public class Person {    @Expose
@SerializedName("id")
private Integer id;
@Expose
@SerializedName("imdb_id")
private String imdbId;
@Expose
@SerializedName("name")
private String name;
@Expose
@SerializedName("place_of_birth")
private String placeOfBirth;

// bunch of boring getters and setters
}

As we can see, the fields represent our JSON response. Annotations are not necessary for Gson to work but they can come in handy if we want to be specific or keep the camel case naming. Just keep in mind that if a variable name doesn’t match the JSON key of the corresponding key/value pair Gson won’t recognize it and it will be ignored. That’s where the @SerializedName comes in — as long as we specify it correctly we can name the variable however we want. And that’s that for the model class. Let’s see how to actually make use of Retrofit to specify our requests.

3. Create an ApiService interface

To use Retrofit we simply make an interface to house our API requests (name of the interface is arbitrary, but it’s a good practice to end it with service). Then we use annotations from the Retrofit library to annotate interface methods with specifics of the each request. Going back to the API documentation, we find that our request should look something like this:

public interface ApiService {    @GET("person/{person_id}")
Single<Person> getPersonData(@Path("person_id") int personId,
@Query("api_key") String apiKey);
}

Let’s take a closer look at this piece of code and see what it means:

  • @GET tells Retrofit what kind of request this is, followed by the coresponding API endpoint. It should be obtained from the API documentation
  • Single<Person> is an RxJava constuct and we’ll deal with it later, for now just keep in mind that it requires a type, which must be of the model class we are fetching with the request (in this case, Person)
  • specific method name is not required, but we shouldn’t stray away from good naming conventions
  • @Path and @Query annotations are used on method parameters to specify path and queries used to build the request

Put simply, when this method is called, Retrofit will generate a request and forward it to the API. If successful, it will use the Gson converter to convert the response to the object of our model class. Under the hood, the generated request looks roughly like this:

https://api.themoviedb.org/3/person/{person_id}?api_key=<<api_key>>&language=en-US

For those still uncertain about what goes where, here is a quick recap:

  • https://api.themoviedb.org/3/ - this is the base URL
  • person/{person_id} - this is the endpoint followed by a path
  • ? - a question mark indicates the beginning of queries
  • api_key=<<api_key>> - this is a query, in this case, for an api key
  • & - more queries incoming
  • language=en-US - this too is a query, but we left it at default

And since we annotated the parameters with @Path(“person_id”) and @Query(“api_key”), the arguments we pass when calling the method will replace {person_id} and <<api_key>>.

4. Hook up the service with Retrofit and make the request

All that’s left to do is to actually make the request. To do that we must first create an instance of the ApiService using the Retrofit object we created at the beginning.

// create an instance of the ApiService
ApiService apiService = retrofit.create(ApiService.class);
// make a request by calling the corresponding method
Single<Person> person = apiService.getPersonData(personId, API_KEY);

That last line of code is where we actually make a request to get the data we need, wrapped in a Single object. But what is a Single? And how does it help us deal with the asynchronous nature of network calls? We answer these questions, and more, in the second part.

We’ve all heard of AsyncTask. Most of us have used it at some point in our developer careers. The unfortunate few still use it today. But almost everybody hates it. It’s overly verbose, clumsy, prone to memory leaks and bugs, and yet, in lack of a better tool, many online tutorials still use it in dealing with all that background work Android platform is ripe with.

In this part we’ll see how to ditch the AsyncTask altogether in favor of RxJava, and in the process, save yourself a lot of time and avoid headache. Keep in mind that this is not an RxJava tutorial and we will only scratch the surface of what RxJava is capable of — working with Retrofit to ensure a simple and elegant solution to asynchronous network calls.

In the last part we left off at this line of code:

Single<Person> person = apiService.getPersonData(personId, API_KEY);

So what is a Single? Without making this an RxJava tutorial, let’s say it allows us to recieve a single set of data from the API, do some stuff with it in the background, and, when done, present it to the user — all that in a few lines of code. Internally, it is based on the observer pattern and some functional programming goodness, with data being pushed to interested observers at the moment of subscription.

To receive the data we only have to subscribe to a Single, calling the subscribe() method on it and passing a SingleObserver as an argument. SingleObserver is an interface containing 3 methods. By subscribing we ensure that the data is pushed when ready, and is passed to us in onSuccess() method. That is, if request was successfully completed — if not, onError() is invoked, enabling us to deal with the exception as we see fit.

Single<Person> person = apiService.getPersonData(personId, API_KEY)
.subscribe(new SingleObserver<Person>() {
@Override
public void onSubscribe(Disposable d) {
// we'll come back to this in a moment
}

@Override
public void onSuccess(Person person) {
// data is ready and we can update the UI
}
@Override
public void onError(Throwable e) {
// oops, we best show some error message
}
});

But what about the onSubscribe() method? It is called in the moment of subscription and it can serve us to prevent potential memory leaks. It gives us access to a Disposable object, which is just a fancy name for the reference to the connection we established between our Single and a SingleObserver — the subscription. That subscription can be disposed with a simple method call, thus preventing those nasty situations when, for example, rotating the device in the middle of a running background task causes a memory leak. What we want to do is the following:

  • first we create a CompositeDisposable object which acts as a container for disposables (think Recycle Bin) and add our Disposable to it in the onSubscribe() method:
@Override
public void onSubscribe(Disposable d) {
compositeDisposable.add(d);
}
  • then we simply call the dispose() method on the CompositeDisposable in the appropriate lifecycle method:
@Override
protected void onDestroy() {
if (!compositeDisposable.isDisposed()) {
compositeDisposable.dispose();
}
super.onDestroy();
}

We’re almost there — we got the data we wanted and we did our chores of cleaning up any potential mess. But we still haven’t dealt with asynchronicity. NetworkingOnMainThread exception will still be thrown. Now comes the hard part, you must be thinking? Not really. Thanks to the RxAndroid library, RxJava can be made aware of Android threads, so all we have to do is add two more lines of code:

Single<Person> person = apiService.getPersonData(personId, API_KEY);
person.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(...);

As easy as that, with subscribeOn() we told RxJava to do all the work on the background(io) thread. When the work is done and our data is ready, observeOn() ensures that onSuccess() or onError() are called on the main thread.

While we’re at it, let’s explore RxJava some more. Let’s say, for the sake of example, that we want to make some more use of the API and fetch a list of movies related to our Person. We would have to make another API request following the same steps — create a model class and add another method to the ApiService, with the corresponding API endpoint. You’re probably thinking we have to repeat the steps with RxJava too, then think of a complex logic to deal with acychronicity and time the callbacks so we can update the UI at just the right time? Not necessarily.

Enter RxJava Operators. Operators are methods that allow us to, well, operate on our data and manipulate the way it’s being pushed to us. They can be called on Single objects and chained just like we did before. The list of available operators and their uses is huge and you can find it here.

In our case, wouldn’t it be nice if we could simply join the Person with the list of movies in the background and get them at the same time, as a single set of data? Luckily, there is just the Operator to meet our needs — Zip. Presuming the request for movies returns a list of Movie objects, we can add a List<Movie> field to the Person class and do the following:

Single<Person> person = apiService.getPersonData(personId, API_KEY);
person.zipWith(apiService.getMovies(personId),
(person, movies) -> {
person.setMovies(movies);
return person;
})

.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()
.subscribe(...);

If it seems confusing at first glance, don’t worry, it usually is. It takes some time and practice to get acquainted with the syntax, and knowing your lambda expressions helps a lot. What we did is call the zipWith() method on the Single wrapping a Person request — zipWith() accepts 2 arguments — another Single (the one wrapping a list of movies) and a lambda expression which gives us access to both person and movie list data, since the Zip operator joins two separate data sources. In the lambda we assigned the movie list to the person. Now, when all the background work is done, onSuccess() will give us a Person object together with the list of movies we assigned to it.

Again, make sure to check out the list of operators, as there are many of them, and they can be used in a myriad of ways to achieve most wonderful things.

And that about covers it. We did what we set out to do, without going to much into the inner workings of any of the used libraries. Both Retrofit and RxJava are amazing tools and this article offers only a glimpse at the tip of the iceberg of what they’re capable of. Even so, if it managed to pique your interest and motivate you to dig below the surface we will consider the time spent writing it time well spent indeed.

If you want to get a better insight into how this stuff works in a simple MVP app check out my Github repo.

If you’re interested in the Room Persistence Library and how to make it work with RxJava, you can check out my next article on the subject:

Also, feel free to post any questions, comments, suggestions or corrections you may have, or even better — visit the official sites of RxJava and Retrofit and get more immersed into their amazing worlds. You’ll love it, and you’ll love yourself for doing so.

Self-taught Android developer | Law school graduate | Bookworm