functional_listener
Extension functions on ValueListenable
that allows you to work with them almost as if it was a synchronous stream. Each extension function returns a new ValueNotifier
that updates its value when the value of this
changes. You can chain these functions to build complex processing pipelines from a simple ValueListenable
.
Here are some examples how to use it:
listen()
Lets you work with a ValueListenable
as it should be by installing a handler function that is called on any value change of this
and gets the new value passed as an argument.
If we want to print every new value of a ValueListenable<int>
we can do:
final listenable = ValueNotifier<int>(0);
final subscription = listenable.listen((x, _) => print(x));
The returned subscription
can be used to deactivate the passed handler function. As you might need to uninstall the handler function from inside the handler
you get the subscription object passed to the handler function as second parameter like:
listenable.listen((x, subscription) {
print(x);
if (x == 42){
subscription.cancel();
}
})
map()
Lets you convert the value of one ValueListenable
to anything you want.
Imagine we have a ValueNotifier<String>
in an Model object that we can't change but we need it's value all UPPER CASE in our UI:
ValueNotifier<String> source; //this is the one from the model object
final upperCaseSource = source.map( (s)=>s.toUpperCase() );
or you can change the type:
ValueNotifier<int> intNotifier;
final stringNotifier = intNotifier.map<String>( (s)=>s.toString() );
where()
Lets you filter the values that a ValueListenable can have:
ValueNotifier<int> intNotifier;
bool onlyEven = false; // depending on this variable we want only even values or all
final filteredNotifier = intNotifier.where( (i)=> onlyEven ? i.isEven : i );
select()
Lets you react only on changes to selected properties of a ValueListenable value.
This is usefully when you have a complex state model, and only want to react when a specific property change.
ValueNotifier<User> notifier = ValueNotifier(User(age: 18, property2: "John"));
final birthdayNotifier = notifier.select<int>((model)=> model.age); //selectedNotifier will ignore changes that does not affect age
The selector function that you pass to select
is called on every new value, but only propagate it when the returned value distinct.
chaining functions
As all the extension function (with the exception of listen
) return a new ValueNotifier
we can chain these extension functions as we need them like:
ValueNotifier<int> intNotifier;
intNotifier.where((x)=>x.isEven).map<String>( (s)=>s.toString() ).listen(print);
debounce()
If you don't want or can't handle too rapid value changes debounce
is your friend. It only propagates values if there is a pause after a value changes. Most typical example is you have a search function that polls a REST API and in every change of the search term you execute a http request. To avoid overloading your REST server you probably want to avoid that a new request is made on every keypress. I makes much more sense to wait till the user stops modifying the search term for a moment.
ValueNotifier<String> searchTerm; //this is the one from the model object
searchTerm.debounce(const Duration(milliseconds: 500)).listen((s) => callRestApi(s) );
// We ignore for this example that calling a REST API probably involves some async magic
combineLatest()
Combines two source ValueListenables
to one that gets updated with the combined source values when any of the sources values changed.
This comes in handy if you want to use one ValueListenableBuilder
with two ValueNotifiers
.
class StringIntWrapper {
final String s;
final int i;
StringIntWrapper(this.s, this.i);
@override
String toString() {
return '$s:$i';
}
}
ValueNotifier<int> intNotifier;
ValueNotifier<String> stringNotifier;
intNotifier.combineLastest<String,StringIntWrapper>(stringNotifier, (i,s)
=> StringIntWrapper(s,i)).listen(print);
mergeWith
Merges value changes of one ValueListenable
together with value changes of a List of
ValueListenables
so that when ever any of them changes the result of
mergeWith()
will change too.
final listenable1 = ValueNotifier<int>(0);
final listenable2 = ValueNotifier<int>(0);
final listenable3 = ValueNotifier<int>(0);
final listenable4 = ValueNotifier<int>(0);
listenable1.mergeWith([listenable2, listenable3, listenable4]).listen(
(x, _) => print(x));
listenable2.value = 42;
listenable1.value = 43;
listenable4.value = 44;
listenable3.value = 45;
listenable1.value = 46;
Will print 42,43,44,45,46
For details on the functions check the source documentation, the tests and the example.
CustomValueNotifier
enum CustomNotifierMode { normal, manual, always }
/// Sometimes you want a ValueNotifier where you can control when its
/// listeners are notified. With the `CustomValueNotifier` you can do this:
/// If you pass [CustomNotifierMode.always] for the [mode] parameter,
/// `notifierListeners` will be called everytime you assign a value to the
/// [value] property independent of if the value is different from the
/// previous one.
/// If you pass [CustomNotifierMode.manual] for the [mode] parameter,
/// `notifierListeners` will not be called when you assign a value to the
/// [value] property. You have to call it manually to notify the Listeners.
/// Aditionally it has a [listenerCount] property that tells you how many
/// listeners are currently listening to the notifier.
class CustomValueNotifier<T> extends ChangeNotifier
If you miss a function, open an issue on GitHub or even better make an PR :-)