Blog

Tercera traducción de la serie de tutoriales de Flutter

Chopper (Retrofit para Flutter) #2 – Interceptores

Nueva traducción realizada por Antonio Cantero, de la serie de tutoriales sobre Flutter. Puedes leer las demás en este enlace.

Este tutorial y el resto de la serie son una traducción del genial trabajo de Matej Rešetár. Puedes leer el original aquí o si lo prefieres puedes ver el vídeo.

Una vez visto el uso básico de Chopper para hacer peticiones HTTP en el post anterior, es turno de dedicar nuestra atención a los interceptores. Son componentes de Chopper a más alto nivel que se usan para ejecutar algunas acciones justo antes de enviar una petición o inmediatamente después de recibir una respuesta.

Fundamentos

Lo esencial cuando hablamos de interceptors es que se ejecutan con cada petición o respuesta ejecutada en un cliente Chopper. Si quieres llevar a cabo algún operación en el cliente, los interceptares son exactamente lo que tienes que utilizar.

En el apartado anterior aprendimos que Chopper tiene el concepto de ChopperService, que contiene métodos para hacer peticiones. Un servicio se suele reservar para un endpoint (e.g. "/posts", "/comments") y normalmente hay múltiples servicios para un Cliente Chopper (e.g. PostService + CommentService).

¿Quieres mantener una estadística de cuántas veces se llama a una determinada URL? ¿Quieres añadir headers a cada petición? ¿Quieres sugerir al usuario que active el WIFI cuando está apunto de descargar un archivo de gran tamaño? Todas esto es responsabilidad de un interceptor.

Añadiendo interceptores

Cómo hemos visto previamente, los interceptors se aplican en el Cliente Chopper.

Por lo tanto se especificarán en el constructor del cliente. Aunque hay dos tipos de interceptores – request y response, ambos se añaden en una lista de parámetros.

///post_api_service.dart

static PostApiService create() {
final client = ChopperClient(

interceptors: [
// Both request & response interceptors go here
],
);

return _$PostApiService(client);
}

Interceptores incluidos

Chopper viene con algunos interceptores útiles incluidos

HeadersInterceptor

Uno de esos interceptors es el HeadersInterceptor que añadirá headers a todas las peticiones realizadas por el Cliente Chopper. Los headers son un Map<String, String>. Añade lo siguiente a la lista de interceptares en un cliente:

HeadersInterceptor({‘Cache-Control’: ‘no-cache’})

Los otros interceptors incluidos sirven para tareas de depuración.

HttpLoggingInterceptor

El HttpLoggingInterceptor es una herramienta muy útil para obtener información detallada de las peticiones y las respuestas. Una vez configuradas podrás ver logs cómo este:

Chopper usa el package logging, desarrollado directamente por el equipo de desarrolladores de Dart. Antes de poder ver logs en la consola necesitas configurar este package – el mejor sitio es el método main().

///main.dart

import ‘package:logging/logging.dart’;

void main() {
_setupLogging();
runApp(MyApp());
}

// Logger is a package from the Dart team. While you can just simply use print()
// to easily print to the debug console, using a fully-blown logger allows you to
// easily set up multiple logging "levels" – e.g. INFO, WARNING, ERROR.

// Chopper already uses the Logger package. Printing the logs to the console requires
// the following setup.
void _setupLogging() {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((rec) {
print(‘${rec.level.name}: ${rec.time}: ${rec.message}’);
});
}

Todo lo que queda es añadir lo siguiente a la lista de interceptors del Cliente Chopper:

HttpLoggingInterceptor()

CurlInterceptor

Éste es el último interceptor incorporado en Chopper. Si no estás demasiado familiarizado con CURL y quieres ver el comando CURL para la petición hecha por la app puedes añadir lo siguiente:

CurlInterceptor()

Por tanto, después de hacer peticiones GET o POST con Chopper, obtendremos el comando impreso en la consola. Hay que tener en cuenta que aquí, también, necesitamos habilitar el package logging en nuestra app.

Interceptores personalizados

Crear tu propio interceptor para ejecutar tu propia lógica antes de las peticiones o después de recibir las repuestas puede hacerse de dos modos:

Funciones anónimas
Clases que implementan RequestInterceptor o ResponseInterceptor

Funciones anónimas

Éstos son para implementar rápidamente pequeños interceptors que no contienen mucha lógica. No obstante, normalmente es mejor usar la segunda opción – crear una clase separada. De otra manera podríamos acabar con un código “demasiado desordenado”. Como siempre, ¡el principio de responsabilidad única (SRP) es el rey!

Con todo, si prefieres prescindir del SRP por rapidez, así es como pueden definir interceptors anónimos. De nuevo, este código va en la lista de interceptors del Cliente Chopper.

///post_api_service.dart

static PostApiService create() {
final client = ChopperClient(

interceptors: [

(Request request) async {
if (request.method == HttpMethod.Post) {
chopperLogger.info(‘Performed a POST request’);
}
return request;
},
(Response response) async {
if (response.statusCode == 404) {
chopperLogger.severe(‘404 NOT FOUND’);
}
return response;
},
],
);

return _$PostApiService(client);
}

Los interceptores de peticiones y respuestas sólo difieren en el tipo de sus parámetros.

WARNING Los interceptores siempre tienen que devolver una petición/respuesta. De otro modo, el siguiente interceptor que sea lanzado o cualquier otro código en Chopper recibirá una petición/respuesta null… y ya sabemos que pasa con los nulls…

Clases separadas

¿Y si quisiéramos prevenir que el usuario descargue archivos de gran tamaño a menos que tenga WIFI? Para esto lo mejor sería utilizar una clase RequestInterceptor. ResponseInterceptors se hacen del mismo modo.

Antes de implementar nuestro MobileDataInterceptor, nosotros necesitamos añadir un package al proyecto.

pubspec.yaml

dependencies:

connectivity: ^0.4.3+2

Lo que queremos es lanzar una excepción personalizada (MobileDataCostException) si el usuario está utilizando sus datos móviles y está intentando descargar archivos de gran tamaño.

///mobile_data_interceptor.dart

import ‘dart:async’;

import ‘package:chopper/chopper.dart’;
import ‘package:connectivity/connectivity.dart’;

class MobileDataInterceptor implements RequestInterceptor {
@override
FutureOr onRequest(Request request) async {
final connectivityResult = await Connectivity().checkConnectivity();

final isMobile = connectivityResult == ConnectivityResult.mobile;
// Checking for large files is done by evaluating the URL of the request
// with a regular expression. Specify all endpoints which contain large files.
final isLargeFile = request.url.contains(RegExp(r'(/large|/video|/posts)’));

if (isMobile && isLargeFile) {
throw MobileDataCostException();
}

return request;
}
}

class MobileDataCostException implements Exception {
final message =
‘Downloading large files on a mobile data connection may incur costs’;
@override
String toString() => message;
}

Después de añadir una instancia de la clase MobileDataInterceptor a la lista de interceptors, la excepción será lanzada. Debido al modo en el que hemos configurado la app en el post anterior, esta excepción no romperá la app – esto es porque estamos usando un widget FutureBuilder que maneja automáticamente las excepciones.

Aún así, deberíamos mostrar algún mensaje al usuario para decirle que está ocurriendo. En nuestra app de ejemplo mostraremos un widget Text, para simplificar la tarea.

///home_page.dart

FutureBuilder _buildBody(BuildContext context) {
return FutureBuilder(
future: Provider.of(context).getPosts(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// Exceptions thrown by the Future are stored inside the "error" field of the AsyncSnapshot
if (snapshot.hasError) {
return Center(
child: Text(
snapshot.error.toString(),
textAlign: TextAlign.center,
textScaleFactor: 1.3,
),
);
}
// Snapshot’s data is the Response
// You can see there’s no type safety here (only List)
final List posts = json.decode(snapshot.data.bodyString);
return _buildPosts(context, posts);
} else {
// Show a loading indicator while waiting for the posts
return Center(
child: CircularProgressIndicator(),
);
}
},
);
}

Por cierto, si el usuario no está conectado a internet, el package HTTP (que usa Chopper) lanzará su propia excepción y ese mensaje será mostrado en el widget Text.

Conclusión

Los interceptores proporciona un modo de ejecutar código antes de enviar peticiones y después de recibir respuestas. Nos permiten añadir lógica bastante potente, como el MobileDataInterceptor anterior, o para añadir simplemente un log.

En la próxima parte aprenderemos como convertir los datos dinámicos que hay actualmente en la respuesta en una clase personalized usando el asombroso package BuiltValue.

Una traducción de Antonio Cantero

ASPgems icon
C/ Sextante, 9
28023 Madrid,
España

Hablemos.