Skip to content

Instantly share code, notes, and snippets.

@marcossevilla
Created April 17, 2021 20:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save marcossevilla/76544984c0e1457bb9ee1cab69ef4e03 to your computer and use it in GitHub Desktop.
Save marcossevilla/76544984c0e1457bb9ee1cab69ef4e03 to your computer and use it in GitHub Desktop.

Un mejor HTTP con Dio

Nunca falta el escenario en nuestros proyectos donde debemos llamar a un API externa via HTTP.

En Dart, tenemos el paquete oficial de http. Este nos provee de funciones y clases que cumplen lo necesario para efectuar estas llamadas HTTP de una manera sencilla y sin tanta tortura.

El paquete http se caracteriza por estar basado en el tipo de dato Future para peticiones y además se puede utilizar en todas las plataformas que soporta Flutter tanto estable (iOS y Android) como en desarrollo (web y desktop).

Con http podemos hacer llamadas en pocas líneas de la siguiente manera...

https://gist.github.com/503f70eedec287427bcc17b33e134174

Un%20mejor%20HTTP%20con%20Dio%200d611cae5ece4856a5ba6ab8e46d61aa/carbon.png

Y muchos ejemplos en la comunidad son hechos utilizando http, porque en muchos casos resuelve de sobra.

Pero no todo es tan sencillo. A veces tenemos casos de uso donde nuestros APIs nos exponen endpoints que contienen funcionalidad más compleja como subida/descarga de un archivo, realizar un streaming de datos, etc.

Hay muchos casos donde se puede implementar esta funcionalidad en http pero el detalle está en que no es muy sencillo hacerlo, aquí pueden ver un ejemplo.

https://media.giphy.com/media/3o7abL1nxw0AvOK1pu/giphy.gif

Entra Dio

Como siempre, me gusta mostrarles alternativas a los paquetes habituales que usamos y que vean las ventajas, desventajas y por qué vale la pena probar estas opciones en sus futuros desarrollos.

Dio es una alternativa al paquete de http y nos ayuda a incorporar toda esta funcionalidad que les comentaba de manera que no tengamos que invertir tiempo construyendo encima de http para desarrollar funcionalidad.

Su uso es muy similar a http y sólo necesitamos instanciar un objeto de Dio que ya sería nuestro cliente HTTP.

https://gist.github.com/ccc76fb1433b75cfc7fc5b1900804d8b

Un%20mejor%20HTTP%20con%20Dio%200d611cae5ece4856a5ba6ab8e46d61aa/carbon_(1).png

Pueden observar que el único cambio que hice fue cambiar la propiedad body del Response en http por la propiedad data del Response en dio.

Pero bueno, eso es muy básico, ¿no?

Vamos con las ventajas que ofrece este paquete.

BaseOptions

Dio nos permite configurar un objeto con toda la configuración base para interactuar con nuestro API. Este objeto podemos inyectarlo como dependencias de nuestras clases encargadas de los llamados al API (o los APIs).

De paso podemos definir distintos objetos Dio, como les mencionaba, cada uno con una configuración distinta para endpoints o APIs específicos. Pero veamos cómo se hace esta configuración.

https://gist.github.com/07e19ae771ad81b2dcd9026cb2f72473

Si ejecutan este código, van a observar que podemos utilizar de igual manera ese objeto Dio para hacer la misma petición con una configuración de nuestra baseUrl y así al usar el método get sólo debemos especificar el endpoint al que queremos acceder para recuperar esa información.

A como dije, podemos crear varios objetos que contengan BaseOptions distintas por si implementan una arquitectura microservicios donde puedan tener un cliente para cada API que ocupen.

https://gist.github.com/5758519dac3954b99b488f7ad1bb1587

Un%20mejor%20HTTP%20con%20Dio%200d611cae5ece4856a5ba6ab8e46d61aa/carbon_(2).png

Aquí hago un llamado a otro API muy famoso que usan mis amigos de Flutter España, RickAndMortyAPI. En este caso, creo otro cliente y otra configuración para llamar a ese otro endpoint e igual muestro la información de ambos.

Los entusiastas de microservicios van a valorar bastante esta funcionalidad. Pueden chequear más de las opciones por acá.

De paso pueden observar que data funciona idéntico a body cuando queremos acceder a una propiedad específica de nuestro Response.

Concurrencia

Esto igual y no es una funcionalidad propia de Dio pero aprovecho para mencionarla ya que muchos no saben que existe y es de mucha utilidad.

Como vieron en el ejemplo anterior, estoy haciendo 2 peticiones HTTP secuencialmente y a simple vista está bien, tal vez faltaría separar esas funciones cada una por su cuenta para no tener una función principal tan grande. Pero otra forma en la que podemos organizar nuestro código es con un Future.wait.

https://gist.github.com/2d768eb2d43666df3700f8c43f977287

Un%20mejor%20HTTP%20con%20Dio%200d611cae5ece4856a5ba6ab8e46d61aa/carbon%201.png

Con Future.wait podemos enviar la lista de Futures o peticiones que vamos a hacer y este método nos devuelve una lista de respuestas donde podemos acceder a cada una y en este caso simplemente imprimirla en consola.

Descargar archivos

Si tienen proyectos que requieran el uso interno de archivos, es muy probable que requieran descargar algún archivo para guardar en los dispositivos que opere su app.

Esto se puede hacer con http pero nos toma mucho tiempo implementarlo desde cero. Sí, adivinaron, dio nos lo facilita de la siguiente manera:

https://gist.github.com/8c5bc340998f6e453ecb2122bd245da3

Analizando cómo hacer estas descargas, debemos tener -como siempre- nuestro cliente de Dio, la url del archivo que queremos descargar, un directorio temporal que obtenemos utilizando el paquete path_provider, e igualmente un directorio en el dispositivo donde guardes esos archivos que descargues.

Luego con el método asíncrono de download() tenemos toda la comunicación con este archivo alojado en un servidor remoto. Podemos asignar opcionalmente configuración adicional mediante Options, por ejemplo, un [**acceptEncoding**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Accept-Encoding) que nos permite decirle al servidor qué algoritmos de compresión para el contenido soporta nuestro cliente.

Lo más importante acá es que download() nos da acceso a un callback llamado onReceiveProgress que nos dispone dos valores enteros: received y total. El primero nos brinda la cantidad de bytes del archivo que han sido recibidos en el dispositivo y el segundo es la cantidad total de bytes del archivo.

Ahora pueden descargar archivos tranquilamente en sus apps.

Si hay un error en alguna parte de la descarga, dio se detiene y elimina todo lo que llevaba descargado.

Subir archivos

Subir archivos es una petición POST normal con algunos ajustes de por medio.

https://gist.github.com/1bfdb3eb32a894a0007f7607708d66b7

Lo que hacemos en esta parte es usar un archivo del dispositivo, cuya ubicación contenemos -adicionalmente su nombre- en fileName. Y siguiente formamos un objeto FormData, similar al que se utiliza en HTML, que es capaz de guardar la información con un constructor fromMap y si queremos enviar un archivo, que usualmente va a durar un momento en subirse, utilizamos la clase de MultipartFile que contiene un método asíncrono fromFile para subir el archivo.

Al subir el archivo se debe esperar a que MultipartFile termine de ejecutar su método, por eso utilizamos un await.

Igual el POST nos provee los dos callbacks de onReceiveProgress y onSendProgress que nos permiten hacer un cálculo de porcentajes para mostrar información de descarga/subida en nuestra interfaz gráfica.

Algunos consejos extras

No puedo dejar de lado mis temas favoritos: arquitectura y estructura de código. No les voy a hacer largo el artículo explicándoles cómo utilizar dio para sus arquitecturas, sino cómo estructurar una capa de datos basada en dio.

Orientándolo a una arquitectura microservicios donde poseemos distintas APIs para módulos por aparte (o incluso acciones muy puntuales), les recomiendo separar por API que ustedes ocupen. De esta manera pueden tener un cliente para cada API con las BaseOptions de dio que ustedes necesiten.

https://gist.github.com/2df371d3223012ca109d76fc3a6a9f11

Ya teniendo nuestra carpeta de API, podemos separar en 2-3 grupos principales:

  1. Modelos: Son las clases que representan la data que nosotros vamos a enviar o recibir del API.
  2. Parsers: Son clases que abstraen la lógica de conversión del String codificado en JSON a objeto de Dart. Esta misma lógica se podría implementar ya sea dentro del modelo o en un DataSource o DataProvider. No son tan necesarios pero a muchos les gusta utilizarlos.
  3. Datasources/DataProviders: Son los encargados de ejecutar las llamadas a los APIs por medio de un cliente determinado.

Nuestra estructura por carpetas quedaría así:

https://gist.github.com/864d284c62bd233919ca5c52f9f7ab0d

https://media.giphy.com/media/xUA7b8VnHVOMeTawFO/giphy.gif

Lo de siempre...

Si aprendiste algo nuevo y te fue de utilidad, podés compartir este artículo para ayudar a otro/a desarrollador(a) a seguir mejorando su productividad y calidad al escribir aplicaciones con Flutter.

También hay una versión de este mismo artículo en inglés publicado en dev.to. De nada. 🇺🇸

Además, si te gustó este contenido, podés encontrar aún más y seguir en contacto conmigo en mis redes sociales:

  • dev.to - donde publico versiones en inglés de mis artículos.
  • GitHub - donde están mis repositorios de código por si te gustan los ejemplos.
  • LinkedIn - donde conecto profesionalmente.
  • Medium - donde estás leyendo este artículo.
  • Twitter - donde expreso mis ideas cortas y comparto mi contenido.
  • Twitch - donde hago directos informales de los que saco clips con información puntual.
  • YouTube - donde publico los clips que salen de mis directos.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment