| // 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 --template=stateful_widget_material_ticker} |
| /// |
| /// 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. |
| /// |
| /// ```dart dartImports |
| /// import 'dart:math' as math; |
| /// ``` |
| /// |
| /// ```dart preamble |
| /// class SpinModel extends InheritedNotifier<AnimationController> { |
| /// const SpinModel({ |
| /// Key? key, |
| /// AnimationController? notifier, |
| /// required Widget child, |
| /// }) : super(key: key, notifier: notifier, child: child); |
| /// |
| /// static double of(BuildContext context) { |
| /// return context.dependOnInheritedWidgetOfExactType<SpinModel>()!.notifier!.value; |
| /// } |
| /// } |
| /// |
| /// class Spinner extends StatelessWidget { |
| /// const Spinner({Key? key}) : super(key: key); |
| /// |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return Transform.rotate( |
| /// angle: SpinModel.of(context) * 2.0 * math.pi, |
| /// child: Container( |
| /// width: 100, |
| /// height: 100, |
| /// color: Colors.green, |
| /// child: const Center( |
| /// child: Text('Whee!'), |
| /// ), |
| /// ), |
| /// ); |
| /// } |
| /// } |
| /// ``` |
| /// |
| /// ```dart |
| /// late AnimationController _controller; |
| /// |
| /// @override |
| /// void initState() { |
| /// super.initState(); |
| /// _controller = AnimationController( |
| /// duration: const Duration(seconds: 10), |
| /// vsync: this, |
| /// )..repeat(); |
| /// } |
| /// |
| /// @override |
| /// void dispose() { |
| /// _controller.dispose(); |
| /// super.dispose(); |
| /// } |
| /// |
| /// @override |
| /// Widget build(BuildContext context) { |
| /// return SpinModel( |
| /// notifier: _controller, |
| /// child: Row( |
| /// mainAxisAlignment: MainAxisAlignment.spaceAround, |
| /// children: const <Widget>[ |
| /// Spinner(), |
| /// Spinner(), |
| /// Spinner(), |
| /// ], |
| /// ), |
| /// ); |
| /// } |
| /// ``` |
| /// {@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({ |
| Key? key, |
| this.notifier, |
| required Widget child, |
| }) : assert(child != null), |
| super(key: key, child: child); |
| |
| /// 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 |
| _InheritedNotifierElement<T> createElement() => _InheritedNotifierElement<T>(this); |
| } |
| |
| class _InheritedNotifierElement<T extends Listenable> extends InheritedElement { |
| _InheritedNotifierElement(InheritedNotifier<T> widget) : super(widget) { |
| widget.notifier?.addListener(_handleUpdate); |
| } |
| |
| @override |
| InheritedNotifier<T> get widget => super.widget as InheritedNotifier<T>; |
| |
| bool _dirty = false; |
| |
| @override |
| void update(InheritedNotifier<T> newWidget) { |
| final T? oldNotifier = widget.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); |
| return super.build(); |
| } |
| |
| void _handleUpdate() { |
| _dirty = true; |
| markNeedsBuild(); |
| } |
| |
| @override |
| void notifyClients(InheritedNotifier<T> oldWidget) { |
| super.notifyClients(oldWidget); |
| _dirty = false; |
| } |
| |
| @override |
| void unmount() { |
| widget.notifier?.removeListener(_handleUpdate); |
| super.unmount(); |
| } |
| } |