In Flutter, how does InheritedNotifier differ from a plain InheritedWidget, and when would you use it for a changing setting?
answer
- wraps a Listenable
- element adds and removes the listener
- notify fires markNeedsBuild on the scope
- several notifications coalesce per frame
- updateShouldNotify only for notifier swaps
basics
~20 sInheritedNotifier holds a Listenable and notifies its dependents whenever that Listenable fires, without anyone rebuilding the scope. Its updateShouldNotify only handles the notifier object being replaced. Use it when a shared, mutable controller should drive dependents directly.
solid answer
~40 s`InheritedNotifier<T extends Listenable>` takes a `notifier` such as a `ValueNotifier<UnitSystem>`. Its element subscribes to the notifier; when it fires, the element marks itself for rebuild, and during that build it calls `notifyClients`, so registered dependents rebuild. Multiple notifications before the next frame **coalesce** into one rebuild of dependents. The default `updateShouldNotify` returns `oldWidget.notifier != notifier`, so it covers only a **swap** of the notifier object; value changes bypass it. The element also moves its listener on a swap and removes it on unmount, but it does **not** own or dispose the notifier: whoever created it does. Use it when a mutable controller already represents the setting, so no `StatefulWidget` has to rebuild the scope; a plain `InheritedWidget` fits immutable values supplied from above.
code
dart · 51 linesimport 'package:flutter/material.dart';
enum UnitSystem { metric, imperial }
class UnitsNotifierScope extends InheritedNotifier<ValueNotifier<UnitSystem>> {
const UnitsNotifierScope({super.key, required super.notifier, required super.child});
// Readers register and rebuild when the notifier fires.
static UnitSystem of(BuildContext context) =>
context.dependOnInheritedWidgetOfExactType<UnitsNotifierScope>()!.notifier!.value;
// Writers look up without registering.
static ValueNotifier<UnitSystem> controllerOf(BuildContext context) =>
context.getInheritedWidgetOfExactType<UnitsNotifierScope>()!.notifier!;
}
class UnitsHost extends StatefulWidget {
const UnitsHost({super.key, required this.child});
final Widget child;
@override
State<UnitsHost> createState() => _UnitsHostState();
}
class _UnitsHostState extends State<UnitsHost> {
final ValueNotifier<UnitSystem> _units = ValueNotifier<UnitSystem>(UnitSystem.metric);
@override
void dispose() {
_units.dispose(); // the scope never disposes it
super.dispose();
}
@override
Widget build(BuildContext context) =>
UnitsNotifierScope(notifier: _units, child: widget.child);
}
class UnitsToggle extends StatelessWidget {
const UnitsToggle({super.key});
@override
Widget build(BuildContext context) {
return Switch(
value: UnitsNotifierScope.of(context) == UnitSystem.imperial,
onChanged: (bool imperial) => UnitsNotifierScope.controllerOf(context).value =
imperial ? UnitSystem.imperial : UnitSystem.metric,
);
}
}go deeper
Recall that InheritedNotifier wraps a Listenable such as a ValueNotifier and rebuilds dependents when it fires.
Explain the listener, markNeedsBuild and notifyClients sequence, the coalescing per frame, and that updateShouldNotify only covers swapping the notifier.
Spot notifier ownership bugs: a notifier created in build that leaks and forces notifications, or one never disposed by the State that made it.
Decide when a hand-rolled Listenable scope is enough and when a state library's lifecycle, testing and selection features justify the dependency.
## Two ways a scoped value can change With a plain **`InheritedWidget`**, the data is immutable. To change the hiking app's unit system, a `StatefulWidget` above the scope calls `setState`, rebuilds the scope with a new `units` value, and `updateShouldNotify` decides whether dependents rebuild. With **`InheritedNotifier`**, the scope holds a **`Listenable`**, an object that can notify listeners, such as a `ValueNotifier<UnitSystem>`, an `Animation` or a `ScrollPosition`. The settings screen writes `notifier.value = UnitSystem.imperial`, the notifier fires, and dependents rebuild. Nothing above the scope has to rebuild. ## What the element does The private element behind `InheritedNotifier` does four things: 1. **Subscribes** to `notifier` when it is created. 2. On a notification, sets a dirty flag and calls **`markNeedsBuild`** on itself, rather than notifying straight away. 3. In its next `build`, if dirty, calls **`notifyClients`**, which calls `didChangeDependencies` on every registered dependent, then clears the flag. 4. On `update`, if the new widget carries a **different notifier**, moves its listener; on `unmount`, removes it. Because step 2 only schedules work, several notifications between two frames produce one round of dependent rebuilds, which the framework documents as **coalescing**. That matters for high-frequency sources such as an animation or a scroll position. ## The role of `updateShouldNotify` | Situation | Who decides dependents rebuild | |---|---| | The notifier fires | The element's listener, always, with no `updateShouldNotify` call | | The scope is rebuilt with the **same** notifier | `updateShouldNotify` returns `false` by default | | The scope is rebuilt with a **different** notifier | `updateShouldNotify` returns `true` by default | The default compares notifiers with `!=`. You may override it, but only to change the swap rule; it is not how value changes are detected. While `notifier` is `null`, nothing is sent, because there is nothing to listen to. ## Ownership The element adds and removes its own listener, but it never calls `dispose` on the notifier. The object that creates the `ValueNotifier`, typically a `State` above the scope, creates it once in `initState` and disposes it in `dispose`. Creating a new notifier inside a `build` method is a bug twice over: the notifier leaks, and every rebuild swaps it, so `updateShouldNotify` notifies all dependents each time. ## Reading and writing A dependent reads with a static `of` that calls `dependOnInheritedWidgetOfExactType` and returns `notifier.value`, which registers it. A widget that only **writes**, such as a settings toggle, should look up the notifier with `getInheritedWidgetOfExactType`, which does not register, so the toggle does not rebuild when the value it just set changes. ## Pitfalls seen in review - **Notifier recreated in `build`.** Each rebuild passes a different notifier, which forces a notification through `updateShouldNotify`, moves the listener, and leaves the old notifier undisposed. - **Reading the notifier without registering.** A widget that grabs the notifier through a non-registering lookup and reads `value` once shows a stale value; readers must go through the registering `of`. - **Expecting partial rebuilds.** Every dependent is notified on every fire. If most readers need one field of a larger object, split the notifier or use `InheritedModel`. - **A `null` notifier.** The constructor allows it, but then no notifications are ever sent, and an `of` that force-unwraps `notifier` throws. - **Writing during build.** Setting `value` inside a `build` method notifies listeners while the tree is being built, and in debug builds the scope's element fails the `setState() or markNeedsBuild() called during build` assertion; write from callbacks instead. ## When to choose it - **Choose `InheritedNotifier`** when a mutable controller already models the value, or when the source is inherently a `Listenable` (animation progress shared by many widgets, a scroll position driving several headers). - **Choose a plain `InheritedWidget`** when the value is small and immutable and a parent already owns it in `State`. - **Choose `InheritedModel`** when dependents read different parts and should rebuild independently; `InheritedNotifier` notifies all its dependents on every fire. The notifier classes themselves, `ChangeNotifier` and `ValueNotifier`, and packages that wrap this pattern, such as Provider, are separate topics.
- A ValueNotifier driving an InheritedNotifier fires three times within one frame. How many times do dependents rebuild?Once. Each notification only sets a dirty flag and calls `markNeedsBuild` on the scope's element. The element notifies its dependents when it builds, in the next build phase, so the three notifications collapse into a single `notifyClients` call and one rebuild of each dependent.
saying these in an interview costs you the question
- InheritedNotifier disposes its notifier when it is removed from the tree.
- updateShouldNotify decides whether a notifier's value change rebuilds dependents.
- Each notification rebuilds dependents immediately, once per notification.
- Creating the ValueNotifier inside build is fine because the scope manages it.
- InheritedNotifier lets dependents rebuild only for the fields they read.