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.
Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter