FutureBuilder en Flutter

Maneja operaciones asíncronas y estados de carga de forma elegante

Descargar Ejemplos Completos

¿Qué es FutureBuilder?

El widget FutureBuilder en Flutter es esencial para trabajar con operaciones asíncronas, permitiendo construir la interfaz de usuario en base al estado de un Future.

Waiting
Active
Done
Error

Estados que maneja FutureBuilder:

  • Waiting: El Future no se ha completado
  • Active: El Future está en progreso (con datos opcionales)
  • Done: El Future se completó exitosamente
  • Error: El Future falló con un error
  • None: No hay Future asociado

¿Cuándo usar FutureBuilder?

Llamadas a API

Para cargar datos desde servicios web y APIs REST

Consultas a BD

Operaciones de lectura/escritura en bases de datos

Descarga de Archivos

Descarga y procesamiento de archivos

Cálculos Pesados

Operaciones que requieren tiempo de procesamiento

Implementación Básica

FutureBuilder básico para cargar datos de API

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FutureBuilder Example',
      home: const ApiExample(),
    );
  }
}

class ApiExample extends StatelessWidget {
  const ApiExample({super.key});

  // Future que simula una llamada a API
  Future<String> fetchUserData() async {
    await Future.delayed(const Duration(seconds: 2)); // Simula delay de red
    // En una app real, aquí iría la llamada HTTP real
    return 'Datos del usuario cargados exitosamente';
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('FutureBuilder Example')),
      body: FutureBuilder<String>(
        future: fetchUserData(),
        builder: (context, snapshot) {
          // Verificar el estado del Future
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(
              child: CircularProgressIndicator(),
            );
          } else if (snapshot.hasError) {
            return Center(
              child: Text('Error: ${snapshot.error}'),
            );
          } else if (snapshot.hasData) {
            return Center(
              child: Text('Datos: ${snapshot.data}'),
            );
          } else {
            return const Center(
              child: Text('No hay datos disponibles'),
            );
          }
        },
      ),
    );
  }
}

Propiedades de FutureBuilder

future

El Future que se va a observar y esperar

Tipo: Future<T>?

Requerido: Sí (puede ser null)

builder

Función que construye el widget basado en el snapshot

Tipo: Widget Function(BuildContext, AsyncSnapshot<T>)

Requerido:

initialData

Datos iniciales mientras el Future se completa

Tipo: T?

Por defecto: null

AsyncSnapshot Properties

connectionState

Estado actual de la conexión del Future

Valores: none, waiting, active, done

hasData

Indica si el snapshot contiene datos

Tipo: bool

data

Los datos recibidos del Future

Tipo: T?

hasError

Indica si el Future falló con un error

Tipo: bool

error

El objeto de error si el Future falló

Tipo: Object?

Estados de ConnectionState

ConnectionState.none

No hay Future asociado

future: null

ConnectionState.waiting

Future pendiente, no hay datos aún

!snapshot.hasData

ConnectionState.active

Future en progreso, puede tener datos

snapshot.hasData (opcional)

ConnectionState.done

Future completado (éxito o error)

snapshot.hasData || snapshot.hasError

Ejemplos Visuales

1. Carga de Datos de Usuario

Perfil de Usuario

Cargando datos del usuario...

Error al cargar los datos

FutureBuilder<User>(
  future: userRepository.getUser(),
  builder: (context, snapshot) {
    if (snapshot.connectionState == ConnectionState.waiting) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            CircularProgressIndicator(),
            SizedBox(height: 16),
            Text('Cargando datos del usuario...'),
          ],
        ),
      );
    } else if (snapshot.hasError) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.error, size: 64, color: Colors.red),
            SizedBox(height: 16),
            Text('Error al cargar los datos'),
            SizedBox(height: 16),
            ElevatedButton(
              onPressed: () {
                // Recargar los datos
                setState(() {});
              },
              child: Text('Reintentar'),
            ),
          ],
        ),
      );
    } else if (snapshot.hasData) {
      final user = snapshot.data!;
      return UserProfile(user: user);
    } else {
      return Center(child: Text('No hay datos disponibles'));
    }
  },
)

2. Lista con Datos Iniciales

Lista de Productos
Laptop Gaming - \$1200
Smartphone - \$800
Audífonos - \$150
FutureBuilder<List<Product>>(
  future: productService.getProducts(),
  initialData: const [], // Lista vacía como datos iniciales
  builder: (context, snapshot) {
    final products = snapshot.data ?? [];
    
    if (snapshot.connectionState == ConnectionState.waiting && products.isEmpty) {
      return ListView.builder(
        itemCount: 3,
        itemBuilder: (context, index) {
          return ListTile(
            leading: CircleAvatar(backgroundColor: Colors.grey[300]),
            title: Container(
              height: 16,
              width: 100,
              color: Colors.grey[300],
            ),
            subtitle: Container(
              height: 12,
              width: 60,
              color: Colors.grey[300],
            ),
          );
        },
      );
    }
    
    return ListView.builder(
      itemCount: products.length,
      itemBuilder: (context, index) {
        final product = products[index];
        return ListTile(
          leading: Icon(Icons.shopping_bag),
          title: Text(product.name),
          subtitle: Text('\$${product.price}'),
          trailing: Icon(Icons.arrow_forward_ios),
        );
      },
    );
  },
)

Demo Interactivo de FutureBuilder

Demo FutureBuilder

Listo para ejecutar

Presiona "Ejecutar Future" para comenzar

// El código se generará aquí

Mejores Prácticas

✅ Lo que SÍ debes hacer

  • Usa initialData para mostrar contenido mientras carga
  • Maneja todos los estados: waiting, done, error
  • Usa ConnectionState para estados precisos
  • Proporciona feedback visual durante la carga
  • Implementa reintentos para errores de red

❌ Lo que NO debes hacer

  • No llames setState dentro del builder
  • No crees el Future dentro del build method
  • No ignores el estado de error
  • No uses FutureBuilder para streams (usa StreamBuilder)
  • No olvides manejar el caso de datos nulos

Tip Profesional

Para operaciones que pueden ejecutarse múltiples veces, considera usar FutureBuilder con un Key único que cambie cuando necesites recargar los datos. Esto fuerza la recreación del FutureBuilder y la ejecución del Future nuevamente.

Ejercicios para Practicar

Ejercicio 1: App del Clima

Crea una app que muestre el clima actual usando una API pública.

API sugerida: OpenWeatherMap

Estados a manejar: Loading, datos del clima, error de conexión

Ejercicio 2: Lista de Noticias

Implementa un lector de noticias que cargue artículos desde una API.

Características: Scroll infinito, skeleton loading, pull to refresh

Ejercicio 3: Galería de Imágenes

Crea una galería que cargue imágenes desde un servicio web.

Funcionalidades: Grid de imágenes, carga progresiva, manejo de errores por imagen

Solución de Problemas Comunes

❌ El Future se ejecuta múltiples veces

Solución: Mueve la creación del Future fuera del método build

// ❌ MAL: En el método build future: miFuture() // ✅ BIEN: En initState o variable de clase Future<String> miFuture = obtenerDatos();

❌ No se actualiza cuando cambian los datos

Solución: Usa un Key único o llama a setState con un nuevo Future

FutureBuilder( key: ValueKey(uniqueId), // Fuerza recreación future: miFuture, // ... )

❌ Error: "setState() called during build"

Solución: No llames setState dentro del builder del FutureBuilder

// Usa un callback o método separado onPressed: () => _recargarDatos()

Recursos Adicionales

📚 Documentación Oficial

Consulta la documentación completa de FutureBuilder

Ver Documentación

🎥 Video Tutorial

Aprende con ejemplos prácticos de FutureBuilder

Ver Tutorial

💡 Ejemplos Avanzados

Descubre implementaciones complejas con APIs reales

Explorar Ejemplos