• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2018 The Chromium 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.inheritFromWidgetOfExactType] 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/// See also:
32///
33///  * [Animation], an implementation of [Listenable] that ticks each frame to
34///    update a value.
35///  * [ViewportOffset] or its subclass [ScrollPosition], implementations of
36///    [Listenable] that trigger when a view is scrolled.
37///  * [InheritedWidget], an inherited widget that only notifies dependents
38///    when its value is different.
39///  * [InheritedModel], an inherited widget that allows clients to subscribe
40///    to changes for subparts of the value.
41abstract class InheritedNotifier<T extends Listenable> extends InheritedWidget {
42  /// Create an inherited widget that updates its dependents when [notifier]
43  /// sends notifications.
44  ///
45  /// The [child] argument must not be null.
46  const InheritedNotifier({
47    Key key,
48    this.notifier,
49    @required Widget child,
50  }) : assert(child != null),
51       super(key: key, child: child);
52
53  /// The [Listenable] object to which to listen.
54  ///
55  /// Whenever this object sends change notifications, the dependents of this
56  /// widget are triggered.
57  ///
58  /// By default, whenever the [notifier] is changed (including when changing to
59  /// or from null), if the old notifier is not equal to the new notifier (as
60  /// determined by the `==` operator), notifications are sent. This behavior
61  /// can be overridden by overriding [updateShouldNotify].
62  ///
63  /// While the [notifier] is null, no notifications are sent, since the null
64  /// object cannot itself send notifications.
65  final T notifier;
66
67  @override
68  bool updateShouldNotify(InheritedNotifier<T> oldWidget) {
69    return oldWidget.notifier != notifier;
70  }
71
72  @override
73  _InheritedNotifierElement<T> createElement() => _InheritedNotifierElement<T>(this);
74}
75
76class _InheritedNotifierElement<T extends Listenable> extends InheritedElement {
77  _InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) {
78    widget.notifier?.addListener(_handleUpdate);
79  }
80
81  @override
82  InheritedNotifier<T> get widget => super.widget;
83
84  bool _dirty = false;
85
86  @override
87  void update(InheritedNotifier<T> newWidget) {
88    final T oldNotifier = widget.notifier;
89    final T newNotifier = newWidget.notifier;
90    if (oldNotifier != newNotifier) {
91      oldNotifier?.removeListener(_handleUpdate);
92      newNotifier?.addListener(_handleUpdate);
93    }
94    super.update(newWidget);
95  }
96
97  @override
98  Widget build() {
99    if (_dirty)
100      notifyClients(widget);
101    return super.build();
102  }
103
104  void _handleUpdate() {
105    _dirty = true;
106    markNeedsBuild();
107  }
108
109  @override
110  void notifyClients(InheritedNotifier<T> oldWidget) {
111    super.notifyClients(oldWidget);
112    _dirty = false;
113  }
114
115  @override
116  void unmount() {
117    widget.notifier?.removeListener(_handleUpdate);
118    super.unmount();
119  }
120}
121