| // 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(); |
| } |
| } |