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.
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: Sí
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
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
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()