Flutter State Management
State management is a core concept in Flutter development.
This section will introduce Flutter's built-in state management approaches, including setState, Provider, and ChangeNotifier.
What is state management
State management refers to the mechanism for managing data changes and UI updates in an application. When data changes, the UI needs to update accordingly; this is the problem that state management solves.
| State Types | Description | Examples |
|---|---|---|
| Local State | State within a single Widget | Input box text, animation progress |
| Shared State | State that needs to be shared across multiple Widgets | User login information, themes |
| Applied state | State that the entire application needs to access | Shopping cart contents, user configuration |
Local state - setState
For simple local state, using StatefulWidget and setState is sufficient.
Example: Counter example
const CounterWidget({super.key});
@override
State<CounterWidget> createState() => _CounterWidgetState();
}
class _CounterWidgetState extends State<CounterWidget> {
// Local state
int _count = 0;
// Increment count
void _increment() {
setState(() {
_count++;
});
}
// Decrement count
void _decrement() {
setState(() {
_count--;
});
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(Count: $_count, style: const TextStyle(fontSize: 32)),
const SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: _decrement, child: const Text('-')),
const SizedBox(width: 20),
ElevatedButton(onPressed: _increment, child: const Text('+')),
],
),
],
);
}
}
Provider state management
Provider is one of the state management solutions officially recommended by Flutter, suitable for managing state shared across components.
Install Provider
Add the dependency in pubspec.yaml:
dependencies: provider: ^6.0.0
ChangeNotifier Pattern
Example: Provider + ChangeNotifier
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
// Notify listeners that state has changed
notifyListeners();
}
void decrement() {
_count--;
notifyListeners();
}
}
// 2. Provide data at the top of the app
void main() {
runApp(
// Provide CounterModel to child widgets
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
// 3. Use data in the widget
class CounterDisplay extends StatelessWidget {
const CounterDisplay({super.key});
@override
Widget build(BuildContext context) {
// Read CounterModel
final counter = context.watch<CounterModel>();
return Column(
children: [
Text('Count: ${counter.count}', style: const TextStyle(fontSize: 32)),
ElevatedButton(
onPressed: () => counter.increment(),
child: const Text('Increase'),
),
],
);
}
}
Multi-Provider composition
Example: Using multiple Providers
runApp(
MultiProvider(
providers: [
// User Information Provider
ChangeNotifierProvider(create: (_) => UserModel()),
// Theme Provider
ChangeNotifierProvider(create: (_) => ThemeModel()),
// Shopping Cart Provider
ChangeNotifierProvider(create: (_) => CartModel()),
],
child: const MyApp(),
),
);
}
// Use context.read when you don't need to listen for changes.
class SomeWidget extends StatelessWidget {
const SomeWidget({super.key});
@override
Widget build(BuildContext context) {
// Read but do not listen
final user = context.read<UserModel>();
return ElevatedButton(
onPressed: () => user.logout(),
child: const Text('Log Out'),
);
}
}
Lifting State Up
When multiple child components need to share state, the state can be lifted to the nearest common parent component.
Example: State lifting
class ParentWidget extends StatefulWidget {
const ParentWidget({super.key});
@override
State<ParentWidget> createState() => _ParentWidgetState();
}
class _ParentWidgetState extends State<ParentWidget> {
// Shared state
bool _isEnabled = false;
// Callback method
void _toggleEnabled() {
setState(() {
_isEnabled = !_isEnabled;
});
}
@override
Widget build(BuildContext context) {
return Column(
children: [
// Child component A
ChildWidgetA(
isEnabled: _isEnabled,
),
// Child component B
ChildWidgetB(
isEnabled: _isEnabled,
onToggle: _toggleEnabled,
),
],
);
}
}
// Child Component A (only shows state)
class ChildWidgetA extends StatelessWidget {
final bool isEnabled;
const ChildWidgetA({super.key, required this.isEnabled});
@override
Widget build(BuildContext context) {
return Text('Status: ${isEnabled ? "Enabled" : "Disabled"}');
}
}
// Child component B (controls state)
class ChildWidgetB extends StatelessWidget {
final bool isEnabled;
final VoidCallback onToggle;
const ChildWidgetB({
super.key,
required this.isEnabled,
required this.onToggle,
});
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: onToggle,
child: Text(isEnabled ? 'Close' : 'Enable'),
);
}
}
other extensionsThe choice of state management depends on the application scale: simple apps use setState, medium-sized apps use Provider, and complex apps can consider solutions such as Riverpod, BLoC, or GetX.