Revert "InheritedModel - an InheritedWidget for data models (#19739)" (#21523)
This reverts commit 343b57036a94ee9b0131d4d957372a147f8d838d.
Reverting because the assert introduced in framework.dart(inheritFromElement) is breaking certain Mulligan pages. @hixie suspects it might be due to global keys.
diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart
index c42c958..12e2802 100644
--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -1870,19 +1870,6 @@
/// render object is usually short.
Size get size;
- /// Registers this build context with [ancestor] such that when
- /// [ancestor]'s widget changes this build context is rebuilt.
- ///
- /// Returns `ancestor.widget`.
- ///
- /// This method is rarely called directly. Most applications should use
- /// [inheritFromWidgetOfExactType], which calls this method after finding
- /// the appropriate [InheritedElement] ancestor.
- ///
- /// All of the qualifications about when [inheritFromWidgetOfExactType] can
- /// be called apply to this method as well.
- InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect });
-
/// Obtains the nearest widget of the given type, which must be the type of a
/// concrete [InheritedWidget] subclass, and registers this build context with
/// that widget such that when that widget changes (or a new widget of that
@@ -1892,7 +1879,7 @@
/// This is typically called implicitly from `of()` static methods, e.g.
/// [Theme.of].
///
- /// This method should not be called from widget constructors or from
+ /// This should not be called from widget constructors or from
/// [State.initState] methods, because those methods would not get called
/// again if the inherited value were to change. To ensure that the widget
/// correctly updates itself when the inherited value changes, only call this
@@ -1905,9 +1892,9 @@
/// It is safe to use this method from [State.deactivate], which is called
/// whenever the widget is removed from the tree.
///
- /// It is also possible to call this method from interaction event handlers
- /// (e.g. gesture callbacks) or timers, to obtain a value once, if that value
- /// is not going to be cached and reused later.
+ /// It is also possible to call this from interaction event handlers (e.g.
+ /// gesture callbacks) or timers, to obtain a value once, if that value is not
+ /// going to be cached and reused later.
///
/// Calling this method is O(1) with a small constant factor, but will lead to
/// the widget being rebuilt more often.
@@ -1917,12 +1904,7 @@
/// called, whenever changes occur relating to that widget until the next time
/// the widget or one of its ancestors is moved (for example, because an
/// ancestor is added or removed).
- ///
- /// The [aspect] parameter is only used when [targetType] is an
- /// [InheritedWidget] subclasses that supports partial updates, like
- /// [InheritedModel]. It specifies what "aspect" of the inherited
- /// widget this context depends on.
- InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect });
+ InheritedWidget inheritFromWidgetOfExactType(Type targetType);
/// Obtains the element corresponding to the nearest widget of the given type,
/// which must be the type of a concrete [InheritedWidget] subclass.
@@ -3244,31 +3226,15 @@
}
@override
- InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }) {
- assert(ancestor != null);
- assert(() {
- if (_parent == null) {
- // We're being deactivated, see deactivateChild()
- return true;
- }
- Element element = _parent;
- while (ancestor != element && element != null)
- element = element._parent;
- return ancestor == element;
- }());
- _dependencies ??= new HashSet<InheritedElement>();
- _dependencies.add(ancestor);
- ancestor.updateDependencies(this, aspect);
- return ancestor.widget;
- }
-
- @override
- InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }) {
+ InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
assert(_debugCheckStateIsActiveForAncestorLookup());
final InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
- return inheritFromElement(ancestor, aspect: aspect);
+ _dependencies ??= new HashSet<InheritedElement>();
+ _dependencies.add(ancestor);
+ ancestor._dependents.add(this);
+ return ancestor.widget;
}
_hadUnsatisfiedDependencies = true;
return null;
@@ -3879,13 +3845,11 @@
}
@override
- InheritedWidget inheritFromElement(Element ancestor, { Object aspect }) {
- assert(ancestor != null);
+ InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
assert(() {
- final Type targetType = ancestor.widget.runtimeType;
if (state._debugLifecycleState == _StateLifecycle.created) {
throw new FlutterError(
- 'inheritFromWidgetOfExactType($targetType) or inheritFromElement() was called before ${_state.runtimeType}.initState() completed.\n'
+ 'inheritFromWidgetOfExactType($targetType) was called before ${_state.runtimeType}.initState() completed.\n'
'When an inherited widget changes, for example if the value of Theme.of() changes, '
'its dependent widgets are rebuilt. If the dependent widget\'s reference to '
'the inherited widget is in a constructor or an initState() method, '
@@ -3898,7 +3862,7 @@
}
if (state._debugLifecycleState == _StateLifecycle.defunct) {
throw new FlutterError(
- 'inheritFromWidgetOfExactType($targetType) or inheritFromElement() was called after dispose(): $this\n'
+ 'inheritFromWidgetOfExactType($targetType) called after dispose(): $this\n'
'This error happens if you call inheritFromWidgetOfExactType() on the '
'BuildContext for a widget that no longer appears in the widget tree '
'(e.g., whose parent widget no longer includes the widget in its '
@@ -3918,7 +3882,7 @@
}
return true;
}());
- return super.inheritFromElement(ancestor, aspect: aspect);
+ return super.inheritFromWidgetOfExactType(targetType);
}
@override
@@ -4068,7 +4032,7 @@
@override
InheritedWidget get widget => super.widget;
- final Map<Element, Object> _dependents = new HashMap<Element, Object>();
+ final Set<Element> _dependents = new HashSet<Element>();
@override
void _updateInheritance() {
@@ -4090,110 +4054,6 @@
super.debugDeactivated();
}
- /// Returns the dependencies value recorded for [dependent]
- /// with [setDependencies].
- ///
- /// Each dependent element is mapped to a single object value
- /// which represents how the element depends on this
- /// [InheritedElement]. This value is null by default and by default
- /// dependent elements are rebuilt unconditionally.
- ///
- /// Subclasses can manage these values with [updateDependencies]
- /// so that they can selectively rebuild dependents in
- /// [notifyDependents].
- ///
- /// This method is typically only called in overrides of [updateDependencies].
- ///
- /// See also:
- ///
- /// * [updateDependencies], which is called each time a dependency is
- /// created with [inheritFromWidgetOfExactType].
- /// * [setDependencies], which sets dependencies value for a dependent
- /// element.
- /// * [notifyDependent], which can be overridden to use a dependent's
- /// dependencies value to decide if the dependent needs to be rebuilt.
- /// * [InheritedModel], which is an example of a class that uses this method
- /// to manage dependency values.
- @protected
- Object getDependencies(Element dependent) {
- return _dependents[dependent];
- }
-
- /// Sets the value returned by [getDependencies] value for [dependent].
- ///
- /// Each dependent element is mapped to a single object value
- /// which represents how the element depends on this
- /// [InheritedElement]. The [updateDependencies] method sets this value to
- /// null by default so that dependent elements are rebuilt unconditionally.
- ///
- /// Subclasses can manage these values with [updateDependencies]
- /// so that they can selectively rebuild dependents in [notifyDependents].
- ///
- /// This method is typically only called in overrides of [updateDependencies].
- ///
- /// See also:
- ///
- /// * [updateDependencies], which is called each time a dependency is
- /// created with [inheritFromWidgetOfExactType].
- /// * [getDependencies], which returns the current value for a dependent
- /// element.
- /// * [notifyDependent], which can be overridden to use a dependent's
- /// [getDependencies] value to decide if the dependent needs to be rebuilt.
- /// * [InheritedModel], which is an example of a class that uses this method
- /// to manage dependency values.
- @protected
- void setDependencies(Element dependent, Object value) {
- _dependents[dependent] = value;
- }
-
- /// Called by [inheritFromWidgetOfExactType] when a new [dependent] is added.
- ///
- /// Each dependent element can be mapped to a single object value with
- /// [setDependencies]. This method can lookup the existing dependencies with
- /// [getDependencies].
- ///
- /// By default this method sets the inherited dependencies for [dependent]
- /// to null. This only serves to record an unconditional dependency on
- /// [dependent].
- ///
- /// Subclasses can manage their own dependencies values so that they
- /// can selectively rebuild dependents in [notifyDependents].
- ///
- /// See also:
- ///
- /// * [getDependencies], which returns the current value for a dependent
- /// element.
- /// * [setDependencies], which sets the value for a dependent element.
- /// * [notifyDependent], which can be overridden to use a dependent's
- /// dependencies value to decide if the dependent needs to be rebuilt.
- /// * [InheritedModel], which is an example of a class that uses this method
- /// to manage dependency values.
- @protected
- void updateDependencies(Element dependent, Object aspect) {
- setDependencies(dependent, null);
- }
-
- /// Called by [notifyClients] for each dependent.
- ///
- /// Calls `dependent.didChangeDependencies()` by default.
- ///
- /// Subclasses can override this method to selectively call
- /// [didChangeDependencies] based on the value of [getDependencies].
- ///
- /// See also:
- ///
- /// * [updateDependencies], which is called each time a dependency is
- /// created with [inheritFromWidgetOfExactType].
- /// * [getDependencies], which returns the current value for a dependent
- /// element.
- /// * [setDependencies], which sets the value for a dependent element.
- /// * [InheritedModel], which is an example of a class that uses this method
- /// to manage dependency values.
- @protected
- void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
- dependent.didChangeDependencies();
- }
-
/// Calls [Element.didChangeDependencies] of all dependent elements, if
/// [InheritedWidget.updateShouldNotify] returns true.
///
@@ -4210,7 +4070,7 @@
if (!widget.updateShouldNotify(oldWidget))
return;
assert(_debugCheckOwnerBuildTargetExists('notifyClients'));
- for (Element dependent in _dependents.keys) {
+ for (Element dependent in _dependents) {
assert(() {
// check that it really is our descendant
Element ancestor = dependent._parent;
@@ -4220,7 +4080,7 @@
}());
// check that it really depends on us
assert(dependent._dependencies.contains(this));
- notifyDependent(oldWidget, dependent);
+ dependent.didChangeDependencies();
}
}
}
diff --git a/packages/flutter/lib/src/widgets/inherited_model.dart b/packages/flutter/lib/src/widgets/inherited_model.dart
deleted file mode 100644
index 87dcf60..0000000
--- a/packages/flutter/lib/src/widgets/inherited_model.dart
+++ /dev/null
@@ -1,197 +0,0 @@
-// 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.
-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
- InheritedModelElement<T> createElement() => new InheritedModelElement<T>(this);
-
- /// 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) {
- setDependencies(dependent, new HashSet<T>());
- } else {
- assert(aspect is T);
- setDependencies(dependent, (dependencies ?? new HashSet<T>())..add(aspect));
- }
- }
-
- @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();
- }
-}
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index 8dc2fbc..72920b0 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -46,7 +46,6 @@
export 'src/widgets/image.dart';
export 'src/widgets/image_icon.dart';
export 'src/widgets/implicit_animations.dart';
-export 'src/widgets/inherited_model.dart';
export 'src/widgets/layout_builder.dart';
export 'src/widgets/list_wheel_scroll_view.dart';
export 'src/widgets/localizations.dart';
diff --git a/packages/flutter/test/widgets/inherited_model.dart b/packages/flutter/test/widgets/inherited_model.dart
deleted file mode 100644
index e69de29..0000000
--- a/packages/flutter/test/widgets/inherited_model.dart
+++ /dev/null
diff --git a/packages/flutter/test/widgets/inherited_model_test.dart b/packages/flutter/test/widgets/inherited_model_test.dart
deleted file mode 100644
index aec404f..0000000
--- a/packages/flutter/test/widgets/inherited_model_test.dart
+++ /dev/null
@@ -1,461 +0,0 @@
-// 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 'package:flutter_test/flutter_test.dart';
-
-import 'package:flutter/foundation.dart';
-import 'package:flutter/material.dart';
-
-// A simple "flat" InheritedModel: the data model is just 3 integer
-// valued fields: a, b, c.
-class ABCModel extends InheritedModel<String> {
- const ABCModel({
- Key key,
- this.a,
- this.b,
- this.c,
- this.aspects,
- Widget child,
- }) : super(key: key, child: child);
-
- final int a;
- final int b;
- final int c;
-
- // The aspects (fields) of this model that widgets can depend on with
- // inheritFrom.
- //
- // This property is null by default, which means that the model supports
- // all 3 fields.
- final Set<String> aspects;
-
- @override
- bool isSupportedAspect(Object aspect) {
- return aspect == null || aspects == null || aspects.contains(aspect);
- }
-
- @override
- bool updateShouldNotify(ABCModel old) {
- return !setEquals<String>(aspects, old.aspects) || a != old.a || b != old.b || c != old.c;
- }
-
- @override
- bool updateShouldNotifyDependent(ABCModel old, Set<String> dependencies) {
- return !setEquals<String>(aspects, old.aspects)
- || (a != old.a && dependencies.contains('a'))
- || (b != old.b && dependencies.contains('b'))
- || (c != old.c && dependencies.contains('c'));
- }
-
- static ABCModel of(BuildContext context, { String fieldName }) {
- return InheritedModel.inheritFrom<ABCModel>(context, aspect: fieldName);
- }
-}
-
-class ShowABCField extends StatefulWidget {
- const ShowABCField({ Key key, this.fieldName }) : super(key: key);
-
- final String fieldName;
-
- @override
- _ShowABCFieldState createState() => new _ShowABCFieldState();
-}
-
-class _ShowABCFieldState extends State<ShowABCField> {
- int _buildCount = 0;
-
- @override
- Widget build(BuildContext context) {
- final ABCModel abc = ABCModel.of(context, fieldName: widget.fieldName);
- final int value = widget.fieldName == 'a' ? abc.a : (widget.fieldName == 'b' ? abc.b : abc.c);
- return new Text('${widget.fieldName}: $value [${_buildCount++}]');
- }
-}
-
-void main() {
- testWidgets('InheritedModel basics', (WidgetTester tester) async {
- int _a = 0;
- int _b = 1;
- int _c = 2;
-
- final Widget abcPage = new StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- const Widget showA = ShowABCField(fieldName: 'a');
- const Widget showB = ShowABCField(fieldName: 'b');
- const Widget showC = ShowABCField(fieldName: 'c');
-
- // Unconditionally depends on the ABCModel: rebuilt when any
- // aspect of the model changes.
- final Widget showABC = new Builder(
- builder: (BuildContext context) {
- final ABCModel abc = ABCModel.of(context);
- return new Text('a: ${abc.a} b: ${abc.b} c: ${abc.c}');
- }
- );
-
- return new Scaffold(
- body: new StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- return new ABCModel(
- a: _a,
- b: _b,
- c: _c,
- child: new Center(
- child: new Column(
- mainAxisSize: MainAxisSize.min,
- children: <Widget>[
- showA,
- showB,
- showC,
- showABC,
- new RaisedButton(
- child: const Text('Increment a'),
- onPressed: () {
- // Rebuilds the ABCModel which triggers a rebuild
- // of showA because showA depends on the 'a' aspect
- // of the ABCModel.
- setState(() { _a += 1; });
- },
- ),
- new RaisedButton(
- child: const Text('Increment b'),
- onPressed: () {
- // Rebuilds the ABCModel which triggers a rebuild
- // of showB because showB depends on the 'b' aspect
- // of the ABCModel.
- setState(() { _b += 1; });
- },
- ),
- new RaisedButton(
- child: const Text('Increment c'),
- onPressed: () {
- // Rebuilds the ABCModel which triggers a rebuild
- // of showC because showC depends on the 'c' aspect
- // of the ABCModel.
- setState(() { _c += 1; });
- },
- ),
- ],
- ),
- ),
- );
- },
- ),
- );
- },
- );
-
- await tester.pumpWidget(new MaterialApp(home: abcPage));
-
- expect(find.text('a: 0 [0]'), findsOneWidget);
- expect(find.text('b: 1 [0]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 0 b: 1 c: 2'), findsOneWidget);
-
- await tester.tap(find.text('Increment a'));
- await tester.pumpAndSettle();
- // Verify that field 'a' was incremented, but only the showA
- // and showABC widgets were rebuilt.
- expect(find.text('a: 1 [1]'), findsOneWidget);
- expect(find.text('b: 1 [0]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 1 b: 1 c: 2'), findsOneWidget);
-
- // Verify that field 'a' was incremented, but only the showA
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment a'));
- await tester.pumpAndSettle();
- expect(find.text('a: 2 [2]'), findsOneWidget);
- expect(find.text('b: 1 [0]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 2 b: 1 c: 2'), findsOneWidget);
-
- // Verify that field 'b' was incremented, but only the showB
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment b'));
- await tester.pumpAndSettle();
- expect(find.text('a: 2 [2]'), findsOneWidget);
- expect(find.text('b: 2 [1]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 2 b: 2 c: 2'), findsOneWidget);
-
- // Verify that field 'c' was incremented, but only the showC
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment c'));
- await tester.pumpAndSettle();
- expect(find.text('a: 2 [2]'), findsOneWidget);
- expect(find.text('b: 2 [1]'), findsOneWidget);
- expect(find.text('c: 3 [1]'), findsOneWidget);
- expect(find.text('a: 2 b: 2 c: 3'), findsOneWidget);
- });
-
- testWidgets('Inner InheritedModel shadows the outer one', (WidgetTester tester) async {
- int _a = 0;
- int _b = 1;
- int _c = 2;
-
- // Same as in abcPage in the "InheritedModel basics" test except:
- // there are two ABCModels and the inner model's "a" and "b"
- // properties shadow (override) the outer model. Further complicating
- // matters: the inner model only supports the model's "a" aspect,
- // so showB and showC will depend on the outer model.
- final Widget abcPage = new StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- const Widget showA = ShowABCField(fieldName: 'a');
- const Widget showB = ShowABCField(fieldName: 'b');
- const Widget showC = ShowABCField(fieldName: 'c');
-
- // Unconditionally depends on the closest ABCModel ancestor.
- // Which is the inner model, for which b,c are null.
- final Widget showABC = new Builder(
- builder: (BuildContext context) {
- final ABCModel abc = ABCModel.of(context);
- return new Text('a: ${abc.a} b: ${abc.b} c: ${abc.c}', style: Theme.of(context).textTheme.title);
- }
- );
-
- return new Scaffold(
- body: new StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- return new ABCModel( // The "outer" model
- a: _a,
- b: _b,
- c: _c,
- child: new ABCModel( // The "inner" model
- a: 100 + _a,
- b: 100 + _b,
- aspects: new Set<String>.of(<String>['a']),
- child: new Center(
- child: new Column(
- mainAxisSize: MainAxisSize.min,
- children: <Widget>[
- showA,
- showB,
- showC,
- const SizedBox(height: 24.0),
- showABC,
- const SizedBox(height: 24.0),
- new RaisedButton(
- child: const Text('Increment a'),
- onPressed: () {
- setState(() { _a += 1; });
- },
- ),
- new RaisedButton(
- child: const Text('Increment b'),
- onPressed: () {
- setState(() { _b += 1; });
- },
- ),
- new RaisedButton(
- child: const Text('Increment c'),
- onPressed: () {
- setState(() { _c += 1; });
- },
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- );
- },
- );
-
- await tester.pumpWidget(new MaterialApp(home: abcPage));
- expect(find.text('a: 100 [0]'), findsOneWidget);
- expect(find.text('b: 1 [0]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 100 b: 101 c: null'), findsOneWidget);
-
- await tester.tap(find.text('Increment a'));
- await tester.pumpAndSettle();
- // Verify that field 'a' was incremented, but only the showA
- // and showABC widgets were rebuilt.
- expect(find.text('a: 101 [1]'), findsOneWidget);
- expect(find.text('b: 1 [0]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 101 b: 101 c: null'), findsOneWidget);
-
- await tester.tap(find.text('Increment a'));
- await tester.pumpAndSettle();
- // Verify that field 'a' was incremented, but only the showA
- // and showABC widgets were rebuilt.
- expect(find.text('a: 102 [2]'), findsOneWidget);
- expect(find.text('b: 1 [0]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 102 b: 101 c: null'), findsOneWidget);
-
- // Verify that field 'b' was incremented, but only the showB
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment b'));
- await tester.pumpAndSettle();
- expect(find.text('a: 102 [2]'), findsOneWidget);
- expect(find.text('b: 2 [1]'), findsOneWidget);
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 102 b: 102 c: null'), findsOneWidget);
-
- // Verify that field 'c' was incremented, but only the showC
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment c'));
- await tester.pumpAndSettle();
- expect(find.text('a: 102 [2]'), findsOneWidget);
- expect(find.text('b: 2 [1]'), findsOneWidget);
- expect(find.text('c: 3 [1]'), findsOneWidget);
- expect(find.text('a: 102 b: 102 c: null'), findsOneWidget);
- });
-
- testWidgets('InheritedModel inner models supported aspect change', (WidgetTester tester) async {
- int _a = 0;
- int _b = 1;
- int _c = 2;
- Set<String> _innerModelAspects = new Set<String>.of(<String>['a']);
-
- // Same as in abcPage in the "Inner InheritedModel shadows the outer one"
- // test except: the "Add b aspect" changes adds 'b' to the set of
- // aspects supported by the inner model.
- final Widget abcPage = new StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- const Widget showA = ShowABCField(fieldName: 'a');
- const Widget showB = ShowABCField(fieldName: 'b');
- const Widget showC = ShowABCField(fieldName: 'c');
-
- // Unconditionally depends on the closest ABCModel ancestor.
- // Which is the inner model, for which b,c are null.
- final Widget showABC = new Builder(
- builder: (BuildContext context) {
- final ABCModel abc = ABCModel.of(context);
- return new Text('a: ${abc.a} b: ${abc.b} c: ${abc.c}', style: Theme.of(context).textTheme.title);
- }
- );
-
- return new Scaffold(
- body: new StatefulBuilder(
- builder: (BuildContext context, StateSetter setState) {
- return new ABCModel( // The "outer" model
- a: _a,
- b: _b,
- c: _c,
- child: new ABCModel( // The "inner" model
- a: 100 + _a,
- b: 100 + _b,
- aspects: _innerModelAspects,
- child: new Center(
- child: new Column(
- mainAxisSize: MainAxisSize.min,
- children: <Widget>[
- showA,
- showB,
- showC,
- const SizedBox(height: 24.0),
- showABC,
- const SizedBox(height: 24.0),
- new RaisedButton(
- child: const Text('Increment a'),
- onPressed: () {
- setState(() { _a += 1; });
- },
- ),
- new RaisedButton(
- child: const Text('Increment b'),
- onPressed: () {
- setState(() { _b += 1; });
- },
- ),
- new RaisedButton(
- child: const Text('Increment c'),
- onPressed: () {
- setState(() { _c += 1; });
- },
- ),
- new RaisedButton(
- child: const Text('rebuild'),
- onPressed: () {
- setState(() {
- // Rebuild both models
- });
- },
- ),
- ],
- ),
- ),
- ),
- );
- },
- ),
- );
- },
- );
-
- _innerModelAspects = new Set<String>.of(<String>['a']);
- await tester.pumpWidget(new MaterialApp(home: abcPage));
- expect(find.text('a: 100 [0]'), findsOneWidget); // showA depends on the inner model
- expect(find.text('b: 1 [0]'), findsOneWidget); // showB depends on the outer model
- expect(find.text('c: 2 [0]'), findsOneWidget);
- expect(find.text('a: 100 b: 101 c: null'), findsOneWidget); // inner model's a, b, c
-
- _innerModelAspects = new Set<String>.of(<String>['a', 'b']);
- await tester.tap(find.text('rebuild'));
- await tester.pumpAndSettle();
- expect(find.text('a: 100 [1]'), findsOneWidget); // rebuilt showA still depend on the inner model
- expect(find.text('b: 101 [1]'), findsOneWidget); // rebuilt showB now depends on the inner model
- expect(find.text('c: 2 [1]'), findsOneWidget); // rebuilt showC still depends on the outer model
- expect(find.text('a: 100 b: 101 c: null'), findsOneWidget); // inner model's a, b, c
-
- // Verify that field 'a' was incremented, but only the showA
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment a'));
- await tester.pumpAndSettle();
- expect(find.text('a: 101 [2]'), findsOneWidget); // rebuilt showA still depends on the inner model
- expect(find.text('b: 101 [1]'), findsOneWidget);
- expect(find.text('c: 2 [1]'), findsOneWidget);
- expect(find.text('a: 101 b: 101 c: null'), findsOneWidget);
-
- // Verify that field 'b' was incremented, but only the showB
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment b'));
- await tester.pumpAndSettle();
- expect(find.text('a: 101 [2]'), findsOneWidget); // rebuilt showB still depends on the inner model
- expect(find.text('b: 102 [2]'), findsOneWidget);
- expect(find.text('c: 2 [1]'), findsOneWidget);
- expect(find.text('a: 101 b: 102 c: null'), findsOneWidget);
-
- // Verify that field 'c' was incremented, but only the showC
- // and showABC widgets were rebuilt.
- await tester.tap(find.text('Increment c'));
- await tester.pumpAndSettle();
- expect(find.text('a: 101 [2]'), findsOneWidget);
- expect(find.text('b: 102 [2]'), findsOneWidget);
- expect(find.text('c: 3 [2]'), findsOneWidget); // rebuilt showC still depends on the outer model
- expect(find.text('a: 101 b: 102 c: null'), findsOneWidget);
-
- _innerModelAspects = new Set<String>.of(<String>['a', 'b', 'c']);
- await tester.tap(find.text('rebuild'));
- await tester.pumpAndSettle();
- expect(find.text('a: 101 [3]'), findsOneWidget); // rebuilt showA still depend on the inner model
- expect(find.text('b: 102 [3]'), findsOneWidget); // rebuilt showB still depends on the inner model
- expect(find.text('c: null [3]'), findsOneWidget); // rebuilt showC now depends on the inner model
- expect(find.text('a: 101 b: 102 c: null'), findsOneWidget); // inner model's a, b, c
-
- // Now the inner model supports no aspects
- _innerModelAspects = new Set<String>.of(<String>[]);
- await tester.tap(find.text('rebuild'));
- await tester.pumpAndSettle();
- expect(find.text('a: 1 [4]'), findsOneWidget); // rebuilt showA now depends on the outer model
- expect(find.text('b: 2 [4]'), findsOneWidget); // rebuilt showB now depends on the outer model
- expect(find.text('c: 3 [4]'), findsOneWidget); // rebuilt showC now depends on the outer model
- expect(find.text('a: 101 b: 102 c: null'), findsOneWidget); // inner model's a, b, c
-
- // Now the inner model supports all aspects
- _innerModelAspects = null;
- await tester.tap(find.text('rebuild'));
- await tester.pumpAndSettle();
- expect(find.text('a: 101 [5]'), findsOneWidget); // rebuilt showA now depends on the inner model
- expect(find.text('b: 102 [5]'), findsOneWidget); // rebuilt showB now depends on the inner model
- expect(find.text('c: null [5]'), findsOneWidget); // rebuilt showC now depends on the inner model
- expect(find.text('a: 101 b: 102 c: null'), findsOneWidget); // inner model's a, b, c
- });
-}