How To Use ChangeNotifier

andrious

Greg Perry

Posted on November 29, 2024

How To Use ChangeNotifier

How To Use ChangeNotifier

It can’t be used in certain cases. Not directly anyway.

Have you ever tried to use the ChangeNotifier as a Mixin on a State class? You can’t do it.

class NotifierState extends State<NotifierWidget> with ChangeNotifier;

If you try, your State class will no longer call its dispose() method, and you need to call its dispose() method to ‘clean’ things up in many cases. However, the ChangeNotifier is a Mixin with its own dispose() method, and it doesn’t extend a parent class with a dispose() function. Therefore, as you can see in the second screenshot below, its dispose() method doesn’t have to call, super.dispose(). It just won’t work as a Mixin on any class that has its own dispose() method. However, there is a way to introduce a ChangeNotifier with a Mixin by doing it indirectly.

change_notifier.dart

change_notifier.dart

Supply the Mixin, ImplNotifyListenersChangeNotifierMixin, to your State class, and place the method call, disposeChangeNotifier(), to its dispose() method. You‘ll then have a ChangeNotifier working with your State class.

  @override
  @mustCallSuper
  void dispose() {
    // Call the dispose of the implementation of Change Notifier
    disposeChangeNotifier();
    super.dispose();
  }
Enter fullscreen mode Exit fullscreen mode

Grab a copy of this Mixin below:

import 'package:flutter/material.dart';

import 'listenable_widget_builder.dart';

mixin ImplNotifyListenersChangeNotifierMixin {
  //

  // Instantiate the Change Notifier
  void initChangeNotifier() {
    _implChangeNotifier ??= ImplNotifyListenersChangeNotifier();
  }

  /// A flag. Instantiated Change Notifier
  bool get hasChangeNotifierImpl => _implChangeNotifier != null;

  /// Don't forget to call dispose() function!
  void disposeChangeNotifier() {
    _implChangeNotifier?.dispose();
    _implChangeNotifier = null;
  }

  // Implementation of the ChangeNotifier
  ImplNotifyListenersChangeNotifier? _implChangeNotifier;

  /// Returns a widget from builder assuming the current object is a [Listenable]
  /// const SizedBox.shrink() otherwise
  Widget setBuilder(MaybeBuildWidgetType? builder) {
    // Ensure the ChangeNotifier is initialized
    initChangeNotifier();

    if (builder != null) {
      _widgetBuilderUsed = true;
    }
    return ListenableWidgetBuilder(
      listenable: _implChangeNotifier,
      builder: builder,
    );
  }

  /// A flag. Noting if the function above is ever used.
  bool get setBuilderUsed => _widgetBuilderUsed;
  bool _widgetBuilderUsed = false;

  /// Whether any listeners are currently registered.
  bool get hasChangeListeners => _implChangeNotifier?.hasChangeListeners ?? false;

  /// Call all the registered listeners.
  bool notifyListeners()  {
    final notify = _implChangeNotifier != null;
    if (notify) {
      _implChangeNotifier?.notifyListeners();
    }
    return notify;
  }

  /// Register a closure to be called when the object changes.
  bool addListener(VoidCallback listener) {
    final add = _implChangeNotifier != null;
    if (add) {
      _implChangeNotifier?.addListener(listener);
    }
    return add;
  }

  /// Remove a previously registered closure from the list of closures that are
  /// notified when the object changes.
  bool removeListener(VoidCallback listener) {
    final remove = _implChangeNotifier != null;
    if (remove) {
      _implChangeNotifier?.removeListener(listener);
    }
    return remove;
  }
}

/// Implementing ChangeNotifier
class ImplNotifyListenersChangeNotifier with ChangeNotifier {
  /// Whether any listeners are currently registered.
  bool get hasChangeListeners => super.hasListeners;

  /// Call all the registered listeners.
  @override
  // ignore: unnecessary_overrides
  void notifyListeners() => super.notifyListeners();

// The 'unnecessary overrides' prevent the Dart Analysis warning:
// The member 'hasListeners' can only be used within instance members of
// subclasses of 'package: change_notifier.dart'.
}
Enter fullscreen mode Exit fullscreen mode

The trick is when you attach this Mixin, it supplies a ChangeNotifier as field property instead (see first screenshot below). In the second screenshot, a ChangeNotifier is instead attached to a very simple class. It is that class that is then instantiated as a field property in the method, initChangeNotifier()(see first screenshot below).

part14_change_notifier_class.dart

part14_change_notifier_class.dart

A closer look at the Mixin below, reveals how the traditional methods found in the ChangeNotifier are then mirrored in this Mixin. You can see in the third screenshot below, the methods notifyListeners(), addListener(),and removeListener() are all called by functions of the same name.

Further note, the ChangeNotifier field property is nullable. A Mixin can’t have a constructor, and so it’s up to the user to properly use the Mixin and to call both the method, initChangeNotifier(), and the method, disposeChangeNotifier(), when appropriate. If not done so, the Mixin will not crash, but instead just not work properly. It’s on you to work it properly.

part14_change_notifier_class.dart

part14_change_notifier_class.dart

part14_change_notifier_class.dart

Add Some Class

Note, what you could do is specially assign such a Mixin to a State class, but it wouldn’t be very practical. You would have to dedicate a new Mixin to each State class. That’s because you would have use the on clause (see the first screenshot below) to reference that specific State class. However, you can then include a dispose() method in the Mixin. It would be called by the State object. See how that works?

impl_change_notifier_mixin.dart

counter_listenable.dart

class _MyHomePageState extends State<MyHomePage> with ImplNotifyListenersChangeNotifierMixin;

The second screenshot above is a simple counter app utilizing this particular Mixin. With every push of the button, you then use the functions setBuilder() and notifyListeners() to only rebuild the Text widget containing the count. The rest of the screen is left untouched.

That’s the primary purpose of the setBuilder() function found in the Mixin. It returns a Widget defined by its supplied builder, and that Widget is only rebuilt with the function call, notifyListeners(). Your interface could be dotted with these setBuilder() functions. For better performance, only those areas of the screen are rebuilt instead of the whole screen.

The class, StateXController, in the example app, flutter_weather, has its own dispose() function. Therefore, in the first screenshot below, you can see the Mixin is attached to this class, and the method, disposeChangeNotifier(),is called in its dispose() method. In the second screenshot, you see the function, setBuilder(), is called.

part13_statex_controller.dart

settings_page.dart

In the video above, you see this simple app allows you can tap to another screen and switch the city of Chicago’s temperature from Celsius to Fahrenheit (°C to °F). Again, the second screenshot above shows how this second screen is implemented. When the user taps on the Switch widget to change from Celsius to Fahrenheit, the class, StateXController, calls the function, notifyListeners(). That will call the Widget builder in the setBuilder() function again rebuilding the Switch widget with now the toggled isCelsius value. Easy peasy.

Lastly, let’s examine the setBuilder() function in the first screenshot below. It utilizes the StatefulWidget, [ListenableWidgetBuilder]. You’ll have to get a copy of this StatefulWidget as well to use the Mixin. This StatefulWidget takes in the ChangeNotifier as a [Listenable] and its State object, _ListenableState, adds the function, _callBuild(), as a listener (see the second screenshot below). In the third screenshot below, you can see the function, _callBuild(), merely calls the build() function listed further below. See how that works?

part14_change_notifier_class.dart

part15_listenable_widget_builder.dart

part15_listenable_widget_builder.dart

I hope this has given you some insight about the ChangeNotifier.

Cheers.

→ Other Stories by Greg Perry

💖 💪 🙅 🚩
andrious
Greg Perry

Posted on November 29, 2024

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related