Provider Taxonomy & Decision Trees
🎯 Learning Objective
Master the complete taxonomy of Riverpod 3 providers (Provider, FutureProvider, StreamProvider, Notifier, AsyncNotifier, and Family) and learn how to select the exact provider variant using production decision trees.
📋 Prerequisites
- Completion of Phase 2: Core Concepts & Ref Lifecycle.
1. The Master Provider Decision Tree
Do not guess which provider to use. Follow this architectural decision tree to determine the optimal provider type for any feature:
AsyncNotifier• NO (Read-only Future): Use
FutureProvider• NO (Realtime Stream): Use
StreamProvider
Notifier• NO (Read-only Dependency / Service): Use
Provider
2. Detailed Provider Breakdown
PURPOSE: Exposes a read-only synchronous object or service instance (e.g. ApiClient, DateFormatter, Repository).
WHEN TO USE: Injecting stateless repositories, utilities, or derived calculations from other providers.
WHEN NOT TO USE: Storing mutable user state that changes over time.
final apiClientProvider = Provider<ApiClient>((ref) => ApiClient());
PURPOSE: Executes a single Future and wraps the result in an AsyncValue<T> (data, loading, error).
WHEN TO USE: Read-only network requests like fetching app configurations or static public lists.
WHEN NOT TO USE: Complex features requiring user action mutations (use AsyncNotifier instead).
final configProvider = FutureProvider<AppConfig>((ref) async {
return ref.watch(apiClientProvider).fetchConfig();
});
PURPOSE: Subscribes to a Dart Stream (e.g. Firebase Auth changes, WebSockets, SSE events) and converts stream emissions into AsyncValue<T>.
WHEN TO USE: Real-time location tracking, chat message streams, or live auth status.
final authStateProvider = StreamProvider<User?>((ref) {
return FirebaseAuth.instance.authStateChanges();
});
PURPOSE: Encapsulates synchronous mutable state alongside domain logic methods that update state.
WHEN TO USE: Cart item toggles, local step wizard forms, theme settings.
class CounterNotifier extends Notifier<int> {
@override
int build() => 0;
void increment() => state++;
}
PURPOSE: Asynchronous state holder returning FutureOr<T> with built-in AsyncValue state management and operational mutation methods.
WHEN TO USE: Production user profiles, dynamic lists requiring pagination, and form submitting controllers.
class UserProfileNotifier extends AsyncNotifier<User> {
@override
Future<User> build() async => ref.watch(userRepoProvider).fetchMe();
Future<void> updateName(String name) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
return ref.read(userRepoProvider).updateName(name);
});
}
}
PURPOSE: Passes external parameters (e.g. product ID, query filters) to a provider to dynamically create parameterized state instances.
When passing custom class objects as family parameters, you MUST override operator == and hashCode to ensure proper cache key deduplication.
final productDetailProvider = FutureProvider.family<Product, String>((ref, productId) async {
return ref.watch(productRepoProvider).fetchById(productId);
});
3. Provider Taxonomy Matrix
| Provider Variant | State Type | Mutability | Primary Production Use Case |
|---|---|---|---|
| Provider<T> | Synchronous T |
Read-only | Service & repository dependency injection |
| FutureProvider<T> | AsyncValue<T> |
Read-only async | Single read-only REST API requests |
| StreamProvider<T> | AsyncValue<T> |
Read-only stream | Firebase auth changes & WebSocket streams |
| Notifier<T> | Synchronous T |
Mutable | Local form state, cart toggle, theme switch |
| AsyncNotifier<T> | AsyncValue<T> |
Mutable async | User profile management & CRUD data lists |
4. Knowledge Check & Mini Challenge
🏋️ Mini Challenge: Parameterized Product Provider
Create a family FutureProvider named itemDetailsProvider that accepts an integer itemId and returns a dummy string "Item #$itemId".
Phase 03 Summary
You have mastered the complete taxonomy of Riverpod 3 providers and learned how to use decision trees to select between Provider, FutureProvider, Notifier, and AsyncNotifier.