FLUTTER ECOSYSTEM

flutter-it/listen_it

機能リスナの再ブランディングフォーク、将来の開発はここに行われます

listen_it のプロジェクト画像
Stars
14
Forks
1
最終プッシュ(UTC)
2026/02/20
プロジェクト状態
公開中
flutter-it GitHub avatar
GITHUB Organization

flutter-it ↗

言語DartC++CMakeSwiftCHTMLShellKotlinObjective-C

このリポジトリが公開するパッケージ

使用している依存関係

依存関係一覧 6 件

元の README

以下は英語原文のスナップショットです。最新版は GitHub をご覧ください。

README を開く / 閉じる
listen_it logo

listen_it codecov

📚 Complete documentation available at flutter-it.dev Check out the comprehensive docs with detailed guides, examples, and best practices!

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.
  • 🔗 Chainable Operators — Transform, filter, combine ValueListenables with map(), select(), where(), debounce(), combineLatest(), mergeWith().
  • 🎯 Selective Updates — React only to specific property changes with select(). Avoid unnecessary rebuilds.
  • ⚡ Transaction Support — Batch multiple operations into a single notification for optimal performance.
  • 🔒 Type Safe — Full compile-time type checking. No runtime surprises.
  • 📦 Pure Dart Core — Operators work in pure Dart (collections require Flutter for ChangeNotifier).

Learn more about listen_it →

💡 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.

Quick Start

Installation

Add to your pubspec.yaml:

dependencies:
  listen_it: ^5.1.0
Reactive Collections

Simply wrap your collection type with a notifier:

// Instead of:
final items = <String>[];

// Use:
final items = ListNotifier<String>();

// With initial data:
final items = ListNotifier<String>(data: ['item1', 'item2']);

All standard collection methods work as expected - the difference is they now notify listeners!

Integration with Flutter
class TodoListWidget extends StatelessWidget {
  final todos = ListNotifier<String>();

  @override
  Widget build(BuildContext context) {
    return ValueListenableBuilder<List<String>>(
      valueListenable: todos,
      builder: (context, items, _) {
        return ListView.builder(
          itemCount: items.length,
          itemBuilder: (context, index) => Text(items[index]),
        );
      },
    );
  }
}
ValueListenable Operators
listen()

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:

listenable.listen((x, subscription) {
  print(x);
  if (x == 42) {
     subscription.cancel();
  }
});

This is particularly useful when you want a handler to run only once or a certain number of times:

// Run only once
listenable.listen((x, subscription) {
  print('First value: $x');
  subscription.cancel();
});

// Run exactly 3 times
var count = 0;
listenable.listen((x, subscription) {
  print('Value: $x');
  if (++count >= 3) subscription.cancel();
});

For regular Listenable (not ValueListenable), the handler only receives the subscription parameter since there's no value to access:

final listenable = ChangeNotifier();
listenable.listen((subscription) => print('Changed!'));
Chaining Operators

Chain operators to build reactive data pipelines:

final searchTerm = ValueNotifier<String>('');

searchTerm
    .debounce(const Duration(milliseconds: 300))
    .where((term) => term.length >= 3)
    .listen((term, _) => callSearchApi(term));

That's it! Collections notify automatically, operators let you transform data reactively.

Key Features

Reactive Collections

Choose the collection that fits your needs:

  • ListNotifier — Order matters, duplicates allowed. Perfect for: todo lists, chat messages, search history. Read more →

  • MapNotifier<K,V> — Key-value lookups. Perfect for: user preferences, caches, form data. Read more →

  • SetNotifier — Unique items only, fast membership tests. Perfect for: selected item IDs, active filters, tags. Read more →

Notification Modes:

  • always (default) — Notify on every operation
  • normal — Only notify on actual changes
  • manual — You control when to notify

Read more about notification modes →

Transactions — Batch operations into single notification:

products.startTransAction();
products.add(item1);
products.add(item2);
products.add(item3);
products.endTransAction(); // Single notification

Read more →

ValueListenable Operators

Transform and combine observables:

  • listen() — Install handlers that react to value changes. The foundation for reactive programming with ValueListenables.

    listenable.listen((value, subscription) => print(value));
    
  • map() — Transform values to different types

  • select() — React only when specific properties change

  • where() — Filter which values propagate (now with optional fallbackValue for initial value handling!)

  • debounce() — Control rapid value changes (great for search!)

  • async() — Defer updates to next frame to avoid setState-during-build

  • combineLatest() — Merge multiple ValueListenables (supports 2-6 sources)

  • mergeWith() — Combine value changes from multiple sources

Read operator documentation →

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:

  1. Attach - the chain subscribes to its source(s) when it is created (default) or, with lazy: true, when it gets its first listener.
  2. 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.
  3. 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);

void listener() {}
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:

class MyWidget extends WatchingWidget {
  @override
  Widget build(BuildContext context) {
    // selector is cached (called once per widget instance); when the widget
    // is disposed the chain loses its listener and detaches from m.source
    final value = watchValue((Model m) => m.source.map((x) => x * 2));
    return Text('$value');
  }
}

Two things still matter:

Don't create chains on every rebuild - each one is a new object doing work while it is attached:

Widget build(BuildContext context) {
  return ValueListenableBuilder(
    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.

class MyService {
  final counter = ValueNotifier<int>(0);
  late final doubled = counter.map((x) => x * 2);

  void dispose() {
    counter.dispose(); // stops notifications; the chain is GC'd with the service
  }
}

Read complete disposal guide →

Read complete best practices guide →

Ecosystem Integration

listen_it works independently — Use it standalone for reactive collections and operators in any Dart or Flutter project.

Want more? Combine with other packages from the flutter_it ecosystem:

  • Optional: watch_it — Reactive state management with automatic selector caching. Makes inline chain creation safe! Highly recommended for listen_it operator chains.

  • Optional: get_it — Dependency injection. Register your ListNotifiers, ValueNotifiers, and chains in get_it for global access.

  • Optional: command_it — Command pattern with automatic state tracking. Uses listen_it operators internally.

Remember: flutter_it is a construction set. Each package works independently. Pick what you need, combine as you grow.

Learn about the ecosystem →

AI-Assisted Development

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:

dart run skills@ add flutter-it/flutter_it

Learn more about AI skills →

Learn More

Documentation
Community & Support

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE file for details.


Part of the flutter_it ecosystem — Build reactive Flutter apps the easy way. No codegen, no boilerplate, just code.