Y por fin, después de ver toda la teoría introductoria a Flutter y Dart, empecemos con lo bueno. Hagamos una app!
Introducción
Para poner en práctica todo lo que hemos aprendido, vamos a realizar una Pokedex con Flutter. Una app que liste y muestre los detalles de los Pokémons. Para ello, haremos uso de la API https://pokeapi.co/ y con lo cual, aprenderemos a consumir una API.
Para esta app, nos centraremos en desarrollar algo básico por lo que no tendremos en cuenta la gestión de estado ni arquitectura. Estos temas serán tratados en posteriores posts.
Paso 1: Hacer limpieza
Para empezar, cuando creamos nuestra app, se crea con un ejemplo de un contador. Lo primero que tendremos que hacer será limpiar todo ese código auto generado para así poder empezar a crear nuestra app desde cero. Para ello, limpiaremos todo el código del archivo main.dart y a continuación, siempre y cuando hayamos instalado las extensiones recomendadas, escribiremos el comando mateapp. Este comando, nos genera de manera automática el famoso Hello World de cualquier lenguaje de programación.
Paso 2: Estructura del proyecto.
A continuación, vamos a generar la estructura de nuestro proyecto. Para ello, bajo la carpeta lib, crearemos 2 carpetas nuevas: pages y services. Dentro de la carpeta pages, crearemos otra carpeta que la llamaremos home y dentro de la misma crearemos un nuevo fichero que se llamará home_page.dart y la cuál será la página de inicio de nuestra app. Dentro de la carpeta services, crearemos otro fichero y lo llamaremos, por ejemplo, pokedex_service.dart y el cual se encargará de consumir la API.
Teniendo ya nuestro archivo main.dart listo, cambiaremos el título (title) de la app, el título de la AppBar y cambiaremos el body para que sea nuestra home_page.dart. Después de estos cambios, debería quedar así.
Paso 3: Generando la home
Hemos cambiado el body para que apunte a nuestra home por lo que, ahora, deberemos empezar a crear la misma.
Para ello, al igual que tenemos un comando para generar el archivo main, también tenemos otro para generar, digamos, una vista. Como aprendimos en los anteriores posts, tenemos 2 tipos de widgets: StatelessWidget y StatefulWidget. Tenemos 2 comandos para generar cada uno de estos widgets: statelessW y statefulW.
Entrando un poco en las buenas prácticas de Flutter:
Se recomienda usar Stateless widget siempre y cuando sea posible. El motivo de esto es que, en este tipo de widgets la vista solo se renderiza una única vez mientras que, en un StatefulWidget se renderiza cada vez que cambia el valor de cualquier propiedad del mismo.
Usaremos entonces el comando statelessW y como nombre, pondremos HomePage. En este momento, os habréis dado cuenta que, el código contiene muchos errores y es que, aunque hemos generado nuestro widget, necesitamos indicar desde donde se está importando todo ese código que se ha generado. Para ello, al inicio del fichero, añadiremos la siguiente línea:
import 'package:flutter/material.dart';
Ahora, ya tenemos nuestra home lista para empezar lanzar nuestra app en el emulador. Pero antes, esta vista no mostrará nada ya que no hemos añadido ningún contenido. Vamos a cambiar un poco el código para ver que realmente todo esto está funcionando. Para ello, modificaremos el return del método build, de manera que quede así:
En este momento, si lanzamos nuestra app como ya vimos en anteriores posts, veremos el siguiente resultado:
Paso 4: Generando el servicio
Antes de continuar modificando la home, vamos a generar nuestro servicio para así, ver cuál es la respuesta de la API y ver la manera más óptima de mostrar esa información.
Existen librerías de terceros para hacer llamadas HTTP pero os voy a mostrar cómo hacerlo con la propia librería que se incluye con Flutter.
Empezaremos importando 2 librerías que vamos a necesitar para consumir cualquier API. Son las siguientes:
import 'dart:convert';
import 'package:http/http.dart' as http;
Como vemos, en la segunda importación, he usado 'as' http. Esto se hacer para luego referirse a la misma con el nombre de http.
A continuación, empezaremos creando una clase a la que le pondremos el nombre de PokedexService y añadiremos el siguiente contenido:
Tranquilos, lo sé. Demasiada información de golpe. Vamos a explicar cada parte del código que hemos añadido.
Lo primero que hemos hecho, es almacenar la url que vamos a consumir en una variable. Como hemos visto, dart es un lenguaje tipado por lo que es recomendable y de buenas prácticas, tipar todas nuestras variables. En este caso, es un String. Esta url la he obtenido de la API, la cual os invito a jugar con ella desde su página web.
Si tienes experiencia en programación, sabrás que cualquier llamada a una API, se hace manera asíncrona y para los que no tenéis experiencia, esto quiere decir que, al hacer la llamada la respuesta no llega de manera inmediata, sino que no sabemos cuánto tiempo va a tardar en llegar la misma.
En Flutter, esto se hace mediante el uso de Future y entre brackets, el tipo de datos que vamos a recibir. Como en estos momentos no sabemos de qué tipo será la respuesta, indicaremos que será de tipo dynamic, con el cual indicamos que podrá ser de cualquier tipo.
Po supuesto, al ser asíncrono, haremos uso de async-await aunque también podríamos hacerlo basándonos en promesas.
Una vez que le hemos dado nombre a nuestro método (getPokemonList), y especificando que este método será asíncrono con la palabra reservada async, esperaremos, con await, el resultado de realizar una petición GET a la url que hemos almacenado antes en la propiedad url. El método get de la librería espera un dato de tipo Uri y es por eso por lo que con Uri.parse(url) estamos convirtiendo nuestro String en un dato de tipo de Uri.
La respuesta de esta petición, además de la información que estamos esperando, contiene muchísima información más que no la vamos a necesitar. Para ello, con el uso de jsonDecode (de la librería convert) vamos a extraer el cuerpo (body) de la respuesta que es el que contiene la información que necesitamos.
Por último, devolvemos la información para que pueda ser consumida.
Paso 5: Mostrando la respuesta
Ahora que ya tenemos nuestro servicio y sabiendo que el método que hemos creado devuelve un Future, para mostrar la información que nos devolverá, haremos uso de Future.builder.
Cambiaremos el contenido de nuestra home para adaptarlo a lo mencionado anteriormente. Tenemos otro comando para generar este código de forma automática: futureBldr. Cambiaremos el return del build por el resultado de este comando.
Ahora necesitamos indicarle, cuál será el Future al que debemos esperar, cuál será la información inicial y que debemos hacer con esa información. Al cambiar todo esto, nuestra home quedará de la siguiente forma:
El método builder recibe 2 parámetros. El primero es el context, y el segundo es la información que estamos recibiendo de la API que al venir de un Future, es de tipo AsyncSnapshot. Ahora bien, para tratar este tipo de datos, previamente necesitaremos saber cómo será la respuesta de la API y para ello, si pegamos en el navegador la url que tenemos en el servicio podremos ver que será un objeto el cual contiene una propiedad llamada results que a su vez es un array de objetos que representa la lista de Pokémons. Una vez que sabemos esto, vamos a almacenar esta lista en un variable dentro del método builder de la siguiente forma:
List<dynamic> pokemons = snapshot.data['results'];
Accedemos a la propiedad data del snapshot ya que este objeto además de la información, también nos ofrece, entre otras cosas, el estado de la petición.
Ahora que sabemos que tenemos una lista de objetos, ¿Cuál será la mejor forma de mostrar esto en nuestra home?.
Pues Flutter lo tiene todo muy pensado y nos da la función de List.builder la cual se encarga de mostrar de forma óptima una lista de datos. Y como no, también tenemos un comando para realizar esto: listViewB.
A este método, entre muchas otras cosas, como mínimo tenemos que indicarle cual será la longitud del array que queremos mostrar en la propieda itemCount y el método builder que nos permitirá mostrar la información.
Por último, dentro de este builder, haremos uso del widget ListTile al cual, entre otras propiedades y usando el index, le indicaremos el title que será cada uno de los nombres de Pokémons que tenemos en nuestro array.
Si ya tenías la aplicación corriendo en el emulador, habrás visto todos estos cambios en tiempo de ejecución. En caso contrario, si volvemos a ejecutar la aplicación podremos ver nuestra lista de Pokémons.
Tip: vemos que los nombres vienen todo en minúsculas. Vamos a crearnos un pequeño método que nos capitalice cada uno de los nombres. Declararemos el método fuera del build de nuestro widget y tendrá el siguiente contenido:
Y ahora sí, podemos ver nuestra lista de Pokémons perfectamente renderizada en nuestra home.
En el siguiente los próximos posts veremos como tipar de manera correcta lo que hasta ahora tenemos cómo tipo dynamic, veremos como gestionar el estado de las peticiones HTTP, como ver los detalles de cada uno de los Pokémons y seguiremos dándole estilo a la aplicación.