What Is Provider?
Provider is a state management solution that makes it easy to share and access state across your widget tree without passing data through constructors. It sits above your widgets, provides data downward, and automatically rebuilds widgets when that data changes. It's the recommended approach for most Flutter apps.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// Add provider to pubspec.yaml:
// dependencies:
// provider: ^6.0.0
void main() => runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
Try it Yourself ->
ChangeNotifier Class
ChangeNotifier is a class that holds your app's state and notifies listeners when it changes. You create a model class that extends ChangeNotifier, put your state inside it, and call notifyListeners() whenever you want the UI to update. It keeps your business logic separate from your widgets.
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Tells all listeners to rebuild
}
void decrement() {
_count--;
notifyListeners();
}
void reset() {
_count = 0;
notifyListeners();
}
}
Try it Yourself ->
Consumer Widget
The Consumer widget listens to a ChangeNotifier and rebuilds whenever notifyListeners is called. You wrap the part of your widget tree that depends on the state inside a Consumer, and it gives you access to the model. Only the widgets inside the Consumer rebuild, not the entire screen.
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Consumer<CounterModel>(
builder: (context, counter, child) {
return Text(
'Count: ${counter.count}',
style: TextStyle(fontSize: 32),
);
},
);
}
}
class CounterButtons extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of<CounterModel>(context, listen: false);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(onPressed: counter.decrement, child: Text('-')),
SizedBox(width: 16),
ElevatedButton(onPressed: counter.increment, child: Text('+')),
],
);
}
}
Try it Yourself ->
Why Provider Beats Passing State Around
Without Provider, you'd have to pass state variables down through multiple widget layers just to share a simple value. Provider eliminates that mess by making state available anywhere in the subtree. Widgets don't need to know about each other — they just read from the Provider. Less boilerplate, cleaner code, fewer bugs.
// Without Provider — messy constructor chains
class Home extends StatelessWidget {
final int count;
final VoidCallback onIncrement;
Home({required this.count, required this.onIncrement});
}
class Child extends StatelessWidget {
final int count;
final VoidCallback onIncrement;
Child({required this.count, required this.onIncrement});
}
// With Provider — clean and direct
class Home extends StatelessWidget {
@override
Widget build(BuildContext context) {
// Just read from Provider, no constructor params needed
final count = context.watch<CounterModel>().count;
return Text('Count: $count');
}
}
Try it Yourself ->