Choosing the Right Riverpod Provider
Select the provider type based on the kind of state you need to manage, not on personal preference.
Choosing the correct provider keeps your application simpler, easier to test, and easier to maintain. Riverpod provides specialized providers for different use cases, so the best choice is usually the simplest provider that satisfies your requirements.
What is it?
Riverpod offers multiple provider types because different kinds of state have different requirements.
For example:
- A constant value does not need mutable state.
- A counter does not need asynchronous loading.
- A network request does not need synchronous state.
- A live chat stream does not need a
Future.
Each provider is optimized for a specific job.
Why does it exist?
Using the wrong provider often leads to:
- Unnecessary complexity
- Extra boilerplate
- Business logic inside widgets
- Difficult testing
- Poor maintainability
Choosing the right provider from the beginning keeps your codebase clean and scalable.
Decision Guide
Do you need state?
│
├── No
│ │
│ ▼
│ Provider
│
└── Yes
│
▼
Is it synchronous?
│
┌──────┴──────┐
│ │
Yes No
│ │
▼ ▼
Simple? Read-only?
│ │
┌────┴────┐ ┌───┴────┐
│ │ │ │
Yes No Yes No
│ │ │ │
▼ ▼ ▼ ▼
State Notifier Future Async
Provider Provider Notifier
Explanation:
- Start with the simplest provider.
- Move to more powerful providers only when your requirements grow.
Provider Selection
Provider
Use when:
- Exposing constants
- Dependency injection
- Repositories
- Services
- Configuration
- Derived values
Example:
final repositoryProvider =
Provider<UserRepository>((ref) {
return UserRepository();
});
Explanation:
- Creates an immutable dependency.
- Consumers cannot modify it.
StateProvider
Use when:
- Counter
- Checkbox
- Selected tab
- Search query
- Temporary UI state
Example:
final tabProvider =
StateProvider<int>((ref) => 0);
Explanation:
- Simple mutable state.
- No business logic required.
NotifierProvider
Use when:
- Validation
- Business logic
- Multiple state updates
- Complex synchronous state
Example:
class CounterNotifier
extends Notifier<int> {
@override
int build() => 0;
void increment() {
state++;
}
}
Explanation:
- Encapsulates state and related operations.
FutureProvider
Use when:
- Loading configuration
- Read-only API calls
- Initial application data
- Static remote resources
Example:
final configProvider =
FutureProvider<Config>((ref) async {
return repository.loadConfig();
});
Explanation:
- Performs a one-time asynchronous operation.
AsyncNotifierProvider
Use when:
- CRUD operations
- Authentication
- User profile management
- Refreshable API data
- Editable asynchronous state
Example:
class UserNotifier
extends AsyncNotifier<User> {
@override
Future<User> build() async {
return repository.fetchUser();
}
}
Explanation:
- Handles asynchronous loading and future updates.
StreamProvider
Use when:
- Firebase streams
- Firestore snapshots
- WebSockets
- Sensor data
- Read-only live updates
Example:
final authProvider =
StreamProvider<User?>((ref) {
return repository.authChanges();
});
Explanation:
- Listens to an existing stream.
StreamNotifierProvider
Use when:
- Chat applications
- Live dashboards
- Stream filtering
- Stream management
- Interactive real-time features
Example:
class ChatNotifier
extends StreamNotifier<List<Message>> {
@override
Stream<List<Message>> build() {
return repository.messages();
}
}
Explanation:
- Owns both the stream and related business logic.
Mental Model
Immutable value
│
▼
Provider
──────────────
Simple mutable value
│
▼
StateProvider
──────────────
Complex synchronous state
│
▼
Notifier
──────────────
Read-only Future
│
▼
FutureProvider
──────────────
Editable async state
│
▼
AsyncNotifier
──────────────
Read-only Stream
│
▼
StreamProvider
──────────────
Managed Stream
│
▼
StreamNotifier
Real-World Mapping
| Scenario | Recommended Provider |
|---|---|
| App configuration | Provider |
| Repository injection | Provider |
| Theme mode | StateProvider |
| Selected tab | StateProvider |
| Shopping cart | NotifierProvider |
| Form validation | NotifierProvider |
| Login | AsyncNotifierProvider |
| User profile | AsyncNotifierProvider |
| REST API (read-only) | FutureProvider |
| Firebase Auth | StreamProvider |
| Firestore collection | StreamProvider |
| Chat room | StreamNotifierProvider |
When to Use
Ask yourself these questions:
- Is the value immutable?
- Does it change?
- Is it asynchronous?
- Does it require business logic?
- Does it come from a stream?
- Will it grow in complexity?
The answers usually point to the correct provider.
When NOT to Use
Avoid choosing providers based on familiarity alone.
For example:
- Don't use
Notifierfor a constant value. - Don't use
AsyncNotifierfor a simple counter. - Don't use
StateProviderfor complex business logic. - Don't use
FutureProviderif the state needs updates after loading.
More powerful providers are not always better.
Best Practices
- Start with the simplest provider.
- Upgrade providers only when requirements change.
- Keep business logic inside notifiers.
- Use immutable state models.
- Prefer feature-based provider organization.
- Avoid premature complexity.
Common Mistakes
Using Notifier everywhere
Wrong:
Notifier
Theme
Counter
Config
API URL
Explanation:
- Simple values don't benefit from complex providers.
Correct:
Provider
Config
API URL
StateProvider
Theme
Notifier
Business Logic
Explanation:
- Match the provider to the problem.
Using StateProvider for complex state
Wrong:
ref.read(userProvider.notifier).state = updatedUser;
Explanation:
- Validation and business logic end up in widgets.
Correct:
ref
.read(userProvider.notifier)
.updateProfile(updatedUser);
Explanation:
- The notifier owns state transitions.
Starting with the most powerful provider
Wrong:
Every feature
↓
AsyncNotifier
Explanation:
- Many features only need a simple
ProviderorStateProvider.
Correct:
Start Simple
↓
Upgrade When Needed
Explanation:
- Complexity should grow with your application's requirements.
Related APIs
- Provider
- StateProvider
- NotifierProvider
- FutureProvider
- AsyncNotifierProvider
- StreamProvider
- StreamNotifierProvider
Summary
Choose the provider that matches the nature of your state. Use Provider for immutable values, StateProvider for simple mutable state, Notifier for complex synchronous logic, FutureProvider for read-only asynchronous data, AsyncNotifier for editable asynchronous state, StreamProvider for passive streams, and StreamNotifier for managed real-time data. Start with the simplest provider and scale up only as your application's needs evolve.