state_notifier 0.7.0-nullsafety.0 state_notifier: ^0.7.0-nullsafety.0 copied to clipboard
ValueNotifier, but outside Flutter and with some extra perks
This repository is a set of packages that reimplements ValueNotifier outside of Flutter.
It is spread across two packages:
state_notifier
, a pure Dart package containing the reimplementation of ValueNotifier.
It comes with extra utilities for combining our "ValueNotifier" with provider and to test it.flutter_notifier
, a binding betweenstate_notifier
and Flutter.
It adds things like ChangeNotifierProvider from provider, but compatible withstate_notifier
.
Motivation #
Extracting ValueNotifier outside of Flutter in a separate package has two purposes:
- It allows Dart packages with no dependency on Flutter to use these
classes.
This means that we can use them on AngularDart for example. - It allows solving some common problems with the original ChangeNotifier/ValueNotifier and/or their combination with provider.
For example, by using state_notifier
instead of the original ValueNotifier, then
you get:
- A significant simplification of the integration with provider
- Simplified testing/mocking
- Improved performances on
addListener
¬ifyListeners
equivalents. - Extra safety through small API changes
Integration with provider/service locators #
StateNotifier is easily compatible with provider through an extra mixin: LocatorMixin
.
Consider a typical StateNotifier written like a ValueNotifier:
class Counter extends StateNotifier<int> {
Counter(): super(0)
void increment() {
state++;
}
}
In this example, we may want to use Provider.of
/context.read
to connect our
Counter
with external services.
To do so, simply mix-in LocatorMixin
as such:
class Counter extends StateNotifier<int> with LocatorMixin {
// unchanged
}
This then gives you access to:
read
, a function to obtain servicesupdate
, a new life-cycle that can be used to listen to changes on a service
We could use them to change our Counter
incrementation to save the counter in
a DB when incrementing the value:
class Counter extends StateNotifier<int> {
Counter(): super(0)
void increment() {
state++;
read<LocalStorage>().writeInt('count', state);
}
}
Testing
When using LocatorMixin
, you may want to mock a dependency for your tests.
Of course, we still don't want to depend on Flutter/provider to do such a thing.
Similarly, since state
is protected, tests need a simple way to read the state.
As such, LocatorMixin
also adds extra utilities to help you with this scenario:
myStateNotifier.debugMockDependency<MyDependency>(myDependency);
print(myStateNotifier.debugState);
myStateNotifier.debugUpdate();
As such, if we want to test our previous Counter
, we could mock LocalStorage
this way:
test('increment and saves to local storage', () {
final mockLocalStorage = MockLocalStorage();
final counter = Counter()
..debugMockDependency<LocalStorage>(mockLocalStorage);
expect(counter.debugState, 0);
counter.increment(); // works fine since we mocked the LocalStorage
expect(counter.debugState, 1);
// mockito stuff
verify(mockLocalStorage.writeInt('int', 1));
});
Differences with ValueNotifier #
This is not a one-to-one reimplementation of ValueNotifier. It has some differences:
- ValueNotifier is instead named StateNotifier (to avoid name clash)
ValueNotifier.value
is renamed tostate
, to match the class name- StateNotifier is abstract
state
is@protected
- The listener passed to
addListener
receives the currentstate
, and is called synchronously on addition. addListener
andremoveListener
are fused in a singleaddListener
function which returns a function to remove the listener.
This makes adding and removing listeners O(1) versus O(N) for ValueNotifier.- listeners cannot add extra listeners.
This makes notifying listeners O(N) versus O(N²) for ValueNotifier - offers a
mounted
boolean to know if the StateNotifier was disposed or not, similar toState
.