Flutter - Using ChangeNotifier & ValueNotifier Examples

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.