Not every piece of state deserves a package. When you have one value — a counter, a toggle, a selected tab — that drives one corner of the screen, ValueNotifier and ValueListenableBuilder are the lightest tools Flutter ships. The notifier holds the value and announces changes; the builder rebuilds only the widget that listens, leaving the rest of the screen perfectly still. This tutorial covers what a ValueNotifier is, how the builder scopes rebuilds, updating the value, disposing to avoid leaks, when it beats setState, and combining several notifiers, with paste-ready code.

The Complete Flutter Guide: Build Android, iOS and Web apps
Go from scratch to building industry-standard apps with Riverpod, Firebase, animations, REST APIs, and more.
Enrol nowEvery snippet below is paste-ready against current stable Flutter. If you are coming straight from setState, think of this as the same idea with a smaller blast radius for rebuilds.
I share quick before-and-after rebuild demos there that show exactly what this builder saves you.
I write longer notes on Flutter performance and state choices on LinkedIn if you want the trade-offs in depth.
ValueNotifier in one line
A ValueNotifier<T> is a ChangeNotifier that wraps exactly one value. Create it with an initial value, and read or write it through its value property.
final counter = ValueNotifier<int>(0);
counter.value = 1; // listeners are notified automatically
That is the whole API for the common case. Setting value to something different from the current value fires a notification; setting it to an equal value does nothing. There is no setState, no model class, and no package — just an observable box around one piece of data.
Because a ValueNotifier lives outside the widget tree, you can hold it in a controller, a service, or a parent State and share it with several widgets. That is its quiet superpower: the value is decoupled from any one widget's lifecycle, so two distant widgets can both read and react to the same source without a parent passing the value down by hand. It implements ValueListenable<T>, which is the interface ValueListenableBuilder consumes, so anything exposing a ValueListenable — including framework controllers — plugs straight into the same builder.
ValueListenableBuilder rebuilds only what listens
To turn a notifier into UI, wrap the part that depends on it in a ValueListenableBuilder. Its builder runs again every time the value changes, and only that subtree rebuilds.
ValueListenableBuilder<int>(
valueListenable: counter,
builder: (context, value, child) {
return Text('Count: $value');
},
)
The surrounding widgets — the Scaffold, the AppBar, sibling widgets — never rebuild. The optional child argument is a subtree that does not depend on the value, so you can build it once and reuse it across rebuilds instead of recreating it each time:
ValueListenableBuilder<int>(
valueListenable: counter,
child: const Icon(Icons.star), // built once
builder: (context, value, child) {
return Row(children: [child!, Text('$value')]);
},
)
Updating the value
You change state by assigning value — there is no separate notifyListeners call to make in the simple case, because the setter calls it for you when the value actually changes.
void increment() => counter.value++;
void reset() => counter.value = 0;
Under the hood a ValueNotifier is a ChangeNotifier, so notifyListeners is what ultimately fires — the value setter invokes it. The only catch is equality: the setter compares old and new with ==, so mutating an object in place (without changing the reference) will not notify. We come back to that in the FAQ.
If you prefer to listen imperatively rather than through a builder — to fire a side effect, navigate, or show a snackbar when the value changes — add a listener directly. Just remember every listener you add by hand must be removed, or you leak:
void _onChange() => print('now ${counter.value}');
counter.addListener(_onChange);
// later, before disposing:
counter.removeListener(_onChange);
For UI that simply displays the value, prefer ValueListenableBuilder — it adds and removes the listener for you and ties it to the widget's lifecycle, so there is nothing to clean up.

Master Flutter state, from notifiers to Riverpod
The Complete Flutter Guide takes you from ValueNotifier up to full app architecture inside real, shipped apps.
Enrol nowDispose to avoid leaks
If you create a notifier inside a State, you own its lifecycle. Create it in initState (or as a final field) and dispose it in dispose, or its listeners linger and you leak memory.
class _CounterState extends State<Counter> {
final _counter = ValueNotifier<int>(0);
@override
void dispose() {
_counter.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<int>(
valueListenable: _counter,
builder: (_, value, __) => Text('$value'),
);
}
}
You do not need to remove listeners added by ValueListenableBuilder — it cleans up after itself. You only dispose notifiers you created.
ValueNotifier vs setState
setState reruns the entire build method of the State, so every widget in that method is rebuilt, even ones unrelated to the change. A ValueNotifier with a ValueListenableBuilder rebuilds only the builder's subtree. For a single value buried inside a large screen, that is a meaningful saving and keeps animations and scroll positions in untouched siblings intact.
The trade-off is ceremony: setState needs no fields and no disposal, so for a quick local toggle inside a small widget it is still the simplest choice. Reach for a ValueNotifier when the value lives outside build, when you want to share it, or when you want to stop a big screen rebuilding for one small change.
Combining multiple notifiers
You can nest builders, but for a widget that depends on two or more notifiers, Listenable.merge is cleaner. It produces a single Listenable that fires when any of its sources changes, which pairs with the more general ListenableBuilder.
final name = ValueNotifier<String>('');
final age = ValueNotifier<int>(0);
ListenableBuilder(
listenable: Listenable.merge([name, age]),
builder: (context, child) {
return Text('${name.value} is ${age.value}');
},
)
When you find yourself merging three or more notifiers, or wiring up cross-field logic, that is the signal you have outgrown a bag of single values — a ChangeNotifier model holding the related fields together is the next step up.
Common mistakes
- Mutating instead of reassigning. Calling
list.addon the held value does not notify; assign a new list tovalueinstead. - Forgetting to dispose. A notifier you create in a
Statemust be disposed, or its listeners leak. - Wrapping the whole screen in the builder. That defeats the point — wrap only the widget that uses the value.
- Recreating the notifier in build. Make it a field or create it in
initState; a fresh notifier each build loses state and listeners. - Building the static child inside the builder. Pass it via the
childargument so it is not rebuilt on every change.
Frequently asked questions
What is a ValueNotifier in Flutter?
It is a tiny ChangeNotifier that holds a single value of type T and notifies listeners automatically when you assign a new value via notifier.value = x. It is the lightest built-in way to make one piece of state observable, with no package required.
How is ValueListenableBuilder different from setState?
setState rebuilds the whole State's build method, while ValueListenableBuilder rebuilds only the small builder subtree that listens to the notifier. That makes it more surgical and usually cheaper for a single changing value, especially inside large screens.
Do I need to dispose a ValueNotifier?
Yes, if you create it yourself in a State, dispose it in the dispose method to release listeners and avoid leaks. A ValueNotifier is a ChangeNotifier, so the same lifecycle rules apply.
Can I hold a list or object in a ValueNotifier?
Yes, but it only notifies when the value reference changes, not when you mutate the object in place. Assign a new collection — for example notifier.value = [...notifier.value, item] — rather than calling add on the existing one. The same applies to maps and custom objects.
Further reads
Keep going with the tutorials that pair with this guide:
- Flutter Development Guide 2026 — the full Flutter hub.
- Flutter ChangeNotifier and Provider Basics — when you outgrow a single value.
- Flutter setState Explained — the baseline you are replacing.
- Flutter FutureBuilder vs StreamBuilder — rebuild on async data.
- Flutter State Management in 2026 — the bigger picture.
Sources: Flutter documentation — ValueNotifier, ValueListenableBuilder, and ChangeNotifier (api.flutter.dev). Verified against current stable Flutter.