ChangeNotifierProvider
In the provider package, ChangeNotifierProvider is the most common widget used to instantiate, expose, and manage mutable states that extend standard Dart ChangeNotifier.
1. Core Mechanics
ChangeNotifierProvider handles two main responsibilities:
- Creation and Disposal: Instantiates a class extending
ChangeNotifierand disposes of it automatically when the provider is removed from the widget tree. - Rebuild Propagation: Listens to notifications from the
ChangeNotifierclass and triggers a rebuild in all listening descendant widgets.
2. Code Example: Theme Changer
Here is a clean implementation of a theme mode state switcher using ChangeNotifierProvider.
Step 1: Define the ChangeNotifier model
import 'package:flutter/material.dart';
class ThemeSettingsModel extends ChangeNotifier {
ThemeMode _themeMode = ThemeMode.light;
ThemeMode get themeMode => _themeMode;
void toggleTheme() {
_themeMode = _themeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light;
// Notify all listening elements to trigger a UI rebuild
notifyListeners();
}
}
Step 2: Provide the model to the app tree
Wrap your root application structure inside ChangeNotifierProvider:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(
ChangeNotifierProvider(
create: (context) => ThemeSettingsModel(),
child: const MyApp(),
),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
// Read theme mode using watch to reactively update the MaterialApp theme
final themeSettings = context.watch<ThemeSettingsModel>();
return MaterialApp(
themeMode: themeSettings.themeMode,
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
home: const HomeScreen(),
);
}
}
Step 3: Trigger state mutations
In descendents, access the toggle function without observing (using read):
class HomeScreen extends StatelessWidget {
const HomeScreen({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Theme Switcher')),
body: Center(
child: ElevatedButton(
onPressed: () {
// Use read here so this button widget does not rebuild itself
context.read<ThemeSettingsModel>().toggleTheme();
},
child: const Text('Toggle Theme'),
),
),
);
}
}
3. The ChangeNotifierProvider.value Constructor
By default, you should instantiate your change notifier using create. However, if you are providing an already existing instance (for example, passing an instance to a new screen or item in a list), use the .value named constructor:
// Use create when creating a new instance
ChangeNotifierProvider(
create: (context) => MyModel(),
child: ...
)
// Use .value when exposing an existing instance
ChangeNotifierProvider.value(
value: alreadyExistingModel,
child: ...
)
[!WARNING] Never use
createto pass an existing instance. This will lead to resource leaks and unexpected lifecycle bugs because Provider will attempt to dispose of it again.