Consuming REST API on Android or How to Make Your Developer’s Life Easier

Introduction

  If you are passionate about developing Android applications, you sure had to make a REST call to manage a resource at some point. Since making network calls is not allowed on the UI thread (the main thread which takes care of updating the UI), you had to find workarounds.

  Creating a separate thread is not an option since it lacks communication with the main thread and no updates to the UI can be made. One of the solutions is to create an AsyncTask. It allows you to perform network operations (or any other) in a background thread and update the main thread. Creating AsyncTasks to make REST calls can give one hell of a headache!

  There are some limitations when it comes to updating the UI. To access the result in the main thread, you need to either create the AsyncTask class as an inner class, provide a callback interface in the AsyncTask interface and implement it in the main class, or create a BroadcastReceiver which would announce the main thread that it finished the background operation and hand the result. Here is an example of providing a callback interface for the AsyncTask class:

public interface CallbackTypeExample<T> {
    public void onSuccess(T data);
    public void onFailure(Exception e);
}
 
public class ExampleActivity extends AppCompatActivity implements CallbackTypeExample<String> {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        AsyncTaskExample example = new AsyncTaskExample(this);
    }

    @Override
    public void onSuccess(String data) {
        Log.d("ASYNCTASK_EXAMPLE_SUCCESS", data);
    }

    @Override
    public void onFailure(Exception e) {
        Log.e("ASYNCTASK_EXAMPLE_ERROR", e.getMessage());
    }
}
 
public class AsyncTaskExample extends AsyncTask<Void, Void, String> {

    private CallbackTypeExample<String> callback;
    private Exception exception;

    public AsyncTaskExample(CallbackTypeExample callback) {
        this.callback = callback;
    }

    @Override
    protected String doInBackground(Void... voids) {
        try {
            return "Hello my friends";
        } catch (Exception e) {
            exception = e;
        }

        return null;
    }

    @Override
    protected void onPostExecute(String result) {
        if (exception == null) {
            callback.onSuccess(result);
        } else {
            callback.onFailure(exception);
        }
    }
}
 

  Writing any of these solutions takes quite some time, it is prone to error and possibly, introduces a lot of boilerplate code. Luckily, you can use the solution provided by Retrofit library and RxAndroid, an extension of RxJava, which is a Java VM implementation of ReactiveX, a library to compose asynchronous and event-based programs using observable sequences.

Using Retrofit

  Some introductory words about Retrofit. This is a REST client, which can be used in Java and Android, to retrieve or upload some data in a certain format (JSON, XML) using a REST-based web service.

  Retrofit allows you to use converters for processing the required data format (Gson/Jackson – JSON converters, or any other custom converter in case you use a different data format, for example, XML). For Http Requests, Retrofit uses the OkHttp library.

  Before showing how to use RxAndroid, we will implement some network calls using Retrofit only. To achieve this, we need three things:

  1. A model class which will represent our data. A simple POJO class will serve the purpose, it will “model” our JSON. Let it be simply a ToDo class.
  2. Interface(s) which will define the possible HTTP calls to the REST API, aka API Services.
  3. A class that will return a Retrofit instance, which will allow us to use the created interfaces and make our calls.

  Let’s now create the Android project and add the following dependencies to build.gradle file:

implementation group: 'com.square.retrofit2', name: 'retrofit', version: '2.5.0'
implementation group: 'com.square.retrofit2', name: 'converter-gson', version: '2.5.0'
implementation group: 'com.square.retrofit2', name: 'adapter-rxjava2', version: '2.5.0'
implementation group: 'io.reactivex.rxjava2', name: 'rxjava', version: '2.2.8'
implementation group: 'io.reactivex.rxjava2', name: 'rxandroid', version: '2.1.1'
 

  Next, we’ll create the ToDo model class:

public class ToDo {

    @SerializedName("label")
    private String label;

    @SerializedName("task")
    private String task;

    public ToDo() {}

    public ToDo(String label, String task) {
        this.label = label;
        this.task = task;
    }

    public String getLabel() {
        return label;
    }

    public void setLabel(String label) {
        this.label = label;
    }

    public String getTask() {
        return task;
    }

    public void setTask(String task) {
        this.task = task;
    }
}
 

  The fields of this class are annotated with @SerializedName. The parameter (value) of this annotation indicates that this member should be serialized to JSON with the provided name. This step is optional.

  Next, we’ll create the API services using some interfaces.

  To define the API calls, we need to create an interface. A method inside the interface corresponds to an API call. Let’s create an interface and look at an example. BTW, one of the good practices states that it’s good to have an interface for every accessible resource.

public interface ToDoAPI {

    @GET("/todos")
    Call<List<ToDo>> findAll();
}
 

  To specify the request type and relative URL (endpoint), I use HTTP annotations. In the example above, I’ve created a method which will make a GET request with the relative URL “todos” when called. The return value is a Call object which wraps the type of the expected result, in our case, a list of ToDos.

  The HTTP annotation allows us to add parameters and query parameters to the URL. Here is an example:

public interface ToDoAPI {

    @GET("/todos")
    Call<List<ToDo>> findAll();

    /* Parameter in URL */
    @GET("/todos/{id}")
    Call<ToDo> findById(@Path("id") String id);

    /* Query parameter in URL */
    @GET("/todos?")
    Call<ToDo> findByLabel(@Query("label") String label);
}
 

  We’ve used a replacement block in our relative URL by introducing the @Path annotation in method’s parameters. When making a call to this method, the replacement block will contain the value specified in the method’s parameters.

  To insert a query parameter, we added @Query annotation as a method parameter. It specifies the name and the value of the parameter, which is automatically added at the end of the relative URL.

  We can also add a request body using @Body annotation. The content can be any serializable object. This is often used when making POST calls:

public interface ToDoAPI {

    @GET("/todos")
    Call<List<ToDo>> findAll();

    /* Parameter in URL */
    @GET("/todos/{id}")
    Call<ToDo> findById(@Path("id") String id);

    /* Query parameter in URL */
    @GET("/todos?")
    Call<ToDo> findByLabel(@Query("label") String label);

    @POST("/todos")
    Call<ToDo> create(@Body ToDo toDo);
}
 

  Next, we need to create the class which will return a Retrofit instance:

public class ExampleAPI {
    private static final String BASE_URL = "http://192.168.88.151:80";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create())
                    .baseUrl(BASE_URL)
                    .build();
        }
        return retrofit;
    }
}
 

  As you can see, when creating a Retrofit instance using the builder, we used two methods: baseUrl() and addConverterFactory(). baseUrl() method speaks for itself, as for addConverterFactory(), it allows us to add a converter for the serialization and deserialization of the request and response body objects since the bytes are meaningless. Also, we can notice that the Retrofit instance is a singleton, we don’t want to use multiple instances. The base URL here is put as an example, you should replace it when working on your environment.

  The three tasks defined above have been achieved, so now we can continue making an actual request and get a response. For this, I’ll create a simple service, let’s call it ToDoService:

public class ToDoService {

    private ToDoAPI toDoAPI;

    public ToDoService() {
        this.toDoAPI = ExampleAPI.getClient().create(ToDoAPI.class);
    }

    public void getTodos() {
       Call<List<ToDo>> todoList = this.toDoAPI.findAll();
       todoList.enqueue(new Callback<List<ToDo>>() {
	   @Override
           public void onResponse(Call<List<ToDo>> call, Response<List<ToDo>> response) {
	       // Do something useful with the requested data.
	   }

           @Override
           public void onFailure(Call<List<ToDo>> call, Throwable T) {
	       // Do something in case of an error.
	   }
       });
    }

    public void getToDoById(String id) {
       Call<ToDo> todo = this.toDoAPI.findById();
       todo.enqueue(new Callback<ToDo>() {
	   @Override
           public void onResponse(Call<ToDo> call, Response<ToDo> response) {
	       // Do something useful with the requested data.
	   }

           @Override
           public void onFailure(Call<ToDo> call, Throwable T) {
	       // Do something in case of an error.
	   }
       });
    }
}
 

  We have two options here. We can make our class implement the Callback interface with its onResponse and onFailure methods, or we can provide a callback implementation to our enqueue method as a lambda or anonymous class, which will make the call. I’ll go for the latter since it’s a little bit more versatile.

  To make a call, we must first choose which API Service we want to use and create an instance. I’ll use the ToDoAPI interface and the Retrofit instance to create an object of the service with the create method.

  We first call the method using our newly created API service instance, to create an object of the same return type. Afterward, we call the enqueue method. Enqueue works asynchronously, it runs the request in background, and once it’s completed, it goes to one of the two methods of the Callback interface. In case we want to make synchronous requests, we can use the execute method.

  Up to this point, we’ve been working with Retrofit only. Next, we will see how to integrate RxAndroid.

  First, we have to modify the ToDoAPI interface. Since RxAndroid is based on RxJava, which in its turn is a reactive programming based library, it uses Observables and Subscribers. Our requests will now return an Observable to which we can subscribe and be notified when the request finishes:

public interface ToDoAPI {

    @GET("/todos")
    Single<List<ToDo>> findAll();

    /* Parameter in URL */
    @GET("/todos/{id}")
    Single<ToDo> findById(@Path("id") String id);

    /* Query parameter in URL */
    @GET("/todos?")
    Single<ToDo> findByLabel(@Query("label") String label);

    @POST("/todos")
    Single<ToDo> create(@Body ToDo toDo);
}
 

  For now, we’ll use Single return type which is an Observable that returns a single object. There are more other types of observables, such as Completable, Observable, Flowable and Maybe. You can use one or the other depending on your needs.

  To make Retrofit work with RxAndroid, we need to add an adapter:

public class ExampleAPI {
    private static final String BASE_URL = "http://192.168.88.151:80";
    private static Retrofit retrofit = null;

    public static Retrofit getClient() {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .addConverterFactory(GsonConverterFactory.create())
                    .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                    .baseUrl(BASE_URL)
                    .build();
        }
        return retrofit;
    }
}
 

  This will allow us to use the reactive library when making requests.

  The next step is to modify the way the requests are performed. For this, we will adjust the ToDoService class:

public class ToDoService {

    private ToDoAPI toDoAPI;

    public ToDoService() {
        this.toDoAPI = ExampleAPI.getClient().create(ToDoAPI.class);
    }

    public void getTodos() {
        this.toDoAPI.findAll()
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<List<ToDo>>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        // Do something useful
                    }

                    @Override
                    public void onSuccess(List<ToDo> toDos) {
                        // Do something useful
                    }

                    @Override
                    public void onError(Throwable e) {
                        // Do something very useful
                    }
                });
    }
}
 

  The first thing to notice is that in neither situation we create a new thread for network calls. Retrofit takes care of this when we use enqueue or execute methods.

  With RxAndroid, the request is a lot cleaner than the traditional Retrofit. We create the request method as before. To perform the request, we should subscribe to it, as it’s an Observable. First of all, we call the subscribeOn(Schedulers.io()) method. This says that the request is to be done using schedulers. When there will be a task to be executed, the scheduler will take a thread from the ThreadPool and will run the task on it. In our case, we used Schedulers.io() scheduler. It is used for network and database operations which are not CPU intensive.

  Since we want to get our data on the UI thread, we chain the next method, observeOn(). We pass a parameter which indicates that we want the result to be passed to the main thread. To start the call, we chain the last method, subscribe() and pass as a parameter a new observer, for which we can specify the implementation or implement it beforehand. In this case we used a SingleObserver and implemented 3 methods: onSubscribe(), onSuccess(), onError(). Each method speaks for itself. Because we are observing on the main thread, the implemented methods have access to the UI components.

  That’s it. This is how Retrofit and RxAndroid can spare you a lot of trouble when working on a project which should make requests to some web services.

Share this article:

Stanislav Sănduța
Java Developer