¿Te vas sintiendo más cómodo con Flutter? Estoy seguro de que sí.
Vamos a seguir con nuestra aplicación, pero antes de nada, vamos a renombrar algunos archivos para que todo tenga un poco más de sentido.
Nuestro archivo home_page.dart lo llamaremos pokemon_list_page.dart y el widget pasará a llamarse PokemonListPage. Según el IDE que estés usando, no tendrás que hacer nada más porque se encargará de refactorizar el código correctamente. En caso contrario, deberás cambiar el nombre de la importación en el fichero main.dart.
Tipando nuestra app
Como mencionamos en el post anterior, el uso de dynamic debe ser el mínimo ya que al tener un código tipado hace que tengamos menos errores de código.
Para ello, lo primero que haremos será crear los modelos que vamos a usar. Pero, ¿y cómo hacemos esto?.
Necesitamos conocer cuál es la respuesta de la API para crear el modelo. Para ello, iremos a la web de la API para consultar la respuesta que obtendremos. En el caso de la llamada que estamos usando hasta ahora , podemos ver que nos devuelve un objeto que a su vez contiene la propiedad results que es a su vez un array de objetos con cada Pokémon. Necesitamos crear un modelo a partir de esta respuesta. Existen herramientas online para hacer este trabajo de manera rápida, pero nosotros los haremos a mano (aunque facilitaré el código).
Modelos en dart
No vamos a profundizar mucho en esto ya que los modelos son usados en cualquier lenguaje de programación e imagino que, si has llegado hasta aquí, tienes conocimientos sobre ello, pero en términos simples, los modelos son solo clases que nos ayudan a determinar la estructura de los datos, por ejemplo, respuestas de una API. Todos conocemos el concepto de clases en la Programación Orientada a Objetos, similar a eso, podemos declarar las variables, sus tipos de datos y podemos escribir algunos métodos para agregar alguna funcionalidad adicional.
Al definir los modelos, estamos definiendo estrictamente el tipo de datos de los valores que se utilizarán, lo que hace que el código sea menos propenso a errores, ya que la mayoría de los errores se pueden manejar durante el tiempo de compilación.
Por otro lado, estos modelos, a su vez, tienen normalmente 2 constructores: fromJson() y toJson().
Usaremos el constructor fromJson() para inicializar los valores del modelo y toJson() para convertir el modelo a un Map.
Puedes obtener más información acerca de los modelos aquí.
En nuestro proyecto, crearemos una carpeta llamada models que contendrá todos los modelos que usaremos. Para empezar, crearemos un nuevo fichero al que llamaremos pokemon_llist_response_model.dart e iremos creando el modelo de manera que el resultado final debería quedar así.
Podemos ver que en la misma clase, tenemos otra llamada Pokemon. La tenemos en el mismo fichero ya que forma parte de la respuesta y solo es usada aquí.
Dio para peticiones HTTP
Bien, ahora que tenemos nuestro primer modelo definido, vamos a usarlo en nuestro servicio a la hora de consumir la API que estamos usando.
Habíamos desarrollado nuestro servicio usando la librería oficial http la cual resuelve nuestros requisitos ahora mismo. 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.
Dio es una alternativa al paquete de http y nos ayuda a incorporar todo lo comentado (incluso más) 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.
Para instalar este cliente, solo debemos añadirlo a nuestro pubspec.yaml o ejecutar el siguiente comando:
flutter pub add dio
Una vez añadido, vamos a refactorizar nuestro servicio de forma que quedaría de así:
Como podemos ver, con Dio ya no es necesario el uso de jsonDecode y lo más importante, podemos tipar con nuestros modelos la respuesta de manera fácil.
Ahora que ya tenemos la respuesta tipada, vamos a cambiar nuestra vista para mostrar correctamente los datos, pero antes, como mencioné en el post anterior, vamos a gestionar el estado de la petición para, por ejemplo, mostrar un 'loading' mientras esperamos la respuesta. Al hacer uso de FutureBuilder para construir la vista, en el snapshot que nos devuelve, podemos acceder a la propiedad connectionState para ver en todo momento el estado de la misma de manera que podemos comprobar si la respuesta está en espera y mostrar un loading de la siguiente forma:
Ahora, se mostrará un loading mientras se realiza la petición y una vez que tengamos la respuesta la mostraremos de la siguiente forma:
El resultado será exactamente el mismo que teníamos antes pero ahora lo tenemos todo tipado y por lo tanto, tenemos nuestro código más optimizado y menos propenso a errores.
Sound Null Safety
Seguramente, os preguntaréis que es eso del símbolo de exclamación al final de una propiedad o el de interrogación en la declaración de variables, ¿verdad?.
Esto es debido a que dart, desde su versión 2.9 es Null Safety.
Dart presenta Sound Null Safety como una función de lenguaje y aporta tres beneficios principales:
- Podemos escribir código seguro para valores nulos con sólidas garantías de tiempo de compilación . Esto nos hace productivos porque Dart puede decirnos cuando estamos haciendo algo mal.
- Podemos declarar más fácilmente nuestra intención . Esto conduce a una API que se autodocumentan y son fáciles de usar.
- El compilador de Dart puede optimizar nuestro código, lo que resulta en programas más pequeños y rápidos.
En este post nos hemos centrado en refactorizar nuestro código para que sea mas estable, escalable y menos propenso a errores.
En siguientes posts, veremos como ver los detalles de cada pokémon e iremos añadiendo un poco de estilo a nuestra app.