Getting Started with Provider for State Management
Getting Started with Provider for State Management
What You’ll Learn
Today, we’ll take the next step up from ValueNotifier and learn about Provider. It’s a powerful and popular package that helps you manage and share state across different parts of your app without passing data through countless widget constructors.
The Core Idea: Provide and Consume
Provider works on a simple principle:
- Provide a piece of state (your data model) at the top of your widget tree.
- Consume or access that state from any widget further down the tree.
First, add provider to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0 # Check for the latest version
Example: A Shared Counter
Let’s create a counter model that can be shared between a display widget and a button, even if they are far apart in the widget tree.
1. The Data Model (ChangeNotifier)
This class holds the state and notifies listeners when it changes.
import 'package:flutter/material.dart';
class CounterModel extends ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // This tells listening widgets to rebuild
}
}
2. Provide the Model
Wrap a parent widget (like MaterialApp or a Scaffold) with ChangeNotifierProvider.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
// Provide the model to the entire app
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(title: const Text('Provider Demo')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CounterDisplay(), // This widget will show the count
const SizedBox(height: 20),
IncrementButton(), // This widget will increase the count
],
),
),
),
);
}
}
3. Consume the State
Now, our widgets can access the CounterModel.
// Widget to display the count
class CounterDisplay extends StatelessWidget {
@override
Widget build(BuildContext context) {
// context.watch listens for changes and rebuilds the widget
final counter = context.watch<CounterModel>();
return Text(
'Count: ${counter.count}',
style: Theme.of(context).textTheme.headlineMedium,
);
}
}
// Widget with the increment button
class IncrementButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
// context.read gets the model without listening for changes
// Ideal for callbacks like onPressed
final counter = context.read<CounterModel>();
return ElevatedButton(
onPressed: () => counter.increment(),
child: const Text('Increment'),
);
}
}
Try It Yourself
- Add a
decrement()method to theCounterModel. - Create a
DecrementButtonwidget that calls this new method. - Add a “Reset” button that sets the counter back to zero.
Challenge: Create a new screen and navigate to it. Can you display the same counter value on the new screen? (Hint: You can! Because the ChangeNotifierProvider is above MaterialApp).
Tip of the Day
watch vs. read vs. select:
context.watch<T>(): Use in abuildmethod to listen for changes. The widget will rebuild whennotifyListeners()is called.context.read<T>(): Use inside callbacks (onPressed,onTap) to get the state once without subscribing to updates. This prevents unnecessary rebuilds.context.select<T, R>: Use to listen to only a small part of a large model. The widget rebuilds only if the selected value changes, which is great for performance.