Sparround

Listenable, ChangeNotifier, ValueNotifier

Listenable is Flutter's observer interface: addListener and removeListener. The "something changed" half of every state management library is built either on this interface or on Stream.

  • `ChangeNotifier` — a ready implementation of Listenable. You call notifyListeners() and it notifies every listener. It does not say what changed — it is a bare "something changed" signal.
  • `ValueNotifier<T>` — a ChangeNotifier holding a single value. Its value setter compares the new value with the old one using == and notifies only when they differ.
  • `ListenableBuilder` / `ValueListenableBuilder` — the widgets that wire those signals into the UI: only what is inside the builder rebuilds.

The official Flutter docs list ValueNotifier + InheritedNotifier as an approach in its own right: an approach that uses only Flutter-provided APIs to update state and notify the UI of changes.

dart
class CounterView extends StatefulWidget {
  const CounterView({super.key});
  @override
  State<CounterView> createState() => _CounterViewState();
}

class _CounterViewState extends State<CounterView> {
  final _count = ValueNotifier<int>(0);

  @override
  void dispose() {
    _count.dispose(); // notifier-i sökmək listener sızmasının qarşısını alır
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const ExpensiveHeader(), // rebuild olunmur
        ValueListenableBuilder<int>(
          valueListenable: _count,
          builder: (context, value, _) => Text('$value'),
        ),
        ElevatedButton(
          onPressed: () => _count.value++,
          child: const Text('Artır'),
        ),
      ],
    );
  }
}

Local reactivity without a library — `ValueListenableBuilder` rebuilds only the `Text`.

The most common trap: mutating a list inside a ValueNotifier<List<T>> with add. value still points at the same object, == sees no difference and no notification is sent. The fix is to assign a new list each time: notifier.value = [...notifier.value, item]. This is the smallest, clearest illustration of why immutable state matters.

ToolNotification precisionWhen it is enough
`setState`The whole `build` method re-runsA small widget with simple ephemeral state
`ValueNotifier` + `ValueListenableBuilder`Only inside the builder; filtered by `==`One value, within a screen
`ChangeNotifier` + `ListenableBuilder`"Something changed" — you don't know which fieldA model with a few fields, medium complexity
Provider / Riverpod / BLoCScoping plus selection plus lifetime managementShared, async, tested state across screens

📚 Sources and documentation