Android Series: Using Retrofit to Consume APIs

Tobiloba Adejumo
Tobiloba Adejumo

Prologue

These are the articles in this series:

There are two ways to write error-free programs; only the third one works. — Alan J. Perlis

In the previous article in this series, we learnt the basis of retrofit and talked about the importance of HTTP verbs that enables the server understand the type of request made. This article will dwell on consuming various API endpoints using HTTP verb POST.

Retrofit makes use of annotations that are in similitude with HTTP verbs to describe the type of action to be performed on the server. Some of these annotations include: GET, POST, DELETE e.t.c.

What’s the significance of these annotations in Retrofit?

Retrofit is a big fan of single responsibility principle. And as such, it tries as much as possible to uncouple actual code from implementation thus promoting better modularization of code. The whole idea why annotations are used is summarized here by Robert C. Martin.

Gather together the things that change for the same reasons. Separate those things that change for different reasons.

We are getting closer…

I know this is an old chestnut: REST is a client-server architecture. I’ve said that a lot of times already and I never want to get tired of saying it. Our REST Client for this project is Retrofit and I’ll be making use of Laragon as my local web server. Clearly, we won’t be building a REST API as it falls outside the scope of this course but simply consuming the API.

Is REST API similar to SOAP?

SOAP is an acronym that denotes: Simple Object Access Protocol. SOAP no doubt is flexible and lightweight but it relies heavily on XML. On the other hand, REST supports data structures in XML, JSON, or any other readable data structures

Hey! Your local server requires some magical touch😄.

Dummy API

I made use of my private IP address and my mobile phone as my emulator. Why?

All requests will be made to my local web server. In view of this, your mobile phone should be in the same network with your PC. Preferably, you tether the hotspot of your mobile phone and connect your PC to your mobile network.

When making use of your private IP address, you also need to include a port number in the URL. This port number acts a communication endpoint on a specific local IP address. You do not need to include a port number when making use of a public IP address.

Depending on your local server, although if you use Laragon, you launch the terminal and enter the command:

php -S ipAddress:portNumber -t public

Starting the Laragon server with a wrong private IP address shows this error message.
Starting the Laragon server with the right private IP address shows this success message.

Although both images show similar IP addresses. The first error occurred because I was connected to a public network and I made a call to a local IP address that doesn’t exist.

Finally! Let’s launch our favourite application of the year: Android Studio!

Question: Retrofit is a library. What do we do whenever we want to work with libraries?

Answer: We declare the library dependencies in our app build.gradle file.

You can get the dependencies from their official website.

compile 'com.squareup.retrofit2:retrofit:2.4.0'
compile 'com.squareup.retrofit2:converter-gson:2.4.0'

I didn’t tell you this previously. There are various types of converter used in Retrofit. Remember I said Retrofit supports myriad data types. This is because there are various converters available for the library and these converters are standalone library.

In the snippet above, we are making use of GSON converter.

Gson converter translates JSON into Java Objects and vice versa. It deserializes JSON strings into Java types and serializes Java types into JSON strings.

Next: Include Internet Permission to your Manifest file i.e app/src/main/AndroidManifest.xml file:

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

Time for serious business: We create an interface!

The interface contains all the methods that will be supported by the client. This methods are declared and not defined and each of the method maps to an endpoint (url requested for) in the server. The methods here cannot be used until a class implements this interface.

`
public interface UserClient {
   
   @POST("users/create")
   Call<UserResponse> createAccount(@Body Data data);


}

Further explanation about the created Interface!

  1. The method createAccount is declared. This method is responsible for creating a user account.
  2. It takes a Data object, and this data object is sent to the server.
  3. The annotation @Post specifies the HTTP method or verb to be used and also the @Body argument is included in the methods’ parameter so the information is sent as a Data object to the server.
  4. createAccount returns the UserResponse as a Call Object. It gets the JSON string and converts it to a Java object. Calls may be executed synchronously with execute, or asynchronously with enqueue.
Once Call object has been returned, the request is only prepared and execution is done at a later stage either synchronously or asynchronously.

Another serious business: We create our Retrofit instance!

There are two ways to doing this and I’ll show you both ways for free :)

First method:

public class Client {public static final String BASE_URL = "http://192.168.43.96:8000/api/v1/";
   public static Retrofit retrofit = null;public static Retrofit getClient() {if (retrofit == null) {
           retrofit = new Retrofit.Builder()
                   .baseUrl(BASE_URL)
                   .addConverterFactory(GsonConverterFactory.create())
                   .build();}
       return retrofit;
   }
}

Explanation

  1. I created String BASE_URL as static and final because it is constant. Declaring it as static alone can cause the variable to change if an instance of the class is created. The instance of the class created can change the value of BASE_URL. Although, all objects of the class will start using the newly changed value of BASE_URL. Adding final keyword ensures just one copy of the variable exist.
  2. This line of code: “public static Retrofit retrofit = null;” is known as lazy instantiation. It delays the creation of the instance until it is needed. It is commonly referred to as a design pattern by a lot of people but I feel it’s just a feature or practice to increase efficiency.
  3. A static method getClient is created. The instance of Retrofit is created using the builder API (Retrofit.builder) in this getClient method. The type of converter to be used is also specified and then we build. Voila! We want the same instance to be used throughout our application more like a singleton approach. Am I making any sense?

Second Method

//Retrofit instance is created here
`public Class Client {public static final String BASE_URL = "http://192.168.43.96:8000/api/v1/";private static Retrofit.Builder builder = new Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory(GsonConverterFactory.create());private static Retrofit retrofit = builder.build();//Service builder that creates implementation of those interfaces is created here. public static <T> T buildService(Class<T> type) {                                               return retrofit.create(type)
}}

Explanation

The first method uses a static getClient. The second method didn’t make use of that, instead all of it’s need variables are declared static.

You don’t make a Retrofit class, you use the builder for that. You instead hold a reference to the interface that the builder makes for you.

Retrofit service building features must be utilized. The helper method buildService helps us to build services. buildService method is a reusable service builder of some kind. The service instance can be created inside an activity for example and then can be used to make our own HTTP request. The service builder creates the implementation of those interfaces.

Still do not understand the buildService method, then READ this!

When you check the create method provided by retrofit, you find this:

public <T> T create(final Class<T> service) {
   Utils.validateServiceInterface(service);
   if (validateEagerly) {
     eagerlyValidateMethods(service);
   }

This looks very similar to our buildService method right? And we know that the buildService method actually generate a proxy that implements the interface.

In our buildService method, we make use of generics. The T can be replaced with any other letter. It represents the class type. The method parameter , Class<T> accepts a type of the interface and returns an implemented type of the interface T.

Enough talk. Show me your codes!

Let’s make our API calls in our MainActivity. Although, this is not the best practice.

Let’s create our ResponseObject.

public class UserResponse {    
private String status;    private Integer code;    private String message;    private Data data;    public String getStatus() {        return status;    }    public void setStatus(String status) {        this.status = status;    }    public Integer getCode() {        return code;    }    public void setCode(Integer code) {        this.code = code;    }    public String getMessage() {        return message;    }    public void setMessage(String message) {        this.message = message;    }    public Data getData() {        return data;    }    public void setData(Data data) {        this.data = data;    }}

Lets create our Body: The data sent to the server in the body of the HTTP request.

public class Data {
   public Data(String email, String password, String first_name, String last_name, String gender, String phone_number) {
       this.email  = email;
       this.password = password;
       this.first_name = first_name;
       this.last_name = last_name;
       this.gender = gender;
       this.phone_number = phone_number;
   }

   private String email;
   private String password;
   private String first_name;
   private String last_name;
   private String gender;
   private String phone_number;



}

And finally let’s do the magic in our MainActivity with this code snippet.

Firstly we build the service;

mUserClient = Client.buildService(UserClient.class);

Then we create a method named: sendNetworkRequestUser that takes in a Data object.

private void sendNetworkRequestUser(Data data) {        Call<UserResponse> call =UserClient.createAccount(data);        call.enqueue(new Callback<UserResponse>() {            @Override            public void onResponse(Call<UserResponse> call, Response<UserResponse> response) {                switch (response.body().getCode()) {                    case 201:                        Toast.makeText(MainActivity.this, "Sucessful Sign Up!", Toast.LENGTH_SHORT).show();                        break;                    case 409:                        Toast.makeText(MainActivity.this, "Response Failure" + response.body().getCode(), Toast.LENGTH_SHORT).show();                            break;                    default:                        Toast.makeText(MainActivity.this, "Request not Processed" + response.body().getCode(), Toast.LENGTH_SHORT).show();                }            }            @Override            public void onFailure(Call<UserResponse> call, Throwable t) {                Toast.makeText(MainActivity.this, "Failure" + t.fillInStackTrace(), Toast.LENGTH_SHORT).show();            }        });           }}

Then the information is sent to the server.

💻 Android

Tobiloba Adejumo

Doctoral Student, Research Assistant at The University of Illinois Chicago | Biomedical Optics and Functional Imaging Lab