FutureProvider
FutureProvider is a provider that exposes the result of an asynchronous operation as an AsyncValue, making it ideal for one-time asynchronous data fetching.
What is it?
FutureProvider is designed for asynchronous operations that produce a single value.
Unlike Provider, which returns a value immediately, FutureProvider waits for a Future to complete before exposing its result.
The exposed value is wrapped in an AsyncValue, allowing Riverpod to automatically manage:
- Loading state
- Success state
- Error state
Common use cases include:
- API requests
- Database queries
- Reading local files
- Loading configuration
- Fetching user data
Why does it exist?
Managing asynchronous state manually often involves multiple variables.
For example:
bool isLoading = true;
User? user;
Object? error;
Then you need to update these variables throughout the request lifecycle.
FutureProvider simplifies this by automatically handling:
- Request execution
- Loading state
- Success state
- Error state
- Caching
- Sharing results across multiple consumers
This reduces boilerplate and makes asynchronous code more declarative.
Syntax
Creating a FutureProvider
final userProvider = FutureProvider<User>((ref) async {
return repository.fetchUser();
});
Explanation:
FutureProvider<T>manages aFuture<T>.- The provider begins executing when first accessed.
- The result is exposed as an
AsyncValue<User>.
Reading the Provider
class UserPage extends ConsumerWidget {
const UserPage({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final user = ref.watch(userProvider);
return user.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text(error.toString()),
data: (user) => Text(user.name),
);
}
}
Explanation:
ref.watch()returns anAsyncValue<User>.when()handles loading, success, and error states.
Refreshing the Data
ref.refresh(userProvider);
Explanation:
- Clears the cached value.
- Executes the future again immediately.
- Returns a new
AsyncValue.
Invalidating the Provider
ref.invalidate(userProvider);
Explanation:
- Removes the cached result.
- The future runs again the next time the provider is accessed.
Mental Model
Think of FutureProvider as an asynchronous pipeline.
FutureProvider
│
▼
Execute Future
│
▼
AsyncValue
┌──────┼──────┐
▼ ▼ ▼
Loading Data Error
Riverpod automatically transitions between these states as the future progresses.
Examples
Fetching a User
final userProvider = FutureProvider<User>((ref) async {
return repository.fetchUser();
});
Explanation:
- Fetches user data from a repository.
- Result is cached until invalidated or refreshed.
Loading Configuration
final configProvider = FutureProvider<AppConfig>((ref) async {
return AppConfig.load();
});
Explanation:
- Loads configuration asynchronously.
- Multiple consumers share the same cached result.
Database Query
final productsProvider =
FutureProvider<List<Product>>((ref) async {
return database.getProducts();
});
Explanation:
- Retrieves products from a database.
- Widgets rebuild automatically when the future completes.
HTTP Request
final weatherProvider =
FutureProvider<Weather>((ref) async {
return api.fetchWeather();
});
Explanation:
- Performs an HTTP request.
- Automatically exposes loading, data, and error states.
AsyncValue States
A FutureProvider always returns an AsyncValue.
Future Starts
│
▼
AsyncLoading
│
▼
Future Completes
│
┌────┴────┐
▼ ▼
Data Error
Possible states:
AsyncLoadingAsyncDataAsyncError
These states are discussed in detail in the AsyncValue section.
Caching Behavior
A FutureProvider executes only once while its value remains cached.
Widget A
│
▼
FutureProvider
▲
│
Widget B
Both widgets share:
- The same request
- The same loading state
- The same result
- The same error
Riverpod does not execute multiple identical requests unless the provider is refreshed or invalidated.
FutureProvider vs Provider
| Feature | Provider | FutureProvider |
|---|---|---|
| Synchronous value | ✅ | ❌ |
| Asynchronous value | ❌ | ✅ |
Returns AsyncValue |
❌ | ✅ |
| Loading state | ❌ | ✅ |
| Error handling | ❌ | ✅ |
| Caching | ✅ | ✅ |
When to Use
Use FutureProvider when:
- Fetching API data
- Reading from a database
- Loading configuration
- Reading files
- Executing one-time asynchronous operations
- Displaying asynchronous data without complex business logic
When NOT to Use
Avoid FutureProvider when:
- The data needs to be modified.
- The request should support retry or mutation logic.
- Business logic is required.
- State changes after the initial load.
Instead, use:
| Scenario | Recommended Provider |
|---|---|
| Mutable async state | AsyncNotifierProvider |
| Continuous updates | StreamProvider |
| Simple synchronous value | Provider |
Best Practices
- Keep asynchronous logic inside repositories or services.
- Return immutable models.
- Use
when()to handle loading, data, and error states. - Refresh or invalidate providers when data becomes stale.
- Use
AsyncNotifierProviderwhen asynchronous state needs to change after loading.
Common Mistakes
1. Returning a Future from Provider
❌ Wrong
final userProvider = Provider((ref) async {
return repository.fetchUser();
});
Why it's wrong:
Provideris intended for synchronous values.- It does not manage asynchronous state.
✔ Correct
final userProvider = FutureProvider((ref) async {
return repository.fetchUser();
});
2. Ignoring Loading and Error States
❌ Wrong
final user = ref.watch(userProvider);
Text(user.value!.name);
Why it's wrong:
- The future may still be loading.
- It may have failed.
✔ Correct
user.when(
loading: () => const CircularProgressIndicator(),
error: (error, stack) => Text(error.toString()),
data: (user) => Text(user.name),
);
Handle all possible states.
3. Using FutureProvider for Mutable Data
❌ Wrong
final todosProvider =
FutureProvider<List<Todo>>((ref) async {
return repository.fetchTodos();
});
// Later...
// Add or remove todos directly
Why it's wrong:
FutureProvideris intended for one-time asynchronous values.- Managing updates becomes difficult.
✔ Correct
Use AsyncNotifierProvider when the data needs to support create, update, or delete operations.
Recommendation
Use
FutureProviderfor read-only asynchronous data.If the data needs to be modified after loading (such as adding, editing, or deleting items), prefer
AsyncNotifierProvider.
Related APIs
- AsyncValue
- AsyncNotifierProvider
- Provider
- StreamProvider
- ref.watch()
- ref.refresh()
- ref.invalidate()
Summary
FutureProvider is Riverpod's solution for one-time asynchronous operations. It executes a Future, exposes the result as an AsyncValue, and automatically manages loading, success, error handling, caching, and shared requests. It is the ideal choice for read-only asynchronous data such as API calls, database queries, and configuration loading.