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