Future Builder: Un pequeño ejemplo

Carlos Millan
3 junio, 2019

. . .

Hola amigos, en este post voy a explicar con un pequeño ejemplo el concepto de Future y como usarlo con un widget llamado FutureBuilder.

Manos a la obra.

Qué es Future?

Un Future representa una manera de obtener un valor en el futuro. Cuando una función que retorna un Future es invocado, dos cosas suceden:

  1. La función pone en cola el trabajo a realizar y devuelve un objeto Future incompleto.
  2. Más adelante, cuando hay un valor disponible, el objeto Future se completa con ese valor (o con un error; vas adelante lo veremos).

En una app de Flutter por ejemplo, cuando estamos esperando un resultado, tenemos que tener en cuenta de que este proceso toma cierto tiempo y por lo tanto detiene la ejecución del programa hasta que termine la tarea que se está realizando en ese momento y así continuar con las siguientes instrucciones. Esto pasa porque el proceso se está ejecutando en el hilo principal de la app y al tener que esperar un resultado, represa las demás instrucciones a ejecutar, lo cual produce un congelamiento.

Dart tiene librerías que están llenas de funciones que devuelven objetos Future o Stream. Estas funciones son asíncronas: ellas retornan después de configurar una operación que puede llevar mucho tiempo (por ejemplo operaciones I/O), sin esperar a que esa operación se termine.

Para eso vamos a utilizar el soporte de Asincronía mediante el empleo de async/await de Dart.

El código que usa Async y await es asíncrono, pero este luce mucho como código síncrono. Por ejemplo, aquí tenemos un pequeño código que usa await para esperar por el resultado de una función asíncrona:

await buscarVersion();

Para usar await, el código debe estar en una función marcada como async:

Future verificarVersion() async {
  var version = await buscarVersion();
  // Hacer algo con version
}

Es este código, mientras se está ejecutando el método buscarVersion() la app sigue su ejecución sin ningún problema. Cuando buscarVersion() completa su trabajo, le pasa el resultado a la variable versión, el cual puede ser usado por ejemplo para mostrar su valor en pantalla.

El ejemplo que vamos a realizar usaremos un widget llamado FutureBuilder.

Qué es FutureBuilder?

FutureBuilder es un widget de Flutter el cual se basa en la última instantánea de la iteracción con un Future. También permite pintar en pantalla los datos obtenidos a través de la instantánea.

Ejercicio

Creamos un nuevo proyecto flutter y luego abrimos el archivo pubspec.yaml. Luego agregamos una dependencia llamada http, el cual nos permite trabajar con cualquier api:

dependencies:
  flutter:
    sdk: flutter
  http: ^0.12.0+2

Luego, si usas VSCode, al guardar los cambios, automáticamente obtiene el paquete de internet. Si usas Android Studio o IntelliJ, haces clic en Packages get.

Luego, abrimos el archivo main.dart y borramos todo el contenido y colocamos lo siguiente:

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Ejemplo de Future Builder',
      home: HomePage(),
    );
  }
}

Ahora, vamos a generar HomePage(), para eso después de la clase MyApp creamos otra clase llamada HomePage (para fines de ese ejemplo creamos las clases y todo en el mismo archivo main.dart para no extenderme, pero lo recomendable es crear la clase MyApp y HomePage en archivos separados para mejor organización):

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Future Builder Example'),
      ),
      body: Center(
        
      ),
    );
  }
}

En este ejercicio vamos a usa una api de Star Wars llamada SWAPI el cual podemos mostrar los personajes, vehículos y planetas de esta famosa serie de películas de Guerra de las Galaxias.

Bueno, a continuación vamos a crear un Future que nos permite conectarnos con la api y traernos los datos que nos devuelve mediante uno de los endpoints que tiene la api, por ejemplo el endpoint de people. Antes de esto, vamos a importar unas librerías de Dart para poder manejar el async/await y el manejo de Json:

import 'package:http/http.dart' show get;
import 'dart:convert' as convert;

Nota: Se preguntarán porqué coloqué show get en el import de http. Bueno, como vamos a obtener únicamente los datos de la api entonces ocuparemos el método get. Esto permite traernos únicamente el método get y no todos los que tiene la librería http.

Ahora si, vamos a crear un Future llamado getPeople() así:

Future<Map<String, dynamic>> getPeople() async {
    String url = 'https://swapi.co/api/people/1/';
    final response = await get(url);
    var res;
    if (response.statusCode == 200) {
      final jsonResponse = convert.jsonDecode(response.body);
      res = jsonResponse;
    } else {
      res = 'Fallo en la petición con código de error: ${response.statusCode}';
    }
    return res;
  }

Este Future lo que hace es obtener la url para obtener el personaje número 1. Si miramos la página web de la api, veremos que al consultar este endpoint, nos devuelve una especie de mapa o diccionario con una claves y valores. Las claves por lo general son de tipo String, los valores pueden ser de cualquier tipo, por lo tanto en el Future colocamos <Map<String, dynamic>> porque el future va a devolver un Mapa. Si por ejemplo el api nos devolviera una lista de Strings entonces lo colocaríamos así: Future<List<String>>. Seguido de esto va el nombre del future que en este caso se llama getPeople().

Como vimos anteriormente, este método tiene que comunicarse con la api y esperar a que ella nos devuelva el dato que necesitamos, lo cual es necesario convertirla en asíncrona, por lo tanto debemos de colocarle la palabra async después de nombre del método:

Future<Map<String, dynamic>> getPeople() async {...

A continuación, con el método get() de http le pasamos la url del endpoint y le asignamos el resultado a una variable llamada response. Si este proceso nos arroja un código de estado 200 entonces convertimos a json el body que nos da la respuesta y asignamos el resultado a una variable llamada por ejemplo jsonResponse. Si por el contrario hubo un error entonces a esa misma variable le asigno un mensaje de que hubo un fallo en la petición. Por último retorno la variable jsonResponse.

Future<Map<String, dynamic>> getPeople() async {
    String url = 'https://swapi.co/api/people/1/';
    final response = await get(url);
    var jsonResponse;
    if (response.statusCode == 200) {
      jsonResponse = convert.jsonDecode(response.body);
    } else {
      jsonResponse = 'Fallo en la petición con código de error: ${response.statusCode}';
    }
    return jsonResponse;
  }

Hay que tener en cuenta que el tipo de dato que estamos devolviendo coincida con el que fijamos en la cabecera del Future (<Map<String, dynamic>>), sino nos produce un error.

En este ejemplo, la api nos devuelve la siguiente información:

{
	"name": "Luke Skywalker",
	"height": "172",
	"mass": "77",
	"hair_color": "blond",
	"skin_color": "fair",
	"eye_color": "blue",
	"birth_year": "19BBY",
	"gender": "male",
	"homeworld": "https://swapi.co/api/planets/1/",
	"films": [
		"https://swapi.co/api/films/2/",
		"https://swapi.co/api/films/6/",
		"https://swapi.co/api/films/3/",
		"https://swapi.co/api/films/1/",
		"https://swapi.co/api/films/7/"
	],
	"species": [
		"https://swapi.co/api/species/1/"
	],
	"vehicles": [
		"https://swapi.co/api/vehicles/14/",
		"https://swapi.co/api/vehicles/30/"
	],
	"starships": [
		"https://swapi.co/api/starships/12/",
		"https://swapi.co/api/starships/22/"
	],
	"created": "2014-12-09T13:50:51.644000Z",
	"edited": "2014-12-20T21:17:56.891000Z",
	"url": "https://swapi.co/api/people/1/"
}

Como ven, nos regresa un mapa o diccionario con clave y valor.

Future Builder

Ahora seguimos con la creación del FutureBuilder para así traernos los datos de la api y pintarlos en pantalla. FutureBuilder tiene entre sus propiedades dos usadas regularmente: future y builder. La propiedad future recibe un Future del cual obtener los datos de la api, en este caso colocamos la función getPeople() que creamos anteriormente. La propiedad builder nos permite pintar la información en pantalla utilizando la instantánea actual. Recibe un callback con un context y un snapshot. ConnectionState es una clase que nos permite verificar el estado de la conexión a una computación asíncrona. Por ejemplo, si la ejecución del future se completó (ConnectionState.done) entonces se pinta la información en pantalla, sino entonces que muestre mientras tanto un CircularProgressIndicator para informarle al usuario que la información está siendo cargada.

En la clase HomePage, nos vamos al body del scaffold y colocamos el FutureBuilder así:

FutureBuilder(
  future: getPeople(),
  builder: (BuildContext context, AsyncSnapshot snapshot) {
    if(snapshot.connectionState == ConnectionState.done) {
      return personajeInfo(snapshot);
    } else {
      return CircularProgressIndicator();
    }
  },
),

En este caso, si se ha completado el proceso del future entonces retornará un widget con la información a mostrar en pantalla. Por lo general, es buena idea separa este código en un bloque aparte para tener todo orgnizado. Debajo de la clase HomePage creamos un widget llamado personajeInfo, el cual recibe como parámetro la instantánea para así dibujar los widgets para mostrar la información:

Widget personajeInfo(snapshot) {
  return Padding(
    padding: const EdgeInsets.all(8.0),
    child: Column(
      children: <Widget>[
        Row(
          children: <Widget>[
            Text(
              'Nombre:',
              style: TextStyle(
                fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(width: 5),
            Text(
              snapshot.data['name'],
              style: TextStyle(fontSize: 18),
            )
          ],
        ),
        SizedBox(
          height: 5,
        ),
        Row(
          children: <Widget>[
            Text(
              'Estatura:',
              style: TextStyle(
                fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(width: 5),
            Text(
              snapshot.data['height'],
              style: TextStyle(fontSize: 18),
            )
          ],
        ),
        SizedBox(
          height: 5,
        ),
        Row(
          children: <Widget>[
            Text(
              'Color de Pelo:',
              style: TextStyle(
                fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(width: 5),
            Text(
              snapshot.data['hair_color'],
              style: TextStyle(fontSize: 18),
            )
          ],
        ),
        SizedBox(height: 5),
        Row(
          children: <Widget>[
            Text(
              'Color de Ojos:',
              style: TextStyle(
                fontSize: 18, fontWeight: FontWeight.bold),
            ),
            SizedBox(width: 5),
            Text(
              snapshot.data['eye_color'],
              style: TextStyle(fontSize: 18),
            )
          ],
        ),
      ],
    ),
  );
}

Es largo el código pero se puede crear en otro archivo para que quede más organizado.

En el código anterior vemos que con la instrucción snapshot.data[] vamos accediendo a las claves de la información que nos viene de la api, la cual se coloca dentro de los corchetes.

Ejecutamos el proyecto y este es el resultado:

Es un ejemplo sencillo y diseño ui modesto, pero lo suficiente para entender el uso de futures y el widget FutureBuilder de Flutter.

Hasta la próxima.

39

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Comunidades en Español