Retrofit en Android

Acerca de Retrofit

Retrofit es un cliente REST para Android y Java creado por Square. Es una librería que es relativamente fácil de utilizar y que tienes muchas características.

Es altamente entendible para principiantes, en especial si la comparamos con otras librerías para networking o la implementación nativa que la misma documentación oficial de Android propone.

Retrofit nos permite hacer uso de diversos comandos HTTP: GET,POST,PUT,DELETE, entre otros, que podemos utilizar para interactuar con cualquier API pública o propia. Retrofit incluso cuenta con una librería "hermana" basado en ella, para el manejo de imágenes locales y remotas: Picasso (que tal vez reseñe en otra ocasión)

Conceptos Básicos

Para poder entender como funciona un cliente REST en Android, usando Retrofit es importante entender 3 conceptos:

POJO

(Plain Old Java Object) También conocidos como modelos o clases, sirven como contenedores para los objetos json que devuelve el servidor.

Interfce

(No como elemento visual) Se refiere a la creación de una clase en particular (una interface) que será la encargada de manejar las rutas que estaremos llamando mediante los GET y POST. Podemos verla como la clase "servicio".

Adaptador REST

Esta sería la clase que tendrá la función del cliente REST. Por default Retrofit utiliza Gson para el parseo de los json que recibe.

Primero lo primero

Es claro suponer que sí estaremos utilizando una "librería de terceros" como lo es Retrofit, esta debe provenir de algún lado, para lo cual es necesario modificar la sección dependencies del archivo build.gradle, ubicado en la carpeta app de nuestro proyecto.

dependencies {
  compile 'com.google.code.gson:gson:2.2.4' (1)
  compile 'com.google.guava:guava:16.0.1'
  compile 'com.squareup.retrofit:retrofit:1.9.0' (2)
  compile 'com.squareup.okhttp:okhttp-urlconnection:2.0.0'
  compile 'com.squareup.okhttp:okhttp:2.0.0'
  compile 'joda-time:joda-time:2.3'
  compile 'org.parceler:parceler:0.2.13'
}
1 Puesto que GSON será el tipo de anotación que estaremos usando.
2 Última versión disponible

Una vez realizados los cambios, Android Studio nos pedirá actualizar nuestro gradle, para que las nuevas librerías se descarguen y esten disponibles.

Deben tener una conexión activa a Internet para poder hacer la descarga de paquetes, de lo contrario no se descargaran y no los podrán utilizar.

Además de las librerías, debemos indicarle al SO, que nuestra aplicación hará uso de Internet, agregando los permisos en nuestro AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
  <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.j2deme.weather" > (1)
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <application ... (2)
1 El nombre del paquete corresponde a su usuario y su aplicación.
2 El resto del manifest.

Construcción del cliente REST

Una vez que hemos descargado las librerías necesarias y añadido los permisos requeridos, lo primero que debemos hacer es crear una clase que hara la función de adaptador REST y que para motivos generales podemos llamar RestClient.java, se recomienda que este dentro de un package llamado rest para mejor organización.

...
public class RestClient {
  private static final String BASE_URL = "http://api.openweathermap.org/data/2.5"; (1)
  private static API REST_CLIENT;

  static {
    setupRestClient();
  }

  private RestClient(){}

  public static API getApiService(){ return REST_CLIENT; }

  private static void setupRestClient(){
    Gson gson = new GsonBuilder()
      .create();
    RequestInterceptor requestInterceptor = new RequestInterceptor() {
      @Override
      public void intercept(RequestFacade request) {
        request.addHeader("Content-Type", "application/json"); (2)
      }
    };
    RestAdapter.Builder builder = new RestAdapter.Builder()
      .setEndpoint(BASE_URL)
      .setRequestInterceptor(requestInterceptor)
      .setConverter(new GsonConverter(gson))
      .setLogLevel(RestAdapter.LogLevel.FULL);

    RestAdapter restAdapter = builder.build();
    REST_CLIENT = restAdapter.create(API.class);
  }
}
1 La variable BASE_URL sería la que modificaríamos según lo necesario.
2 Le indica al servidor que la respuesta debe estar codificada como un JSON.

Como se mencionó anteriormente, esta clase hace el trabajo de adaptador, para conectar las rutas de la API que estemos utilizando a los métodos de nuestro interface en Java, como veremos más adelante.

Las APIS pueden tener infinidad de métodos (rutas) para consultar y manipular, pero nuestro interface solo debe contener aquellas que se van a utilizar.

Creamos una interface con las rutas que vamos a utilizar, esto se hace seleccionado la opción de crear una nueva clase y cambiando el tipo por ìnterface.

...
public interface API {
  @GET("/weather") (1)
  void obtenerClima( (2)
    @Query("q") String city, (3)
    @Query("units") String units,
    @Query("lang") String lang,
    Callback<ApiResponse> callback (4)
  );

  // Otras rutas que utilicemos
  ...
}
1 La anotación @GET permite hacer lecturas o consultas al API.
2 El nombre del método con el cual invocaremos al API.
3 La anotación @Query indica el parámetro que se le pasará a la ruta y la variable que le sigue, indica el parámetro del método
4 El Callback es aquel que nos devolverá la respuesta de la ruta, parseando de forma transparente para el desarrollador y devolviendo un objeto del tipo indicado en los <> (ApiResponse para este caso, veremos más adelante).

Ya que hemos definido las rutas y tipos de datos (u objetos según corresponda) que se devolveran lo que debemos es generar los modelos o "POJOS" que serán rellenados con la respuesta del API, a traveś del método que definimos anteriormente.

Es posible construir los "POJOS" manualmente, analizando todos y cada uno de los atributos escalares (int, char, bool, string, etc.) y de tipos personalizados (sub-objetos) que forman la respuesta del API; esto claramente permite un mayor control sobre la construcción de las clases y sus nomenclaturas, pero da margen a error si no se tiene experiencia en la construcción y uso de los mismos.

Una alternativa es la construcción automática de las clases y sus relaciones mediante el uso de SW creado para tal efecto, en este caso utilizaremos JSON Schema 2 POJO (J2P en adelante) por su interfaz simple, intuitiva y no requerir instalar nada.

Construyendo los POJOS

Construir los POJOS, usando J2P es extremadamente sencillo, ya que solo se requiere copiar la respuesta JSON que nos devuelve el API:

{ (1)
  "coord": { (2)
    "lon": -99.02,
    "lat": 21.98
  },
  "sys": {
    "message": 0.3346,
    "country": "MX",
    "sunrise": 1431604614,
    "sunset": 1431652075
  },
  "weather": [ (3)
    {
      "id": 804,
      "main": "Clouds",
      "description": "overcast clouds",
      "icon": "04d"
    }
  ],
  ...
  "dt": 1431645545,
  "id": 3530582,
  "name": "Ciudad Valles",
  "cod": 200 (4)
}
1 Notamos que la respuesta es un objeto JSON, ya que inicia con {.
2 Aquí tenemos un sub-objeto de la respuesta del API, donde cada llave:valor es un atributo del objeto.
3 Este elemento es un arreglo, ya que inicia con [
4 Observamos que el API devuelve un código entendible por el protocolo HTTP: 200

Si copiamos la respuesta del API, en el área de texto de J2P, generar las clases solo requiere de lo siguiente:

  1. Indicar el nombre del paquete de nuestra aplicación. P.e. com.j2deme.weather.pojos, la última parte pojos se recomienda para motivos de organización de los archivos.

  2. El nombre de la clase que recibira la respuesta del API. P.e. ApiResponse, aunque si se usaran diferentes rutas de una misma API, sería recomendable que el nombre de la clase fuera más significativo como ApiWeatherResponse y/o ApiForecastResponse.

  3. Elegir el "Source type" como JSON.

  4. Elegir el "Annotation style" como Gson.

  5. Verificar que la opción "Use double numbers" este marcada.

  6. Opcional Verificar que la opción "Use Joda Dates" este marcada (si y sólo si usan fechas y saben manipular los Joda Dates).

  7. Presionar el botón "Preview" debajo del área de texto

  8. Ir a Android Studio y generar la carpeta (o "package") pojos, si así se especifico en el primer paso.

  9. Generar cada una de las clases que se nos muestran en el "Preview", poniendo los nombres tal cual se indican en los códigos generados.

  10. Para eliminar el posible error que pudiera aparecer en las clases, borren la línea con el texto @Generated("org.jsonschema2pojo") en cada una de las clases, en caso de aparecer errores con "clases no definidas" o "no existentes", estos deben desaparecer una vez que se termine de copiar y pegar todas las clases.

Usando el cliente REST

Para utilizar el cliente REST, debemos hacer la llamada en donde la necesitemos, ya sea en el evento de click de un botón, o desde el mismo onCreate de nuestro Activity o Fragment, usando la siguiente estructura básica:

RestClient
  .getApiService() (1)
  .obtenerClima(city, "metric", "es", (2)
    new Callback<ApiResponse>() { (3)
      @Override
      public void success(ApiResponse data, Response response) { (4)
        mostrarClima(data); (5)
      }

      @Override
      public void failure(RetrofitError error) { (6)
        Log.e(TAG, "Error : " + error.getMessage()); (7)
      }
    });
1 Esto nos devolverá una conexión hacia el API, simpre y cuanto se haya podido conectar.
2 Aquí tenemos el método que definimos en nuestro interface, con los parámetros que definimos.
3 El "callback" nos devolverá un objeto del tipo que definimos como respuesta del API.
4 Cuando todo este bien, se nos devolvera un objeto del tipo que definimos (ApiResponse) y otro de tipo Response, este último nos da información sobre la respuesta HTTP.
5 Aquí podemos mandar llamar otro método y pasarle el "data" obtenido o manipularlo ahí mismo.
6 En caso de error o de que falle la conexión, se nos devuelve un RetrofitError con información del error.
7 El contenido del error puede mandarse al "log" o mostrar algún Toast o realizar algún cambio en la interfaz, según se desee.

Consideraciones Finales

Como se mencionó anteriormente, implementar un cliente REST con Retrofit es relativamente simple, siempre y cuando se comprendan los conceptos básicos relacionados al tema.

Este post no pretende ser una solución completa a todos los problemas que se puedan presentar al implementar un cliente REST en Android usando Retrofit, sino más bien el servir de referencia inicial para tal implementación.

Para mayor información siempre pueden revisar la documentación oficial de Android, o de las librerías que utilicen, o simplemente googlear lo que necesiten, recuerden que el problema no es que no exista información, sino que no sabemos buscar ;)