Problem Statement
Explain the different types of providers in Riverpod and when to use each.
Explanation
StateProvider is for simple mutable state, similar to ValueNotifier, providing a state object that widgets can read and modify. Use it for simple values like theme mode, counter, or toggles that need direct mutation. For example, final counterProvider = StateProvider<int>((ref) => 0) creates a counter that widgets can increment with ref.read(counterProvider.notifier).state++.
FutureProvider handles async operations returning Future, like API calls or database queries. It provides AsyncValue<T> exposing data, error, and loading states for easy handling in UI. Use it for fetching data that loads once or infrequently. StreamProvider wraps streams providing continuous updates, useful for real-time data like Firestore listeners or WebSocket connections. It also provides AsyncValue for consistent error and loading handling.
StateNotifierProvider combines Cubit-like logic with Riverpod for complex state. Create a StateNotifier subclass holding state and exposing methods to modify it, then provide it with StateNotifierProvider. This pattern is ideal for complex business logic needing multiple methods or internal state that shouldn't be directly mutated. It's the Riverpod equivalent of ChangeNotifierProvider but more performant and type-safe.
Provider is for simple computed values or services that don't change, like dependency injection of repositories or utility classes. Use ref.watch(provider) to read providers and automatically rebuild when they change, ref.read(provider) to read without listening, and ref.listen for side effects. Combining providers is easy with ref.watch inside provider definitions, enabling derived state and complex dependencies. Understanding which provider type to use makes Riverpod powerful while keeping code clean.

