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 callnotifyListeners()and it notifies every listener. It does not say what changed — it is a bare "something changed" signal. - `ValueNotifier<T>` — a
ChangeNotifierholding a single value. Itsvaluesetter 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.
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.
| Tool | Notification precision | When it is enough |
|---|---|---|
| `setState` | The whole `build` method re-runs | A 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 field | A model with a few fields, medium complexity |
| Provider / Riverpod / BLoC | Scoping plus selection plus lifetime management | Shared, async, tested state across screens |
📚 Sources and documentation
- ChangeNotifier APIofficialapi.flutter.dev
- ValueNotifier APIofficialapi.flutter.dev
The == comparison rule is documented here — the origin of the mutable list trap.
- ListenableBuilder APIofficialapi.flutter.dev
- Flutter: state management optionsofficialdocs.flutter.dev
Where the ValueNotifier + InheritedNotifier approach is officially listed.