1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6
7import 'framework.dart';
8
9/// An inherited widget for a [Listenable] [notifier], which updates its
10/// dependencies when the [notifier] is triggered.
11///
12/// This is a variant of [InheritedWidget], specialized for subclasses of
13/// [Listenable], such as [ChangeNotifier] or [ValueNotifier].
14///
15/// Dependents are notified whenever the [notifier] sends notifications, or
16/// whenever the identity of the [notifier] changes.
17///
18/// Multiple notifications are coalesced, so that dependents only rebuild once
19/// even if the [notifier] fires multiple times between two frames.
20///
21/// Typically this class is subclassed with a class that provides an `of` static
22/// method that calls [BuildContext.dependOnInheritedWidgetOfExactType] with that
23/// class.
24///
25/// The [updateShouldNotify] method may also be overridden, to change the logic
26/// in the cases where [notifier] itself is changed. The [updateShouldNotify]
27/// method is called with the old [notifier] in the case of the [notifier] being
28/// changed. When it returns true, the dependents are marked as needing to be
29/// rebuilt this frame.
30///
31/// {@tool dartpad}
32/// This example shows three spinning squares that use the value of the notifier
33/// on an ancestor [InheritedNotifier] (`SpinModel`) to give them their
34/// rotation. The [InheritedNotifier] doesn't need to know about the children,
35/// and the `notifier` argument doesn't need to be an animation controller, it
36/// can be anything that implements [Listenable] (like a [ChangeNotifier]).
37///
38/// The `SpinModel` class could just as easily listen to another object (say, a
39/// separate object that keeps the value of an input or data model value) that
40/// is a [Listenable], and get the value from that. The descendants also don't
41/// need to have an instance of the [InheritedNotifier] in order to use it, they
42/// just need to know that there is one in their ancestry. This can help with
43/// decoupling widgets from their models.
44///
45/// ** See code in examples/api/lib/widgets/inherited_notifier/inherited_notifier.0.dart **
46/// {@end-tool}
47///
48/// See also:
49///
50/// * [Animation], an implementation of [Listenable] that ticks each frame to
51/// update a value.
52/// * [ViewportOffset] or its subclass [ScrollPosition], implementations of
53/// [Listenable] that trigger when a view is scrolled.
54/// * [InheritedWidget], an inherited widget that only notifies dependents
55/// when its value is different.
56/// * [InheritedModel], an inherited widget that allows clients to subscribe
57/// to changes for subparts of the value.
58abstract class InheritedNotifier<T extends Listenable> extends InheritedWidget {
59 /// Create an inherited widget that updates its dependents when [notifier]
60 /// sends notifications.
61 const InheritedNotifier({
62 super.key,
63 this.notifier,
64 required super.child,
65 });
66
67 /// The [Listenable] object to which to listen.
68 ///
69 /// Whenever this object sends change notifications, the dependents of this
70 /// widget are triggered.
71 ///
72 /// By default, whenever the [notifier] is changed (including when changing to
73 /// or from null), if the old notifier is not equal to the new notifier (as
74 /// determined by the `==` operator), notifications are sent. This behavior
75 /// can be overridden by overriding [updateShouldNotify].
76 ///
77 /// While the [notifier] is null, no notifications are sent, since the null
78 /// object cannot itself send notifications.
79 final T? notifier;
80
81 @override
82 bool updateShouldNotify(InheritedNotifier<T> oldWidget) {
83 return oldWidget.notifier != notifier;
84 }
85
86 @override
87 InheritedElement createElement() => _InheritedNotifierElement<T>(this);
88}
89
90class _InheritedNotifierElement<T extends Listenable> extends InheritedElement {
91 _InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) {
92 widget.notifier?.addListener(_handleUpdate);
93 }
94
95 bool _dirty = false;
96
97 @override
98 void update(InheritedNotifier<T> newWidget) {
99 final T? oldNotifier = (widget as InheritedNotifier<T>).notifier;
100 final T? newNotifier = newWidget.notifier;
101 if (oldNotifier != newNotifier) {
102 oldNotifier?.removeListener(_handleUpdate);
103 newNotifier?.addListener(_handleUpdate);
104 }
105 super.update(newWidget);
106 }
107
108 @override
109 Widget build() {
110 if (_dirty) {
111 notifyClients(widget as InheritedNotifier<T>);
112 }
113 return super.build();
114 }
115
116 void _handleUpdate() {
117 _dirty = true;
118 markNeedsBuild();
119 }
120
121 @override
122 void notifyClients(InheritedNotifier<T> oldWidget) {
123 super.notifyClients(oldWidget);
124 _dirty = false;
125 }
126
127 @override
128 void unmount() {
129 (widget as InheritedNotifier<T>).notifier?.removeListener(_handleUpdate);
130 super.unmount();
131 }
132}
133