Guía introductoria al widget FutureBuilder en Flutter


Avatar de Pedro Cortez

Cuando trabajas con datos asincrónicos en Flutter, es esencial mostrar esa información de manera fluida y reactiva, sin bloquear la interfaz de usuario. El widget FutureBuilder es la herramienta ideal para manejar operaciones puntuales que devuelven un único resultado, como la recuperación de datos desde una API o una base de datos. En esta guía,…


flutter futurebuilder

¿Para qué sirve el widget FutureBuilder?

Como vimos en el artículo anterior, es común tener que manejar datos asincrónicos. Esto sucede, por ejemplo, al recuperar información desde una API, acceder a una base de datos o realizar operaciones costosas en segundo plano. Pero, ¿cómo mostrar esos datos en la interfaz de usuario de manera fluida teniendo en cuenta el tiempo que tardan en recuperarse? Aquí es donde entra en juego el widget FutureBuilder.

El FutureBuilder es una herramienta poderosa en Flutter que permite esperar la finalización de una tarea asincrónica y mostrar el contenido correspondiente al estado de esa tarea. Se basa en el concepto de Future, que representa un dato que estará disponible en algún momento… en el futuro.

El FutureBuilder le permite construir interfaces de usuario basadas en el estado del Future:

  • Mientras el Future está en ejecución (estado pendiente), se puede mostrar un indicador de carga (como un círculo de progreso).
  • Una vez que el Future se resuelve con un resultado (estado completado), se pueden mostrar los datos recuperados.
  • En caso de error (estado fallido), también es posible mostrar un mensaje o una interfaz de error.

¿Cuándo debería usar el widget FutureBuilder?

El FutureBuilder es útil para mostrar contenido cuando necesita realizar una operación asincrónica puntual, es decir, cuando desea recuperar un dato una sola vez. Puede usarlo en escenarios donde una tarea termina con un único resultado final, como recuperar datos desde una API, realizar una consulta a una base de datos o cargar un archivo.

Por ejemplo, puede usarlo para:

  • Realizar una llamada única a una API o base de datos (por ejemplo, cargar el perfil de un usuario o mostrar una lista de artículos).
  • Cargar archivos locales, realizar un cálculo pesado en segundo plano y mostrar el resultado una vez finalizado.
  • Mostrar un resultado por defecto si los datos no se cargan después de la recuperación inicial.

El FutureBuilder es perfecto para tareas puntuales que devuelven un único resultado. Sin embargo, si necesita reaccionar a flujos continuos de datos o eventos que ocurren repetidamente, el StreamBuilder, que presentaré en el próximo artículo, será más adecuado.

Ejemplo de implementación de un FutureBuilder en Flutter

A continuación, se muestra un ejemplo simple del uso del widget FutureBuilder, donde se recupera un nombre y un correo electrónico desde un documento en Firebase para mostrarlos en pantalla:

import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:tutoflutter/firebase_options.dart';

void main() async {
  // Inicialización de Firebase
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp(
    options: DefaultFirebaseOptions.currentPlatform,
  );
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FutureBuilder Firebase Demo',
      home: UserScreen(),
    );
  }
}

class UserScreen extends StatelessWidget {
  // Función asincrónica para obtener datos del usuario desde Firebase
  Future<Map<String, dynamic>?> fetchUserData() async {
    try {
      // Recuperación del documento 'User1' en la colección 'Users'
      DocumentSnapshot doc = await FirebaseFirestore.instance
          .collection('Users')
          .doc('User1')
          .get();
      return doc.data() as Map<String, dynamic>?;
    } catch (e) {
      print('Error al recuperar los datos: $e');
      return null;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Usuario Firebase'),
      ),
      body: Center(
        // Uso del FutureBuilder para manejar el estado asincrónico
        child: FutureBuilder<Map<String, dynamic>?>(
          future: fetchUserData(), // Llamada a la función para recuperar los datos
          builder: (context, snapshot) {
            // Mientras se cargan los datos, se muestra un spinner
            if (snapshot.connectionState == ConnectionState.waiting) {
              return CircularProgressIndicator();
            }
            // En caso de error, se muestra un mensaje de error
            if (snapshot.hasError) {
              return Text('Error: ${snapshot.error}');
            }
            // Si los datos están disponibles, se muestran en pantalla
            if (snapshot.hasData) {
              var userData = snapshot.data!;
              return Text(
                'Nombre: ${userData['name']}\nEmail: ${userData['email']}',
                style: TextStyle(fontSize: 20),
                textAlign: TextAlign.center,
              );
            }
            // Si no hay datos, se muestra un mensaje predeterminado
            return Text('No hay datos disponibles');
          },
        ),
      ),
    );
  }
}

Explicación del código:

  1. Inicialización de Firebase: Antes de usar Firestore, Firebase se inicializa en main() con Firebase.initializeApp().
  2. fetchUserData: Esta función asincrónica obtiene un documento específico de la colección Users en Firestore. Si el documento se encuentra, devuelve los datos como un Map. En caso de error, devuelve null.
  3. FutureBuilder:
    • future: Representa la llamada a la función fetchUserData().
    • builder: Genera la interfaz de usuario basada en el estado del snapshot:
      • Pendiente: Se muestra un indicador de carga.
      • Error: Se muestra un mensaje de error.
      • Datos recuperados: Se muestran los datos del usuario (nombre y correo electrónico).

¿Por qué debo definir un tipo en mi FutureBuilder?

En el ejemplo, el FutureBuilder tiene el tipo <Map<String, dynamic>?>. Esto indica que los datos serán un Map con claves de tipo String y valores de tipo dinámico (dynamic).

El signo de interrogación (?) indica que el Future puede devolver un valor nulo si no existe el documento o si ocurre un error en la solicitud.

Definir el tipo de datos recuperados asegura una gestión estricta y clara de los datos esperados, mejorando la estabilidad y claridad del código. Aunque no es obligatorio, es altamente recomendable.

Conclusión

Con el widget FutureBuilder, ahora puede recuperar datos en línea de manera puntual y mostrarlos en su aplicación Flutter.

¿Pero cómo mostrar contenido en tiempo real de forma dinámica? Por ejemplo, ¿cómo actualizar una lista de compras al agregar o eliminar elementos? Esto se logra con el widget StreamBuilder, que exploraremos en el próximo artículo.

Avatar de Pedro Cortez