Getting started with Retrofit: consume your REST API from Android

22-06-2023 - Andrés Cruz

En español
Getting started with Retrofit: consume your REST API from Android

Retrofit is a REST client for developing Android applications; It is relatively easy to use (as expected in Java environments), it allows you to add custom converters to map the data obtained from a REST API in XML or JSON format into an object of a custom class by means of a deserilizer.

Using Retrofit in our Android projects

To be able to work with Retrofit in our project in Android Studio we need:

  • A REST API built on another platform that we can get HTTP; We have already seen above how to create a REST API with CodeIgniter.
  • A model class that allows mapping/casting the JSON/XML returned by the REST API in our object, which is what is known as desirability.
  • An adapter class that allows you to indicate which objects are to be deserialized
  • An interface to define our URLs, annotations (GET, POST...) that allow us to centralize the requests made to our REST API.
  • Reality the connection with the REST API.

Having these elements indicated above we can work perfectly with Retrofit in our Android project.

Incorporating Retrofit and dependencies in our project

For the following experiment we will use the JSONs to list some data series that will later be consumed by this Android app and converted to an object by means of a deserializer; but before that we need to include the Retrofit dependency in our Gradle file of our Android Studio project:

compile 'com.squareup.retrofit2:retrofit:2.3.+'

You can check the latest version from the retrofit official site.

We will also add some more dependencies that will allow us to work comfortably with the returned JSON:

compile 'com.google.code.gson:gson:2.8.1' compile 'com.squareup.retrofit2:converter-gson:2.3.+'

There are several libraries that we can install as a dependency to process JSONs and be able to map them into our classes/objects:

  • Gson: com.squareup.retrofit:converter-gson
  • Moshi: com.squareup.retrofit:converter-moshi
  • Jackson: com.squareup.retrofit:converter-jackson

In this experiment we will use the first one, that is, Gson, but you can use another one to your liking.

The interface for the REST API URLs

Here we will simply define all the URLs along with their parameters from our REST API that we will use in our Android application:

public final class ConstantesRestApi {
    public static final String ROOT_URL = "http://mobiplay.cl/ncadmin/";
    public static final String REST_API = "restserver/";

    public static final String KEY_GET_NOTIFICACIONES = "notifications_send/{grupo_id}/1/{user_id}?format=json";

    public static final String URL_GET_NOTIFICACIONES = ROOT_URL + REST_API + KEY_GET_NOTIFICACIONES;
}
public interface EndpointsApi {

    @GET(ConstantesRestApi.URL_GET_NOTIFICACIONES)
    Call<NotificationResponse> getNotificaciones(@Path("grupo_id") int grupo_id, @Path("user_id") String user_id);
}

The model class of our API

This is simply a model class where we will map each of the attributes of our JSON in this model class, that is, if in our JSON there is a property called image then in our class we will create an attribute called image where we will establish said value in the Deserializer class which we will define a bit later.

public class Notification {
    private int notification_id;
    private String image;
    private String title;
    private String text;
    private int grup_id;
    private String create_at;

    public int getNotification_id() {
        return notification_id;
    }

    public void setNotification_id(int notification_id) {
        this.notification_id = notification_id;
    }

    public String getImage() {
        return image;
    }

    public void setImage(String image) {
        this.image = image;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }

    public int getGrup_id() {
        return grup_id;
    }

    public void setGrup_id(int grup_id) {
        this.grup_id = grup_id;
    }

    public String getCreate_at() {
        return create_at;
    }

    public void setCreate_at(String create_at) {
        this.create_at = create_at;
    }
}

Defining the response class

It is possible that the API to consume not only returns a single entity to be processed but also a list of it, so we must define a class like the following:

public class NotificationResponse {
    ArrayList<Notification> notifications;

    public ArrayList<Notification> getNotifications() {
        return notifications;
    }

    public void setNotifications(ArrayList<Notification> notifications) {
        this.notifications = notifications;
    }
}

Which we will indicate in the next section to indicate that our response costs a list of entities and not just a single entity.

Defining the adapter layer

Finally, the adapters in Retrofit are nothing more than a layer provided through a class that allows you to indicate which objects are going to be deserialized; As we can see, the model class is specified, which is our NotificationResponse defined above, and the desiring class, which is NotificationsDesiring.

public class RestApiAdapter {
   public EndpointsApi establecerConexionRestApi(Gson gson) {
       Retrofit retrofit = new Retrofit.Builder()
               .baseUrl(ConstantesRestApi.ROOT_URL)
               .addConverterFactory(GsonConverterFactory.create(gson))
               .build();

       return retrofit.create(EndpointsApi.class);
   }

   public Gson convierteGsonDesearilizadorNotificaciones() {
       GsonBuilder gsonBuldier = new GsonBuilder();
       gsonBuldier.registerTypeAdapter(NotificationResponse.class, new NotificacionesDesearilizador());

       return gsonBuldier.create();
   }
}

The method establishConexionRestApi is just a control method where we define the base URL of our REST API.

Defining the deserializer class

In this class we indicate each of the attributes of the JSON object we are going to map in our object:

public class NotificacionesDesearilizador implements JsonDeserializer<NotificationResponse> {
    @Override
    public NotificationResponse deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

        JsonArray notificationsResponseData = json.getAsJsonArray();
        NotificationResponse notificationResponse = new NotificationResponse();
        notificationResponse.setNotifications(desearilizadorNotifications(notificationsResponseData));
        return notificationResponse;
    }

    private ArrayList<Notification> desearilizadorNotifications(JsonArray notificacionesResponseData) {

        ArrayList<Notification> notifications = new ArrayList<>();

        for (int i = 0; i < notificacionesResponseData.size(); i++) {
            JsonObject jsonObject = notificacionesResponseData.get(i).getAsJsonObject();

            Notification notification = new Notification();

            if (jsonObject.get("image") == NULL || jsonObject.get("image").isJsonNULL()) {
                notification.setImage(NULL);
            } else {
                notification.setImage(jsonObject.get("image").getAsString());
            }

            notification.setText(jsonObject.get("text").getAsString());
            notification.setTitle(jsonObject.get("title").getAsString());
            notification.setCreate_at(jsonObject.get("create_at").getAsString());
            if (jsonObject.get("grup_id") == NULL || jsonObject.get("grup_id").isJsonNULL()) {
                notification.setGrup_id(0);
            } else {
                notification.setGrup_id(jsonObject.get("grup_id").getAsInt());
            }

            notifications.add(notification);
        }

        return notifications;

    }
}

We are simply establishing the JSON values in our Notification object and saving it in a list.

Making the connection

Finally, we make the connection, as we can see, there is a listener event that will provide us with our JSON deserialization process object and another that will be executed in case of error in the connection, mapping, etc:

    public void cargarNotificaciones() {

        RestApiAdapter restApiAdapter = new RestApiAdapter();
        Gson gson = restApiAdapter.convierteGsonDesearilizadorNotificaciones();
        EndpointsApi endpointsApi = restApiAdapter.establecerConexionRestApi(gson);

        Call<NotificationResponse> responseCall = endpointsApi.getNotificaciones((int) UserPreferences.getUsuarioGrupo(MainActivity.this), UserPreferences.getUsuarioId(MainActivity.this));


        responseCall.enqueue(new Callback<NotificationResponse>() {
            @Override
            public void onResponse(Call<NotificationResponse> call, Response<NotificationResponse> response) {
                NotificationResponse notificationResponse = response.body();

                if (notificationResponse != NULL) {
                    notifications = notificationResponse.getNotifications();
                }

                initAdapter(new NotificacionAdapter(notifications, MainActivity.this));


            }

            @Override
            public void onFailure(Call<NotificationResponse> call, Throwable t) {
                Log.e("Error", t.toString());
                Toast.makeText(MainActivity.this, getResources().getString(R.string.error_login_local_titulo), Toast.LENGTH_LONG).show();

            }
        });
    }

Here it is important to notice the call to the method endpointsApi.getNotifications() whose method has the connection to the URL of our resource that we want to obtain from the REST API.

Considerations in practice

If some of the parameters could be null, it is necessary to validate it to prevent the application from throwing an exception and then failing and closing:

if (jsonObject.get("image") == NULL || jsonObject.get("image").isJsonNULL()) {
   notification.setImage(NULL);
} else {
   notification.setImage(jsonObject.get("image").getAsString());
}

URL by GET

If we want to obtain resources via a GET request:

@GET(ConstantesRestApi.URL_GET_IMAGENES) Call<ImageResponse> getImagenes(@Path("grupo_id") int grupo_id);

URL by POST

Si por el contrario deseamos obtener recursos vía una petición POST:

@FormUrlEncoded
@POST(ConstantesRestApi.URL_POST_INICIAR_SESION)
Call<User> iniciarSesionLocal(@Field("username") String username, @Field("passwd") String passwd);

Authentications in the header

It is possible that the RestApi with which you are working has authentication in the header (the header of the HTTP request), for this you can use a code similar to the following:

    public EndpointsApi establecerConexionRestApi(Gson gson, Activity activity) {

        // establecemos las crecenciales registradas por el usuario
        String username = UserPreferences.getLastClienteNombre(activity),
                password = UserPreferences.getLastClienteContrasena(activity);

        String credentials = username + ":" + password;

        // Base64 encodet string
        final String basic =
                "Basic " + Base64.encodeToString(credentials.getBytes(), Base64.NO_WRAP);

        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
        httpClient.addInterceptor(new Interceptor() {
            @Override
            public Response intercept(Interceptor.Chain chain) throws IOException {
                Request original = chain.request();

                // Request customization: add request headers
                Request.Builder requestBuilder = original.newBuilder()
                        .header("Authorization", basic);

                Request request = requestBuilder.build();
                return chain.proceed(request);
            }
        });

        OkHttpClient client = httpClient.build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(ConstsRestApi.ROOT_URL)
                .client(client)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .build();

        return retrofit.create(EndpointsApi.class);
    }

An important point is to know that the Retrofit responses must have the code 200 in the header so that they can be correctly processed by the desiring classes that we defined previously; If at any time you see that the response is not processed by them, and even this does not fail even when you see that the request/response is working correctly, this may be one of the factors that may be causing problems.

You can consult the official documentation in the following link: Retrofit.

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.