StateProvider
Managing simple mutable state in Riverpod — the easiest way to handle counters, toggles, and form inputs.
StateProvider
is a type of provider in Riverpod that holds a
mutable piece of state
.
It's the simplest way to manage state that changes over time, such as:
- Counter values
- Toggle states (boolean)
- Text input values
- Selected item indices
- Simple form data
✅ When to Use StateProvider
Use
StateProvider
when you need to manage
simple, primitive state
that doesn't have complex validation or business logic. It's perfect for UI state like
what tab is selected, whether a switch is on, or how many items are in a cart.
Defining a
StateProvider
is straightforward. You provide an initial value, and Riverpod
handles the rest.
import
'package:flutter_riverpod/flutter_riverpod.dart'
;
// A StateProvider that holds an integer counter
final
counterProvider = StateProvider<int>((ref) =>
0
);
// A StateProvider that holds a boolean toggle
final
darkModeProvider = StateProvider<bool>((ref) =>
false
);
// A StateProvider that holds a string input
final
usernameProvider = StateProvider<String>((ref) =>
''
);
// A StateProvider that holds a list (simple)
final
selectedItemsProvider = StateProvider<List<String>>((ref) => []);
To read the value of a
StateProvider
, use
ref.watch()
.
To update it, use
ref.read(provider.notifier).state = newValue
.
import
'package:flutter/material.dart'
;
import
'package:flutter_riverpod/flutter_riverpod.dart'
;
import
'providers.dart'
;
class
CounterScreen
extends
ConsumerWidget {
@override
Widget
build
(BuildContext context, WidgetRef ref) {
// 👀 Watch the counter provider to get the current value
final
count = ref.watch(counterProvider);
return
Scaffold(
appBar: AppBar(title: Text(
'Counter with StateProvider'
)),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'Count: $count'
),
ElevatedButton(
onPressed: () {
// 🔄 Update the state using .notifier
ref.read(counterProvider.notifier).state++;
},
child: Text(
'Increment'
),
),
],
),
),
);
}
}
counterProvider
is a
StateProvider
that holds
an integer. The initial value is
0
.
ref.watch(counterProvider)
listens to changes.
When the counter changes, the widget rebuilds with the new value.
ref.read(counterProvider.notifier).state++
increments
the counter. The
.notifier
gives you access to the state object, and
.state
is where the actual value is stored.
Here's a complete, runnable counter app that uses
StateProvider
.
This is the simplest possible Riverpod app.
import
'package:flutter/material.dart'
;
import
'package:flutter_riverpod/flutter_riverpod.dart'
;
// 1. Define the StateProvider
final
counterProvider = StateProvider<int>((ref) =>
0
);
void
main
() {
runApp(
ProviderScope(
child: MyApp(),
),
);
}
class
MyApp
extends
StatelessWidget {
@override
Widget
build
(BuildContext context) {
return
MaterialApp(
home: CounterScreen(),
);
}
}
class
CounterScreen
extends
ConsumerWidget {
@override
Widget
build
(BuildContext context, WidgetRef ref) {
// 2. Watch the provider
final
count = ref.watch(counterProvider);
return
Scaffold(
appBar: AppBar(
title: Text(
'StateProvider Counter'
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'$count'
,
style: Theme.of(context).textTheme.headlineLarge,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 3. Update state
ref.read(counterProvider.notifier).state++;
},
child: Text(
'Increment'
),
),
SizedBox(width:
8
),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).state--;
},
child: Text(
'Decrement'
),
),
SizedBox(width:
8
),
ElevatedButton(
onPressed: () {
ref.read(counterProvider.notifier).state =
0
;
},
child: Text(
'Reset'
),
),
],
),
],
),
),
);
}
}
StateProvider
- Simple mutable state
- Primitive values (int, bool, String)
- No business logic
- Direct state mutation
StateNotifierProvider
- Complex state with methods
- Custom state classes
- Business logic inside the notifier
- Immutability recommended
FutureProvider
- Asynchronous data (Future)
- Loading and error states
- One-time fetch
- Read-only
StreamProvider
- Real-time data (Stream)
- Continuous updates
- Loading and error states
- Read-only
💡 When to Choose StateProvider
Choose StateProvider when: you have simple state that doesn't need validation, transformation, or complex business logic. It's perfect for UI state and simple data. Choose StateNotifierProvider when: you have complex state with methods, validation, or immutability requirements.
ref.read(counterProvider).state = 5
won't work. You must use
.notifier
.
ref.read(counterProvider.notifier).state = 5
is the correct way.
ref.watch()
should only be used inside the
build()
method.
For buttons and callbacks, use
ref.read()
to update state without listening for changes.
If your state has methods, validation, or multiple fields,
StateProvider
becomes cumbersome.
For complex state with methods and validation, use
StateNotifierProvider
.
🎯 Key Takeaway
StateProvider
is the simplest way to manage mutable state in Riverpod.
Use it for counters, toggles, and form inputs. For more complex state, use
StateNotifierProvider
.
Remember: .notifier is the key to updating state!