Primeros pasos con Retrofit: consume tu API REST desde Android

04-09-2017 - Andrés Cruz

Primeros pasos con Retrofit: consume tu API REST desde Android
In english

Este material forma parte de mi curso y libro completo; puedes adquirirlos desde el apartado de libros y/o cursos.

Retrofit es un cliente REST para desarrollar aplicaciones en Android; es relativamente fácil de usar (lo esperado en entornos con Java), permite agregar convertidores personalizados para mapear los datos obtenidos desde una API REST en formato XML o en él más que famoso JSON en un objeto de una clase personalizada mediante un desearilizador.

Usando Retrofit en nuestro proyectos Android

Para poder trabajar con Retrofit en nuestro proyecto en Android Studio necesitamos:

  • Una REST API creada en otra plataforma que podamos obtener HTTP; ya hemos visto anteriormente cómo crear una REST API con CodeIgniter.
  • Una clase modelo que permita mapear/castear el JSON/XML devuelto por la REST API en nuestro objeto que es lo que se conoce como desearilizador.
  • Una clase adaptadora que permite indicar qué objetos van a ser deserializados
  • Una interfaz para definir nuestras URLs, anotaciones (GET, POST...) que permiten tener centralizadas las peticiones realizadas a nuestra REST API.
  • Realidad la conexión con la API REST.

Teniendo estos elementos señalados anteriormente podemos trabajar perfectamente con Retrofit en nuestro proyecto Android.

Incorporando Retrofit y dependencias en nuestro proyecto

Para el siguiente experimento emplearemos los JSONs para listar unos series de datos que luego serán consumidos por esta app Android y convertidos a un objeto mediante un desedesearilizador; pero antes de eso debemos incluir la dependencia de Retrofit en nuestro archivo Gradle de nuestro proyecto en Android Studio:

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

Puedes consultar la última versión desde el sitio oficial de retrofit.

También añadiremos unas dependencias más que nos permitirán trabajar cómodamente con los JSON devueltos:

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

Existen varias librerías que podemos instalar en forma de dependencia para procesar JSONs y poder mapearlos en nuestras clases/objetos:

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

En este experimento emplearemos el primero, es decir Gson pero puedes emplear otro a gusto propio.

La interfaz para las URLs de la REST API

Aquí simplemente definiremos todas las URLs junto con sus parámetros de nuestra API REST que emplearemos en nuestra aplicación Android:

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);
}

La clase modelo de nuestra API

Esto simplemente es una clase modelo en donde mapearemos cada uno de los atributos de nuestro JSON en esta clase modelo, es decir, si en nuestro JSON existe una propiedad llamada image entonces en nuestra clase crearemos un atributo llamado image en donde estableceremos dicho valor en la clase deserializador que definiremos un poco más adelante.

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;
    }
}

Definiendo la clase respuesta

Es posible que la API a consumir no solo devuelve una sola entidad a ser procesada si no un listado de ésta, por eso debemos definir una clase como la siguiente:

public class NotificationResponse {
    ArrayList<Notification> notifications;

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

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

La cual indicaremos en la siguiente sección para indicar que nuestra respuesta costa de un listado de entidades y no solo una sola entidad.

Definiendo la capa adaptadora

Finalmente, los adaptadores en Retrofit no son más que una capa provista a través de una clase que permite indicar qué objetos van a ser deserializados; como vemos, se especifica la clase modelo que viene siendo nuestro NotificationResponse definido anteriormente, y la clase desearilizadora que viene siendo NotificacionesDesearilizador

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();
   }
}

El método establecerConexionRestApi es solo un método de control en donde definimos la URL base de nuestra API REST.

Definiendo la clase deserializadora

En esta clase indicamos cada uno de los atributos del objeto JSON vamos a mapear en nuestra objeto:

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;

    }
}

Simplemente vamos estableciendo los valores el JSON en nuestro objeto Notification y lo vamos guardando en un listado.

Realizando la conexión

Finalmente, realizamos la conexión, como podemos ver, existe un evento escuchador o listener que nos proveerá de nuestro objeto proceso de la deserialización del JSON y otro que se ejecutará en caso de error en la conexión, mapeado, 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();

            }
        });
    }

Aquí es importante notar la invocación al método endpointsApi.getNotificaciones() cuyo método tiene la conexión a la URL de nuestro recurso que queremos obtener de la API REST.

Consideraciones en la práctica

Si algunos de los parámetros pudiera venir nulo, es necesario validarlo para evitar que la aplicación no lance una excepción y posteriormente falle y se cierre:

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

URL por GET

Si deseamos obtener recursos vía una petición GET:

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

URL por 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);

Autenticaciones en el header

Es posible que la RestApi con la cual están trabajando tenga autenticación en el header (el el header de la petición HTTP), para ello pueden emplear un código similar al siguiente:

    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);
    }

Un punto importante es saber que las respuestas de Retrofit deben tener en la cabecera el código 200 para que puedan ser procesadas correctamente por las clases desearilizadora que definimos anteriormente; si en algún momento ves que la respuesta no es procesada por los mismos, e incluso esta no falla aun cuando veas que la petición/respuestas están funcionando correctamente este puede ser uno de los factores que pueden estar dando problemas.

Puedes consultar la documentación oficial en el siguiente enlace: 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.

Conozca nuestros cursos sobre Laravel, CodeIgniter, Flutter, Electron, Django, Flask y muchos más!

Ver los cursos
¡Hazte afiliado en Gumroad!

!Cursos desde!

4$

En Academia

Ver los cursos

!Libros desde!

1$

Ver los libros
!Web Alojada en Hostinger!