StateProvider → Notifier
Migrate from StateProvider to Notifier when state management grows beyond simple value updates.
StateProvider is intended for simple, local state. As soon as your state requires validation, business logic, or multiple operations, Notifier becomes the better choice.
What is it?
Both StateProvider and Notifier manage synchronous state, but they target different levels of complexity.
- StateProvider exposes a mutable value with minimal boilerplate.
- Notifier encapsulates state and the logic that modifies it.
Think of StateProvider as a simple state holder and Notifier as a complete state manager.
Why does it exist?
StateProvider is great for simple values such as:
- Selected tab
- Checkbox state
- Search query
- Theme mode
- Filter selection
However, as requirements grow, state often needs:
- Validation
- Multiple update methods
- Derived state
- Business rules
- Coordination with repositories or services
Moving to Notifier keeps this logic out of the UI and makes it easier to maintain and test.
Syntax
StateProvider
final counterProvider = StateProvider<int>((ref) => 0);
Explanation:
- Stores a single mutable value.
- The UI updates the state directly.
Updating a StateProvider
ref.read(counterProvider.notifier).state++;
Explanation:
- The widget modifies the provider's state.
- No dedicated business logic layer exists.
Notifier
class CounterNotifier extends Notifier<int> {
@override
int build() => 0;
void increment() {
state++;
}
}
final counterProvider =
NotifierProvider<CounterNotifier, int>(
CounterNotifier.new,
);
Explanation:
build()initializes the state.- State changes are performed through methods.
- Business logic is centralized in the notifier.
Mental Model
StateProvider:
UI
│
▼
Update state directly
Notifier:
UI
│
▼
Notifier
│
▼
Update state
With Notifier, the UI asks the notifier to perform an action instead of modifying the state itself.
Examples
Simple Example
Before
final countProvider = StateProvider<int>((ref) => 0);
ref.read(countProvider.notifier).state++;
Explanation:
- The widget changes the state directly.
After
class CountNotifier extends Notifier<int> {
@override
int build() => 0;
void increment() {
state++;
}
}
ref.read(countProvider.notifier).increment();
Explanation:
- The widget calls a method instead of manipulating state.
- The notifier owns the update logic.
Real-World Example
Before
final quantityProvider =
StateProvider<int>((ref) => 1);
ref.read(quantityProvider.notifier).state++;
Explanation:
- There is no validation.
- The UI can set invalid values.
After
class QuantityNotifier extends Notifier<int> {
@override
int build() => 1;
void increment() {
state++;
}
void decrement() {
if (state > 1) {
state--;
}
}
}
Explanation:
- Validation is handled inside the notifier.
- Invalid state transitions are prevented.
When to Use
Migrate to Notifier when:
- State requires business logic.
- Validation is needed.
- Multiple actions modify the state.
- The state is shared across multiple widgets.
- The feature is expected to grow.
- You want better testability.
When NOT to Use
Continue using StateProvider for:
- Simple counters
- Toggle switches
- Selected indices
- Search text
- Temporary UI state
- Small prototypes
Don't replace every StateProvider automatically. If the state remains simple, StateProvider is still an excellent choice.
Best Practices
- Use
StateProvideronly for simple mutable values. - Move business logic into
Notifier. - Keep widgets free of state mutation logic.
- Expose meaningful methods instead of direct state updates.
- Prefer immutable state models as complexity increases.
Common Mistakes
Putting business logic in the UI
Wrong:
final notifier = ref.read(counterProvider.notifier);
if (notifier.state < 10) {
notifier.state++;
}
Explanation:
- Validation is scattered across widgets.
- Multiple widgets may implement the same logic differently.
Correct:
class CounterNotifier extends Notifier<int> {
@override
int build() => 0;
void increment() {
if (state < 10) {
state++;
}
}
}
Explanation:
- The notifier becomes the single source of truth for state changes.
Continuing to modify state directly
Wrong:
ref.read(counterProvider.notifier).state++;
Explanation:
- The UI bypasses business logic.
- Future validation becomes harder to introduce.
Correct:
ref
.read(counterProvider.notifier)
.increment();
Explanation:
- Widgets request an action.
- The notifier decides how the state changes.
Migrating too early
Wrong:
class ThemeNotifier extends Notifier<bool> {
@override
bool build() => false;
}
Explanation:
- If the state only stores a simple boolean with no additional logic,
Notifiermay add unnecessary complexity.
Correct:
final darkModeProvider =
StateProvider<bool>((ref) => false);
Explanation:
- Keep simple state simple.
- Introduce a
Notifieronly when the feature outgrowsStateProvider.
Related APIs
- StateProvider
- Notifier
- NotifierProvider
- AsyncNotifier
ref.watch()ref.read()
Summary
StateProvider is ideal for simple, mutable values with minimal logic. As state becomes more complex, migrating to Notifier centralizes business logic, improves testability, and keeps widgets focused on presentation. Use StateProvider for simplicity and Notifier when your application's requirements demand a more structured approach.