
In a Flutter app, “state” is any data you want to display in the user interface. A simple example is the value of a counter that increases when you press a button. Without a proper state management solution, you’d be forced to manually pass data down through a series of widget constructors, a practice known as “prop drilling.” This makes your code hard to read, maintain, and test.
Provider solves this by allowing you to define a single source of truth for your data and then easily access it from anywhere in your widget tree without passing it down manually. When the data changes, only the widgets that depend on it will rebuild, leading to better performance and a cleaner codebase.
Core Concepts of Provider
To get started with Provider, you need to understand three main concepts: ChangeNotifier, ChangeNotifierProvider, and Consumer.
ChangeNotifierThis is a class from the Flutter SDK that you extend to create your data model. It provides a simple way to notify listeners when the state has changed. You’ll put all your data and the business logic to manipulate it (like incrementing a counter or adding an item to a cart) in this class. To trigger a UI update, you simply call thenotifyListeners()method.ChangeNotifierProviderThis widget is responsible for creating an instance of yourChangeNotifierclass and providing it to its descendants in the widget tree. You typically wrap your entire app (or a specific part of it) with aChangeNotifierProviderso that all the widgets below it can access the state.ConsumerTheConsumerwidget is what listens for changes in yourChangeNotifier. When the state changes (i.e.,notifyListeners()is called), theConsumerrebuilds its UI to reflect the new data. UsingConsumeris a great way to optimize performance, as it ensures only the specific part of the UI that needs to be updated is rebuilt, rather than the entire widget.
A Simple Counter App Example
Let’s walk through a basic example to see how these pieces fit together. We’ll create a simple app with a counter that can be incremented.
Step 1: Add the Dependency
First, add the Provider package to your pubspec.yaml file:
YAML
dependencies:
flutter:
sdk: flutter
provider: ^6.0.0 # Use the latest version
Then, run flutter pub get in your terminal.
Step 2: Create the ChangeNotifier Model
Create a new file, say counter_model.dart, and add your state and logic.
Dart
import 'package:flutter/foundation.dart';
class CounterModel with ChangeNotifier {
int _count = 0;
int get count => _count;
void increment() {
_count++;
notifyListeners(); // Notifies all listeners that the count has changed
}
}
Step 3: Provide the Model to the App
In your main.dart file, wrap your app with ChangeNotifierProvider to make the CounterModel available.
Dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';
import 'home_page.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => CounterModel(),
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
Step 4: Consume the State in a Widget
Now, create a HomePage to display the counter and the button. We’ll use a Consumer to listen to changes in the CounterModel and rebuild the Text widget.
Dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'counter_model.dart';
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Provider Counter App'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('You have pushed the button this many times:'),
Consumer<CounterModel>(
builder: (context, counter, child) {
return Text(
'${counter.count}',
style: Theme.of(context).textTheme.headlineMedium,
);
},
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Provider.of<CounterModel>(context, listen: false).increment();
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Notice how we use Provider.of<CounterModel>(context, listen: false) in the FloatingActionButton‘s onPressed callback. The listen: false parameter is crucial here because we only need to access the model’s method (increment()) and not rebuild the FloatingActionButton itself. This is another key optimization technique.
Conclusion
Provider is an excellent state management solution for Flutter, especially for beginners and small-to-medium-sized apps. Its simplicity, performance, and strong community support make it a top choice for developers. By understanding and applying the core concepts of ChangeNotifier, ChangeNotifierProvider, and Consumer, you can write cleaner, more maintainable, and highly performant Flutter applications.
