inherited_model.dart 7.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
// Copyright 2018 The Chromium 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 'dart:collection';

import 'package:flutter/foundation.dart';

import 'framework.dart';

/// An [InheritedWidget] that's intended to be used as the base class for
/// models whose dependents may only depend on one part or "aspect" of the
/// overall model.
///
/// An inherited widget's dependents are unconditionally rebuilt when the
/// inherited widget changes per [InheritedWidget.updateShouldNotify].
/// This widget is similar except that dependents aren't rebuilt
/// unconditionally.
///
/// Widgets that depend on an [InheritedModel] qualify their dependence
/// with a value that indicates what "aspect" of the model they depend
/// on. When the model is rebuilt, dependents will also be rebuilt, but
/// only if there was a change in the model that corresponds to the aspect
/// they provided.
///
/// The type parameter `T` is the type of the model aspect objects.
///
/// Widgets create a dependency on an [InheritedModel] with a static method:
/// [InheritedModel.inheritFrom]. This method's `context` parameter
/// defines the subtree that will be rebuilt when the model changes.
/// Typically the `inheritFrom` method is called from a model-specific
/// static `of` method. For example:
///
/// ```dart
/// class MyModel extends InheritedModel<String> {
///   // ...
///   static MyModel of(BuildContext context, String aspect) {
///     return InheritedModel.inheritFrom<MyModel>(context, aspect: aspect);
///   }
/// }
/// ```
///
/// Calling `MyModel.of(context, 'foo')` means that `context` should only
/// be rebuilt when the `foo` aspect of `MyModel` changes. If the aspect
/// is null, then the model supports all aspects.
///
/// When the inherited model is rebuilt the [updateShouldNotify] and
/// [updateShouldNotifyDependent] methods are used to decide what
/// should be rebuilt.  If [updateShouldNotify] returns true, then the
/// inherited model's [updateShouldNotifyDependent] method is tested for
/// each dependent and the set of aspect objects it depends on.
/// The [updateShouldNotifyDependent] method must compare the set of aspect
/// dependencies with the changes in the model itself.
///
/// For example:
///
/// ```dart
/// class ABModel extends InheritedModel<String> {
///   ABModel({ this.a, this.b, Widget child }) : super(child: child);
///
///   final int a;
///   final int b;
///
///   @override
///   bool updateShouldNotify(ABModel old) {
///     return a != old.a || b != old.b;
///   }
///
///   @override
///   bool updateShouldNotifyDependent(ABModel old, Set<String> aspects) {
///     return (a != old.a && aspects.contains('a'))
///         || (b != old.b && aspects.contains('b'))
///   }
///
///   // ...
/// }
/// ```
///
/// In the previous example the dependencies checked by
/// [updateShouldNotifyDependent] are just the aspect strings passed to
/// `inheritFromWidgetOfExactType`. They're represented as a [Set] because
/// one Widget can depend on more than one aspect of the model.
/// If a widget depends on the model but doesn't specify an aspect,
/// then changes in the model will cause the widget to be rebuilt
/// unconditionally.
86 87 88 89 90 91 92 93
///
/// See also:
///
///  * [InheritedWidget], an inherited widget that only notifies dependents
///    when its value is different.
///  * [InheritedNotifier], an inherited widget whose value can be a
///    [Listenable], and which will notify dependents whenever the value
///    sends notifications.
94 95 96 97 98 99 100
abstract class InheritedModel<T> extends InheritedWidget {
  /// Creates an inherited widget that supports dependencies qualified by
  /// "aspects", i.e. a descendant widget can indicate that it should
  /// only be rebuilt if a specific aspect of the model changes.
  const InheritedModel({ Key key, Widget child }) : super(key: key, child: child);

  @override
101
  InheritedModelElement<T> createElement() => InheritedModelElement<T>(this);
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189

  /// Return true if the changes between this model and [oldWidget] match any
  /// of the [dependencies].
  @protected
  bool updateShouldNotifyDependent(covariant InheritedModel<T> oldWidget, Set<T> dependencies);

  /// Returns true if this model supports the given [aspect].
  ///
  /// Returns true by default: this model supports all aspects.
  ///
  /// Subclasses may override this method to indicate that they do not support
  /// all model aspects. This is typically done when a model can be used
  /// to "shadow" some aspects of an ancestor.
  @protected
  bool isSupportedAspect(Object aspect) => true;

  // The [result] will be a list of all of context's type T ancestors concluding
  // with the one that supports the specified model [aspect].
  static Iterable<InheritedElement> _findModels<T extends InheritedModel<Object>>(BuildContext context, Object aspect) sync* {
    final InheritedElement model = context.ancestorInheritedElementForWidgetOfExactType(T);
    if (model == null)
      return;

    yield model;

    assert(model.widget is T);
    final T modelWidget = model.widget;
    if (modelWidget.isSupportedAspect(aspect))
      return;

    Element modelParent;
    model.visitAncestorElements((Element ancestor) {
      modelParent = ancestor;
      return false;
    });
    if (modelParent == null)
      return;

    yield* _findModels<T>(modelParent, aspect);
  }

  /// Makes [context] dependent on the specified [aspect] of an [InheritedModel]
  /// of type T.
  ///
  /// When the given [aspect] of the model changes, the [context] will be
  /// rebuilt. The [updateShouldNotifyDependent] method must determine if a
  /// change in the model widget corresponds to an [aspect] value.
  ///
  /// The dependencies created by this method target all [InheritedModel] ancestors
  /// of type T up to and including the first one for which [isSupportedAspect]
  /// returns true.
  ///
  /// If [aspect] is null this method is the same as
  /// `context.inheritFromWidgetOfExactType(T)`.
  static T inheritFrom<T extends InheritedModel<Object>>(BuildContext context, { Object aspect }) {
    if (aspect == null)
      return context.inheritFromWidgetOfExactType(T);

    // Create a dependency on all of the type T ancestor models up until
    // a model is found for which isSupportedAspect(aspect) is true.
    final List<InheritedElement> models = _findModels<T>(context, aspect).toList();
    final InheritedElement lastModel = models.last;
    for (InheritedElement model in models) {
      final T value = context.inheritFromElement(model, aspect: aspect);
      if (model == lastModel)
        return value;
    }

    assert(false);
    return null;
  }
}

/// An [Element] that uses a [InheritedModel] as its configuration.
class InheritedModelElement<T> extends InheritedElement {
  /// Creates an element that uses the given widget as its configuration.
  InheritedModelElement(InheritedModel<T> widget) : super(widget);

  @override
  InheritedModel<T> get widget => super.widget;

  @override
  void updateDependencies(Element dependent, Object aspect) {
    final Set<T> dependencies = getDependencies(dependent);
    if (dependencies != null && dependencies.isEmpty)
      return;

    if (aspect == null) {
190
      setDependencies(dependent, HashSet<T>());
191 192
    } else {
      assert(aspect is T);
193
      setDependencies(dependent, (dependencies ?? HashSet<T>())..add(aspect));
194 195 196 197 198 199 200 201 202 203 204 205
    }
  }

  @override
  void notifyDependent(InheritedModel<T> oldWidget, Element dependent) {
    final Set<T> dependencies = getDependencies(dependent);
    if (dependencies == null)
      return;
    if (dependencies.isEmpty || widget.updateShouldNotifyDependent(oldWidget, dependencies))
      dependent.didChangeDependencies();
  }
}