Flutter용 반응형 기본 요소 - 관찰 가능한 컬렉션과 강력한 연산자입니다. Streams처럼 ValueNotifiers를 사용하세요. ListNotifier, MapNotifier, SetNotifier 포함. 이전에 functional_listener로 출판되었습니다.
ListenIt는 Flutter용 반응형 상태 관리 패키지로, ListNotifier, MapNotifier, SetNotifier와 같은 관찰 가능한 컬렉션을 제공하여 ValueNotifiers를 Streams처럼 사용할 수 있게 해줍니다. 데이터 스트림을 변환하고 결합하기 위한 강력한 연산자를 제공하며, 함수형 반응형 프로그래밍 스타일을 촉진합니다. 원래 functional_listener로 출시되었으며, 최소한의 부가 코드로 상태 업데이트와 구독을 간소화합니다. 반응형 UI에서 동적 데이터를 관리하는 데 이상적입니다.
사용 사례
실시간 데이터 동기화
동적 목록 또는 맵 상태 관리
최소한의 코드로 반응형 UI 업데이트
ValueNotifier에 대한 스트림과 유사한 동작
복잡한 상태 전환
주요 기능
관찰 가능한 컬렉션(ListNotifier, MapNotifier, SetNotifier)
Reactive primitives for Flutter - observable collections and powerful operators for ValueListenable.
Managing reactive state in Flutter can be complex. You need collections that notify listeners when they change, operators to transform and combine observables, and patterns that don't cause memory leaks. listen_it provides two powerful primitives: reactive collections (ListNotifier, MapNotifier, SetNotifier) that automatically notify on mutations, and extension operators on ValueListenable (map, select, where, debounce, combineLatest) that let you build reactive data pipelines.
Previously published as functional_listener. Now includes reactive collections from listenable_collections.
flutter_it is a construction set — listen_it works perfectly standalone or combine it with other packages like watch_it (which provides automatic selector caching for safe inline chain creation!), get_it (dependency injection), or command_it (which uses listen_it internally). Use what you need, when you need it.
Why listen_it?
🔔 Reactive Collections — ListNotifier, MapNotifier, SetNotifier that automatically notify listeners on mutations. No manual notifyListeners() calls needed.
💡 Chain lifecycle (v6.0.0+): Operator chains subscribe to their source eagerly on creation (or on the first listener with lazy: true), detach from the source when their last listener is removed and re-attach on the next one. While a chain has no listeners its .value is derived from the current source value on read, so it is never stale, and a chain that nobody references any more can be garbage collected. For best practices, see the complete documentation.
Lets you work with a ValueListenable (and Listenable) as it should be by installing a handler function that is called on any value change and gets the new value passed as an argument. This gives you the same pattern as with Streams, making it natural and consistent.
final listenable = ValueNotifier<int>(0);
final subscription = listenable.listen((x, _) => print(x));
The returned subscription can be used to deactivate the handler. As you might need to uninstall the handler from inside the handler you get the subscription object passed to the handler function as second parameter:
Chain Lifecycle: Attach, Detach and Derived Values (v6.0.0+)
An operator chain (source.map(...), a.combineLatest(b, ...), ...) is a ValueListenable that follows this lifecycle:
Attach - the chain subscribes to its source(s) when it is created (default) or, with lazy: true, when it gets its first listener.
Detach - when the last listener is removed, the chain unsubscribes from its source(s). In a longer chain this cascades down to the original source.
Re-attach - the next addListener subscribes again and refreshes the stored value before the new listener is registered, so nothing is missed and no spurious notification is sent.
While a chain is detached its .value is derived from the current source value on read, so it is never stale:
final source = ValueNotifier<int>(1);
final doubled = source.map((x) => x * 2);
voidlistener() {}
doubled.addListener(listener);
doubled.removeListener(listener); // last listener gone -> detached
source.value = 5;
print(doubled.value); // 10 ✓ derived on read, no subscription needed
doubled.addListener(listener); // re-attached, value already fresh
Operator
.value while detached
map, select
transform / selector applied to the current source value
where
current source value if it passes the filter, otherwise the last passing value
debounce, async
current source value
combineLatest
combiner applied to the current source values
mergeWith
last received value (it can't know which source changed last)
Because the transformation may run on read while detached, keep transformation functions pure.
lazy: true only changes step 1: the first subscription happens on the first listener instead of on creation. Before that, .value is derived on read exactly as for a detached chain, so lazy: true is a pure memory optimisation with no stale-value trade-off.
Chain Lifecycle & Memory Management
Because a chain releases its source as soon as nobody listens to it any more, a chain that is created for a widget and discarded with it no longer leaves a dangling listener on the source. With watch_it, creating chains inline in a selector is therefore safe:
classMyWidgetextendsWatchingWidget{
@overrideWidgetbuild(BuildContext context) {
// selector is cached (called once per widget instance); when the widget// is disposed the chain loses its listener and detaches from m.sourcefinal value = watchValue((Model m) => m.source.map((x) => x * 2));
returnText('$value');
}
}
Two things still matter:
❌ Don't create chains on every rebuild - each one is a new object doing work while it is attached:
Widgetbuild(BuildContext context) {
returnValueListenableBuilder(
valueListenable: source.map((x) => x * 2), // NEW CHAIN EVERY REBUILD!
builder: (context, value, _) => Text('$value'),
);
}
Create the chain once instead (a field, late final, or createOnce with watch_it).
✅ Chains you keep alive yourself (e.g. as a field of a long-lived manager) stay attached while they have listeners and re-attach when needed - just like any other ValueListenable.
Disposal & Garbage Collection
Chains don't require manual disposal in most cases. A chain without listeners holds no subscription on its source, so it is garbage collected as soon as nothing references it any more; and when the whole graph (source + chain) becomes unreachable, Dart's GC collects it regardless.
Call dispose() on a chain only when you want to end it explicitly while it still has listeners, or to be sure a pending debounce timer / async update is cancelled.
classMyService{
final counter = ValueNotifier<int>(0);
latefinal doubled = counter.map((x) => x * 2);
voiddispose() {
counter.dispose(); // stops notifications; the chain is GC'd with the service
}
}
This package ships an Agent Skill for AI coding assistants
(Claude Code, Cursor, GitHub Copilot, Codex, Gemini CLI and others) in skills/listen-it-expert/.
It teaches them the critical rules, common patterns and anti-patterns of listen_it.
Install it into your project with the official Dart skills tool:
dart run skills@ get
For the ecosystem-wide skills (architecture guidance, feed/data-source patterns, overview)
and the skills of the other flutter_it packages run: