FutureBuilder in Flutter
Elegantly handle asynchronous operations and loading states
Download Complete ExamplesWhat is FutureBuilder?
The FutureBuilder widget in Flutter is essential for working with asynchronous operations, letting you build the user interface based on a Future's state.
States managed by FutureBuilder:
- ✅ Waiting: The Future hasn't completed
- ✅ Active: The Future is in progress (with optional data)
- ✅ Done: The Future completed successfully
- ✅ Error: The Future failed with an error
- ✅ None: There's no associated Future
When to use FutureBuilder?
API Calls
To load data from web services and REST APIs
Database Queries
Read/write operations in databases
File Downloads
Downloading and processing files
Heavy Computations
Operations that require processing time
Basic Implementation
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'),
);
}
},
),
);
}
}
Properties of FutureBuilder
future
The Future that will be observed and awaited
Type: Future<T>?
Required: Yes (can be null)
builder
Function that builds the widget based on the snapshot
Type: Widget Function(BuildContext, AsyncSnapshot<T>)
Required: Yes
initialData
Initial data while the Future completes
Type: T?
Default: null
AsyncSnapshot Properties
connectionState
Current connection state of the Future
Values: none, waiting, active, done
hasData
Indicates whether the snapshot contains data
Type: bool
data
The data received from the Future
Type: T?
hasError
Indicates whether the Future failed with an error
Type: bool
error
The error object if the Future failed
Type: Object?
States of ConnectionState
ConnectionState.none
There's no associated Future
future: null
ConnectionState.waiting
Future pending, no data yet
!snapshot.hasData
ConnectionState.active
Future in progress, may have data
snapshot.hasData (optional)
ConnectionState.done
Future completed (success or error)
snapshot.hasData || snapshot.hasError
Visual Examples
1. Loading User Data
Loading user data...
Error loading the data
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('Loading user data...'),
],
),
);
} 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('Retry'),
),
],
),
);
} else if (snapshot.hasData) {
final user = snapshot.data!;
return UserProfile(user: user);
} else {
return Center(child: Text('No hay datos disponibles'));
}
},
)
2. List with Initial Data
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),
);
},
);
},
)
Interactive Demo of FutureBuilder
Demo FutureBuilder
Ready to run
Press "Run Future" to start
// El código se generará aquí
Best Practices
✅ What you SHOULD do
- Use initialData to show content while loading
- Handle all states: waiting, done, error
- Use ConnectionState for precise states
- Provide visual feedback during loading
- Implement retries for network errors
❌ What you should NOT do
- Don't call setState inside the builder
- Don't create the Future inside the build method
- Don't ignore the error state
- Don't use FutureBuilder for streams (use StreamBuilder)
- Don't forget to handle the null data case
Pro Tip
For operations that can run multiple times, consider using FutureBuilder with a unique Key that changes when you need to reload the data. This forces the FutureBuilder to be recreated and the Future to run again.
Exercises to Practice
Exercise 1: Weather App
Create an app that shows current weather using a public API.
Suggested API: OpenWeatherMap
States to handle: Loading, weather data, connection error
Exercise 2: News List
Implement a news reader that loads articles from an API.
Features: Infinite scroll, skeleton loading, pull to refresh
Exercise 3: Image Gallery
Create a gallery that loads images from a web service.
Features: Image grid, progressive loading, per-image error handling
Common Troubleshooting
❌ The Future runs multiple times
Solution: Move the Future's creation outside the build method
// ❌ WRONG: In the build methodfuture: miFuture()
// ✅ RIGHT: In initState or a class variableFuture<String> miFuture = obtenerDatos();
❌ Doesn't update when the data changes
Solution: Use a unique Key or call setState with a new Future
FutureBuilder(
key: ValueKey(uniqueId), // Fuerza recreación future: miFuture,
// ...
)
❌ Error: "setState() called during build"
Solution: Don't call setState inside the FutureBuilder's builder
// Usa un callback o método separadoonPressed: () => _recargarDatos()