blob: 665a8a7ec080f15363468df174ff3d91629a284b [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'framework.dart';
/// An inherited widget for a [Listenable] [notifier], which updates its
/// dependencies when the [notifier] is triggered.
///
/// This is a variant of [InheritedWidget], specialized for subclasses of
/// [Listenable], such as [ChangeNotifier] or [ValueNotifier].
///
/// Dependents are notified whenever the [notifier] sends notifications, or
/// whenever the identity of the [notifier] changes.
///
/// Multiple notifications are coalesced, so that dependents only rebuild once
/// even if the [notifier] fires multiple times between two frames.
///
/// Typically this class is subclassed with a class that provides an `of` static
/// method that calls [BuildContext.dependOnInheritedWidgetOfExactType] with that
/// class.
///
/// The [updateShouldNotify] method may also be overridden, to change the logic
/// in the cases where [notifier] itself is changed. The [updateShouldNotify]
/// method is called with the old [notifier] in the case of the [notifier] being
/// changed. When it returns true, the dependents are marked as needing to be
/// rebuilt this frame.
///
/// {@tool dartpad}
/// This example shows three spinning squares that use the value of the notifier
/// on an ancestor [InheritedNotifier] (`SpinModel`) to give them their
/// rotation. The [InheritedNotifier] doesn't need to know about the children,
/// and the `notifier` argument doesn't need to be an animation controller, it
/// can be anything that implements [Listenable] (like a [ChangeNotifier]).
///
/// The `SpinModel` class could just as easily listen to another object (say, a
/// separate object that keeps the value of an input or data model value) that
/// is a [Listenable], and get the value from that. The descendants also don't
/// need to have an instance of the [InheritedNotifier] in order to use it, they
/// just need to know that there is one in their ancestry. This can help with
/// decoupling widgets from their models.
///
/// ** See code in examples/api/lib/widgets/inherited_notifier/inherited_notifier.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [Animation], an implementation of [Listenable] that ticks each frame to
/// update a value.
/// * [ViewportOffset] or its subclass [ScrollPosition], implementations of
/// [Listenable] that trigger when a view is scrolled.
/// * [InheritedWidget], an inherited widget that only notifies dependents
/// when its value is different.
/// * [InheritedModel], an inherited widget that allows clients to subscribe
/// to changes for subparts of the value.
abstract class InheritedNotifier<T extends Listenable> extends InheritedWidget {
/// Create an inherited widget that updates its dependents when [notifier]
/// sends notifications.
///
/// The [child] argument must not be null.
const InheritedNotifier({
super.key,
this.notifier,
required super.child,
}) : assert(child != null);
/// The [Listenable] object to which to listen.
///
/// Whenever this object sends change notifications, the dependents of this
/// widget are triggered.
///
/// By default, whenever the [notifier] is changed (including when changing to
/// or from null), if the old notifier is not equal to the new notifier (as
/// determined by the `==` operator), notifications are sent. This behavior
/// can be overridden by overriding [updateShouldNotify].
///
/// While the [notifier] is null, no notifications are sent, since the null
/// object cannot itself send notifications.
final T? notifier;
@override
bool updateShouldNotify(InheritedNotifier<T> oldWidget) {
return oldWidget.notifier != notifier;
}
@override
InheritedElement createElement() => _InheritedNotifierElement<T>(this);
}
class _InheritedNotifierElement<T extends Listenable> extends InheritedElement {
_InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) {
widget.notifier?.addListener(_handleUpdate);
}
bool _dirty = false;
@override
void update(InheritedNotifier<T> newWidget) {
final T? oldNotifier = (widget as InheritedNotifier<T>).notifier;
final T? newNotifier = newWidget.notifier;
if (oldNotifier != newNotifier) {
oldNotifier?.removeListener(_handleUpdate);
newNotifier?.addListener(_handleUpdate);
}
super.update(newWidget);
}
@override
Widget build() {
if (_dirty) {
notifyClients(widget as InheritedNotifier<T>);
}
return super.build();
}
void _handleUpdate() {
_dirty = true;
markNeedsBuild();
}
@override
void notifyClients(InheritedNotifier<T> oldWidget) {
super.notifyClients(oldWidget);
_dirty = false;
}
@override
void unmount() {
(widget as InheritedNotifier<T>).notifier?.removeListener(_handleUpdate);
super.unmount();
}
}