How To Use ChangeNotifier
Greg Perry
Posted on November 29, 2024
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.
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();
}
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'.
}
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).
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.
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?
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.
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?
I hope this has given you some insight about the ChangeNotifier.
Cheers.
Posted on November 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.