This tutorial shows you how to use ChangeNotifier
and ValueNotifier
in Flutter.
In a Flutter application, sometimes you may want to notify a widget that a particular value has been changed. In some cases, the widgets to be notified can be more than one. Flutter already provides a class named ChangeNotifier
that's suitable for that purpose. I'm going to explain how to use it along with another class named ValueNotifier
.
Using ChangeNotifier
ChangeNotifier
is a class that provides a change notification API using VoidCallback
. It can be extended or mixed by another class.
A ChangeNotifier
can have several listeners. Registering a listener can be done by calling the addListener
method. You need to pass a VoidCallback
function which becomes the listener. The addListener
can be called as many times as you want if there are multiple listeners. There is also another method named removeListener
which should be called when a listener is no longer used.
The ChangeNotifier
has a method called notifyListeners()
. When it's invoked, it iterates over the listeners and calls them one by one. Therefore, you can add a call to the notifyListeners()
method every time the listeners need to be notified.
Below is an example of a simple class that uses a ChangeNotifier
as a mixin. The class has an integer value whose value is updated every second. There is a getter for the value as well.
class MyNotifier with ChangeNotifier {
int? _value;
MyNotifier() {
_generateValueContinuously();
}
void _generateValueContinuously() async {
while (true) {
await Future.delayed(const Duration(seconds: 1));
_value = Random().nextInt(100000);
notifyListeners();
}
}
int? get value => _value;
}
We want to display the current value of a MyNotifier
instance. To do it, we need to create the instance first. Then, call the addListener
method by passing a callback function. In the following example, the callback function has the responsibility to update the value of a state variable based on the current value of the MyNotifier
instance. If the listener is no longer used, you can unregister the listener by calling the removeListener
method. Since the listener needs to be registered first and disposed later, you must be able to get the reference to the same function when calling both addListener
and removeListener
class MyPageState extends State<MyPage> {
int? _text;
final MyNotifier _myNotifier = MyNotifier();
void _myListener () {
setState(() {
_text = _myNotifier.value;
});
}
@override
void initState() {
super.initState();
_myNotifier.addListener(_myListener);
}
@override
void dispose() {
_myNotifier.removeListener(_myListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: Text('$_text'),
);
}
}
In the example above, every time MyNotifier
updates the value, it will notify its listeners. As a result, the listener above will be notified every time there is a change and it will update the state variable which is used as the value of a Text
widget. The current value of the MyNotifier
instance can be obtained from the getter.
Using ValueNotifier
Flutter also has another class called ValueNotifier
, which is a type of ChangeNotifier
that holds a single value. In the previous example, there is only a single value that is held by the ChangeNotifier
class. For cases like that, it would be simpler to use a ValueNotifier
instead.
The ValueNotifier
already has a getter and a setter named value
. Therefore, you don't need to define it yourself. Unlike ChangeNotifier
, you can only extend a ValueNotifier
and cannot use it as a mixin since it declares a constructor. There is a parameterized type that can be used to set the type of the value. The constructor itself has one parameter which is the initial value.
class MyValueNotifier extends ValueNotifier<int?> {
MyValueNotifier() : super(null) {
_generateValueContinuously();
}
void _generateValueContinuously() async {
while (true) {
await Future.delayed(const Duration(seconds: 1));
value = Random().nextInt(100000);
notifyListeners();
}
}
}
Then, you can replace the _myNotifier
variable to be an instance of MyValueNotifier
.
final MyValueNotifier _myNotifier = MyValueNotifier();
If you don't need to define a custom constructor or method, you don't need to create a custom class. You can simply create a ValueNotifier
instance by calling the constructor. Let's assume we have a case where the value can be changed by pressing a button (unlike the above examples where the value is generated by the custom class). In this case, the notifier class only needs to hold a value. There's no need to create a custom class. As a result, we can directly call the constructor of ValueNotifier
to create a ChangeNotifier
instance.
final MyValueNotifier _myNotifier = ValueNotifier<int>(0);
Since ValueNotifier
exposes a getter and a setter for the value
field, we can access the field value and set a new value for the field.
OutlinedButton(
onPressed: () {
_myNotifier.value += 1;
},
child: const Text('Increment'),
)
Full Code
Below is the full code of this tutorial.
import 'dart:math';
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Woolha.com Flutter Tutorial',
home: Scaffold(
appBar: AppBar(
title: const Text('Woolha.com Flutter Tutorial'),
backgroundColor: Colors.teal,
),
body: const MyPage(),
),
);
}
}
class MyPage extends StatefulWidget {
const MyPage({super.key});
@override
State<StatefulWidget> createState() {
return MyPageState();
// return MyAnotherPageState();
}
}
class MyPageState extends State<MyPage> {
int? _text;
// final MyNotifier _myNotifier = MyNotifier();
final MyValueNotifier _myNotifier = MyValueNotifier();
void _myListener () {
setState(() {
_text = _myNotifier.value;
});
}
@override
void initState() {
super.initState();
_myNotifier.addListener(_myListener);
}
@override
void dispose() {
super.dispose();
_myNotifier.removeListener(_myListener);
}
@override
Widget build(BuildContext context) {
return Center(
child: Text('$_text'),
);
}
}
class MyAnotherPageState extends State<MyPage> {
int _value = 0;
final ValueNotifier<int> _myNotifier = ValueNotifier<int>(0);
void _myListener() {
setState(() {
_value = _myNotifier.value;
});
}
@override
void initState() {
super.initState();
_myNotifier.addListener(_myListener);
}
@override
void dispose() {
super.dispose();
_myNotifier.removeListener(_myListener);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('$_value'),
OutlinedButton(
onPressed: () {
_myNotifier.value += 1;
},
child: const Text('Increment'),
),
],
),
);
}
}
class MyNotifier with ChangeNotifier {
int? _value;
MyNotifier() {
_generateValueContinuously();
}
void _generateValueContinuously() async {
while (true) {
await Future.delayed(const Duration(seconds: 1));
_value = Random().nextInt(100000);
notifyListeners();
}
}
int? get value => _value;
}
class MyValueNotifier extends ValueNotifier<int?> {
MyValueNotifier() : super(null) {
_generateValueContinuously();
}
void _generateValueContinuously() async {
while (true) {
await Future.delayed(const Duration(seconds: 1));
value = Random().nextInt(100000);
notifyListeners();
}
}
}
Summary
The ChangeNotifier
class defines a change notification API that can be used to notify some listeners when a change occurs. To use it, you need to have a non-abstract class that is a subtype of the ChangeNotifier
. You can create your own class or use a built-in class named ValueNotifier
, which is suitable if the class only holds a single value.
You can also read about.