listen-it-expert
listen_it Expert - ValueListenable Operators & Reactive Collections
What: Extension methods on ValueListenable/Listenable for transformations, filtering, debouncing. Plus reactive collections. Pure Dart.
CRITICAL RULES
- Operators return NEW ValueListenable objects - MUST capture the result
listen()signature differs: onListenablegets(subscription), onValueListenablegets(value, subscription)mergeWith()is an INSTANCE method on ValueListenable, NOT a static method- There is NO
throttle()operator - NEVER create operator chains inline in
build()- memory leak! Use class fields or watch_it - All operators support
{bool lazy = false}for deferred initialization
listen() - Subscribing to Changes
// On ValueListenable<T> - receives value AND subscription
final sub = counter.listen((int value, ListenableSubscription subscription) {
print('New value: $value');
if (value > 100) subscription.cancel(); // Self-cancel
});
sub.cancel(); // Or cancel externally
// On plain Listenable - receives ONLY subscription (no value)
final sub = myChangeNotifier.listen((ListenableSubscription subscription) {
print('Something changed');
});
Transformation Operators
All return new ValueListenable and support {bool lazy = false}:
final counter = ValueNotifier<int>(0);
// map - Transform values
final doubled = counter.map((x) => x * 2);
// select - Like map but only notifies when result CHANGES (equality check)
final user = ValueNotifier<User>(User('Alice', 30));
final name = user.select((u) => u.name); // Only fires when name actually changes
// where - Filter values (only propagate when test passes)
final positives = counter.where((x) => x > 0);
final positives = counter.where((x) => x > 0, fallbackValue: 0); // Default when filtered
// debounce - Delay notifications
final debouncedSearch = searchField.debounce(Duration(milliseconds: 300));
// async - Defer to next frame (prevents setState-during-build)
final deferred = counter.async();
Combining Operators
// combineLatest - Merge 2 ValueListenables
final firstName = ValueNotifier<String>('Alice');
final lastName = ValueNotifier<String>('Smith');
final fullName = firstName.combineLatest(
lastName,
(first, last) => '$first $last',
);
// combineLatest3/4/5/6 - Merge up to 6 sources
final combined = source1.combineLatest3(
source2, source3,
(v1, v2, v3) => '$v1-$v2-$v3',
);
// mergeWith - Merge multiple sources of SAME type (instance method)
final merged = source1.mergeWith([source2, source3]);
// Emits whenever any source changes, value is from the source that changed
Chaining Operators
final processed = searchInput
.where((text) => text.isNotEmpty)
.map((text) => text.trim().toLowerCase())
.debounce(Duration(milliseconds: 300));
processed.listen((query, subscription) {
performSearch(query);
});
Reactive Collections
Auto-notify listeners on mutations:
// ListNotifier
final items = ListNotifier<String>(data: ['a', 'b', 'c']);
items.add('d'); // Notifies
items.remove('a'); // Notifies
items[0] = 'z'; // Notifies
items.value; // UnmodifiableListView (read-only access)
// MapNotifier
final settings = MapNotifier<String, dynamic>(data: {'theme': 'dark'});
settings['theme'] = 'light'; // Notifies
settings.remove('theme'); // Notifies
// SetNotifier
final tags = SetNotifier<String>(data: {'flutter', 'dart'});
tags.add('mobile'); // Notifies
tags.remove('dart'); // Notifies
Notification modes:
// CustomNotifierMode.always - notify on every operation (default for collections)
// CustomNotifierMode.normal - notify only when value changes (== check)
// CustomNotifierMode.manual - no auto-notification, call notifyListeners() yourself
final items = ListNotifier<String>(
notificationMode: CustomNotifierMode.always,
);
Transactions (batch operations, single notification):
items.startTransAction();
items.add('a');
items.add('b');
items.add('c');
items.endTransAction(); // Single notification for all 3 adds
Anti-Patterns
// ❌ Not capturing operator result
counter.map((x) => x * 2); // Lost! Nobody holds a reference
// ✅ Capture it
final doubled = counter.map((x) => x * 2);
// ❌ Creating chains inline in build() - MEMORY LEAK
Widget build(context) {
final doubled = counter.map((x) => x * 2); // New chain every build!
...
}
// ✅ Use class field or watch_it (caches selector automatically)
late final doubled = counter.map((x) => x * 2); // Created once
// ❌ Using addListener instead of listen
notifier.addListener(() { print(notifier.value); });
// ✅ Use listen() from listen_it
notifier.listen((value, sub) { print(value); });
// ❌ ValueNotifier.merge (doesn't exist!)
ValueNotifier.merge([a, b], combiner); // NOT A REAL METHOD
// ✅ Use mergeWith (instance method) or combineLatest
final merged = a.mergeWith([b]);
final combined = a.combineLatest(b, (va, vb) => va + vb);
Production Patterns
Debounced auto-save:
_dataSubscription = _data
.debounce(const Duration(seconds: 1))
.listen((_, __) {
_data.saveDraft();
});
Filtered draft list:
final ListNotifier<CommonComposerData> _drafts = ListNotifier(
notificationMode: CustomNotifierMode.manual,
);
late ValueListenable<List<CommonComposerData>> savedDrafts =
(_drafts as ValueListenable<List<CommonComposerData>>)
.map((list) => list.where((e) => e.intentionallySaved).toList());
Error listening on commands:
updatePostCommand.errors.listen((error, _) {
final composerData = error!.paramData;
composerData?.saveDraft(withIntention: true);
});
CustomValueNotifier
For advanced notification control:
final notifier = CustomValueNotifier<int>(
0,
mode: CustomNotifierMode.normal, // Only notify on actual changes
asyncNotification: false, // true = defer to next frame
);
More from flutter-it/flutter_it
flutter-it
Overview of the flutter_it construction set - modular Flutter packages (get_it, watch_it, command_it, listen_it) that work standalone or together. Use when deciding which flutter_it package to use, understanding package dependencies, or getting oriented with the ecosystem.
6watch-it-expert
Expert guidance on watch_it reactive widget state management for Flutter. Covers watch functions (watch, watchIt, watchValue, watchStream, watchFuture), handler registration (registerHandler, registerStreamHandler, registerFutureHandler), lifecycle functions (callOnce, createOnce, onDispose), ordering rules, widget granularity, and startup orchestration. Use when building reactive widgets with watch_it, watching ValueListenables/Streams/Futures, or managing widget-scoped state.
6get-it-expert
Expert guidance on get_it service locator and dependency injection for Flutter/Dart. Covers registration (singleton, factory, lazy, async), scopes with shadowing, async initialization with init() pattern, retrieval, testing with scope-based mocking, and production patterns. Use when working with get_it, dependency injection, service registration, scopes, or async initialization.
6feed-datasource-expert
Expert guidance on implementing paginated feeds and infinite scroll in Flutter using FeedDataSource and PagedFeedDataSource patterns. Covers base feed data source, cursor-based pagination, auto-pagination at length-3, proxy lifecycle with reference counting, feed widget implementation, filtered feeds, event bus integration, and creation with createOnce. Use when building paginated lists, infinite scroll, feed views, or managing proxy lifecycle in feeds.
6command-it-expert
Expert guidance on command_it command pattern for Flutter. Covers command creation (async, sync, undoable, with progress), execution, observable properties (isRunning, canRun, errors, results), restrictions, error handling with ErrorFilter/ErrorReaction, global error stream, and production patterns. Use when working with command_it commands, async operations with loading/error states, or command restrictions.
6flutter-architecture-expert
Architecture guidance for Flutter apps using the flutter_it construction set (get_it, watch_it, command_it, listen_it). Covers Pragmatic Flutter Architecture (PFA) with Services/Managers/Views, feature-based project structure, manager pattern, proxy pattern with optimistic updates and override fields, DataRepository with reference counting, scoped services, widget granularity, testing, and best practices. Use when designing app architecture, structuring Flutter projects, implementing managers or proxies, or planning feature organization.
6