Phase 03

Provider Taxonomy & Decision Trees

Calculating reading time... Module 1: Foundations

🎯 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

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:

Is your state asynchronous (Network / DB / Stream)?
YES (Async Data)
Does it perform user mutations (POST/PUT)?
YES: Use AsyncNotifier
NO (Read-only Future): Use FutureProvider
NO (Realtime Stream): Use StreamProvider
NO (Synchronous Data)
Does state mutate after creation?
YES: Use 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.

⚠️ Mandatory Equality Rule

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

❓ Question: Which provider should be used for user profile data requiring updateName() mutations?
A) FutureProvider
B) AsyncNotifier
C) StreamProvider

🏋️ 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.