Modified Flutter framework.dart to print widget counts (generic version)
// 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. | |
// @dart = 2.8 | |
import 'dart:async'; | |
import 'dart:collection'; | |
import 'dart:developer'; | |
import 'package:flutter/foundation.dart'; | |
import 'package:flutter/rendering.dart'; | |
import 'debug.dart'; | |
import 'focus_manager.dart'; | |
import 'inherited_model.dart'; | |
export 'dart:ui' show hashValues, hashList; | |
export 'package:flutter/foundation.dart' show | |
factory, | |
immutable, | |
mustCallSuper, | |
optionalTypeArgs, | |
protected, | |
required, | |
visibleForTesting; | |
export 'package:flutter/foundation.dart' show FlutterError, ErrorSummary, ErrorDescription, ErrorHint, debugPrint, debugPrintStack; | |
export 'package:flutter/foundation.dart' show VoidCallback, ValueChanged, ValueGetter, ValueSetter; | |
export 'package:flutter/foundation.dart' show DiagnosticsNode, DiagnosticLevel; | |
export 'package:flutter/foundation.dart' show Key, LocalKey, ValueKey; | |
export 'package:flutter/rendering.dart' show RenderObject, RenderBox, debugDumpRenderTree, debugDumpLayerTree; | |
bool _outputScheduled = false; | |
Map<String, int> _outputMap = <String, int>{}; | |
void _output(Widget widget) { | |
final String typeName = widget.toStringShort(); | |
if (_outputMap.containsKey(typeName)) { | |
_outputMap[typeName] = _outputMap[typeName] + 1; | |
} else { | |
_outputMap[typeName] = 1; | |
} | |
if (_outputScheduled) { | |
return; | |
} | |
_outputScheduled = true; | |
Timer(const Duration(seconds: 3), () { | |
_outputMap.forEach((String key, int value) { | |
switch (widget.runtimeType.toString()) { | |
case 'Focus': | |
case 'FocusTraversalGroup': | |
case 'AutomaticKeepAlive': | |
case 'Material': | |
case 'Scrollable': | |
case 'Tooltip': | |
case 'InkWell': | |
case 'RawGestureDetector': | |
case 'FocusScope': | |
case 'Image': | |
case 'AnimatedBuilder': | |
break; | |
default: | |
print('$key $value'); | |
} | |
}); | |
}); | |
} | |
// Examples can assume: | |
// BuildContext context; | |
// void setState(VoidCallback fn) { } | |
// Examples can assume: | |
// abstract class RenderFrogJar extends RenderObject { } | |
// abstract class FrogJar extends RenderObjectWidget { } | |
// abstract class FrogJarParentData extends ParentData { Size size; } | |
// KEYS | |
/// A key that is only equal to itself. | |
/// | |
/// This cannot be created with a const constructor because that implies that | |
/// all instantiated keys would be the same instance and therefore not be unique. | |
class UniqueKey extends LocalKey { | |
/// Creates a key that is equal only to itself. | |
/// | |
/// The key cannot be created with a const constructor because that implies | |
/// that all instantiated keys would be the same instance and therefore not | |
/// be unique. | |
// ignore: prefer_const_constructors_in_immutables , never use const for this class | |
UniqueKey(); | |
@override | |
String toString() => '[#${shortHash(this)}]'; | |
} | |
/// A key that takes its identity from the object used as its value. | |
/// | |
/// Used to tie the identity of a widget to the identity of an object used to | |
/// generate that widget. | |
/// | |
/// See also: | |
/// | |
/// * [Key], the base class for all keys. | |
/// * The discussion at [Widget.key] for more information about how widgets use | |
/// keys. | |
class ObjectKey extends LocalKey { | |
/// Creates a key that uses [identical] on [value] for its [operator==]. | |
const ObjectKey(this.value); | |
/// The object whose identity is used by this key's [operator==]. | |
final Object value; | |
@override | |
bool operator ==(Object other) { | |
if (other.runtimeType != runtimeType) | |
return false; | |
return other is ObjectKey | |
&& identical(other.value, value); | |
} | |
@override | |
int get hashCode => hashValues(runtimeType, identityHashCode(value)); | |
@override | |
String toString() { | |
if (runtimeType == ObjectKey) | |
return '[${describeIdentity(value)}]'; | |
return '[${objectRuntimeType(this, 'ObjectKey')} ${describeIdentity(value)}]'; | |
} | |
} | |
/// A key that is unique across the entire app. | |
/// | |
/// Global keys uniquely identify elements. Global keys provide access to other | |
/// objects that are associated with those elements, such as [BuildContext]. | |
/// For [StatefulWidget]s, global keys also provide access to [State]. | |
/// | |
/// Widgets that have global keys reparent their subtrees when they are moved | |
/// from one location in the tree to another location in the tree. In order to | |
/// reparent its subtree, a widget must arrive at its new location in the tree | |
/// in the same animation frame in which it was removed from its old location in | |
/// the tree. | |
/// | |
/// Global keys are relatively expensive. If you don't need any of the features | |
/// listed above, consider using a [Key], [ValueKey], [ObjectKey], or | |
/// [UniqueKey] instead. | |
/// | |
/// You cannot simultaneously include two widgets in the tree with the same | |
/// global key. Attempting to do so will assert at runtime. | |
/// | |
/// See also: | |
/// | |
/// * The discussion at [Widget.key] for more information about how widgets use | |
/// keys. | |
@optionalTypeArgs | |
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key { | |
/// Creates a [LabeledGlobalKey], which is a [GlobalKey] with a label used for | |
/// debugging. | |
/// | |
/// The label is purely for debugging and not used for comparing the identity | |
/// of the key. | |
factory GlobalKey({ String debugLabel }) => LabeledGlobalKey<T>(debugLabel); | |
/// Creates a global key without a label. | |
/// | |
/// Used by subclasses because the factory constructor shadows the implicit | |
/// constructor. | |
const GlobalKey.constructor() : super.empty(); | |
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{}; | |
static final Set<Element> _debugIllFatedElements = HashSet<Element>(); | |
// This map keeps track which child reserves the global key with the parent. | |
// Parent, child -> global key. | |
// This provides us a way to remove old reservation while parent rebuilds the | |
// child in the same slot. | |
static final Map<Element, Map<Element, GlobalKey>> _debugReservations = <Element, Map<Element, GlobalKey>>{}; | |
static void _debugRemoveReservationFor(Element parent, Element child) { | |
assert(() { | |
assert(parent != null); | |
assert(child != null); | |
_debugReservations[parent]?.remove(child); | |
return true; | |
}()); | |
} | |
void _register(Element element) { | |
assert(() { | |
if (_registry.containsKey(this)) { | |
assert(element.widget != null); | |
assert(_registry[this].widget != null); | |
assert(element.widget.runtimeType != _registry[this].widget.runtimeType); | |
_debugIllFatedElements.add(_registry[this]); | |
} | |
return true; | |
}()); | |
_registry[this] = element; | |
} | |
void _unregister(Element element) { | |
assert(() { | |
if (_registry.containsKey(this) && _registry[this] != element) { | |
assert(element.widget != null); | |
assert(_registry[this].widget != null); | |
assert(element.widget.runtimeType != _registry[this].widget.runtimeType); | |
} | |
return true; | |
}()); | |
if (_registry[this] == element) | |
_registry.remove(this); | |
} | |
void _debugReserveFor(Element parent, Element child) { | |
assert(() { | |
assert(parent != null); | |
assert(child != null); | |
_debugReservations[parent] ??= <Element, GlobalKey>{}; | |
_debugReservations[parent][child] = this; | |
return true; | |
}()); | |
} | |
static void _debugVerifyGlobalKeyReservation() { | |
assert(() { | |
final Map<GlobalKey, Element> keyToParent = <GlobalKey, Element>{}; | |
_debugReservations.forEach((Element parent, Map<Element, GlobalKey> childToKey) { | |
// We ignore parent that are detached. | |
if (parent.renderObject?.attached == false) | |
return; | |
childToKey.forEach((Element child, GlobalKey key) { | |
// If parent = null, the node is deactivated by its parent and is | |
// not re-attached to other part of the tree. We should ignore this | |
// node. | |
if (child._parent == null) | |
return; | |
// It is possible the same key registers to the same parent twice | |
// with different children. That is illegal, but it is not in the | |
// scope of this check. Such error will be detected in | |
// _debugVerifyIllFatedPopulation or | |
// _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans. | |
if (keyToParent.containsKey(key) && keyToParent[key] != parent) { | |
// We have duplication reservations for the same global key. | |
final Element older = keyToParent[key]; | |
final Element newer = parent; | |
FlutterError error; | |
if (older.toString() != newer.toString()) { | |
error = FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('Multiple widgets used the same GlobalKey.'), | |
ErrorDescription( | |
'The key $key was used by multiple widgets. The parents of those widgets were:\n' | |
'- ${older.toString()}\n' | |
'- ${newer.toString()}\n' | |
'A GlobalKey can only be specified on one widget at a time in the widget tree.' | |
), | |
]); | |
} else { | |
error = FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('Multiple widgets used the same GlobalKey.'), | |
ErrorDescription( | |
'The key $key was used by multiple widgets. The parents of those widgets were ' | |
'different widgets that both had the following description:\n' | |
' ${parent.toString()}\n' | |
'A GlobalKey can only be specified on one widget at a time in the widget tree.' | |
), | |
]); | |
} | |
// Fix the tree by removing the duplicated child from one of its | |
// parents to resolve the duplicated key issue. This allows us to | |
// tear down the tree during testing without producing additional | |
// misleading exceptions. | |
if (child._parent != older) { | |
older.visitChildren((Element currentChild) { | |
if (currentChild == child) | |
older.forgetChild(child); | |
}); | |
} | |
if (child._parent != newer) { | |
newer.visitChildren((Element currentChild) { | |
if (currentChild == child) | |
newer.forgetChild(child); | |
}); | |
} | |
throw error; | |
} else { | |
keyToParent[key] = parent; | |
} | |
}); | |
}); | |
_debugReservations.clear(); | |
return true; | |
}()); | |
} | |
static void _debugVerifyIllFatedPopulation() { | |
assert(() { | |
Map<GlobalKey, Set<Element>> duplicates; | |
for (final Element element in _debugIllFatedElements) { | |
if (element._debugLifecycleState != _ElementLifecycle.defunct) { | |
assert(element != null); | |
assert(element.widget != null); | |
assert(element.widget.key != null); | |
final GlobalKey key = element.widget.key as GlobalKey; | |
assert(_registry.containsKey(key)); | |
duplicates ??= <GlobalKey, Set<Element>>{}; | |
// Uses ordered set to produce consistent error message. | |
final Set<Element> elements = duplicates.putIfAbsent(key, () => LinkedHashSet<Element>()); | |
elements.add(element); | |
elements.add(_registry[key]); | |
} | |
} | |
_debugIllFatedElements.clear(); | |
if (duplicates != null) { | |
final List<DiagnosticsNode> information = <DiagnosticsNode>[]; | |
information.add(ErrorSummary('Multiple widgets used the same GlobalKey.')); | |
for (final GlobalKey key in duplicates.keys) { | |
final Set<Element> elements = duplicates[key]; | |
// TODO(jacobr): this will omit the '- ' before each widget name and | |
// use the more standard whitespace style instead. Please let me know | |
// if the '- ' style is a feature we want to maintain and we can add | |
// another tree style that supports it. I also see '* ' in some places | |
// so it would be nice to unify and normalize. | |
information.add(Element.describeElements('The key $key was used by ${elements.length} widgets', elements)); | |
} | |
information.add(ErrorDescription('A GlobalKey can only be specified on one widget at a time in the widget tree.')); | |
throw FlutterError.fromParts(information); | |
} | |
return true; | |
}()); | |
} | |
Element get _currentElement => _registry[this]; | |
/// The build context in which the widget with this key builds. | |
/// | |
/// The current context is null if there is no widget in the tree that matches | |
/// this global key. | |
BuildContext get currentContext => _currentElement; | |
/// The widget in the tree that currently has this global key. | |
/// | |
/// The current widget is null if there is no widget in the tree that matches | |
/// this global key. | |
Widget get currentWidget => _currentElement?.widget; | |
/// The [State] for the widget in the tree that currently has this global key. | |
/// | |
/// The current state is null if (1) there is no widget in the tree that | |
/// matches this global key, (2) that widget is not a [StatefulWidget], or the | |
/// associated [State] object is not a subtype of `T`. | |
T get currentState { | |
final Element element = _currentElement; | |
if (element is StatefulElement) { | |
final StatefulElement statefulElement = element; | |
final State state = statefulElement.state; | |
if (state is T) | |
return state; | |
} | |
return null; | |
} | |
} | |
/// A global key with a debugging label. | |
/// | |
/// The debug label is useful for documentation and for debugging. The label | |
/// does not affect the key's identity. | |
@optionalTypeArgs | |
class LabeledGlobalKey<T extends State<StatefulWidget>> extends GlobalKey<T> { | |
/// Creates a global key with a debugging label. | |
/// | |
/// The label does not affect the key's identity. | |
// ignore: prefer_const_constructors_in_immutables , never use const for this class | |
LabeledGlobalKey(this._debugLabel) : super.constructor(); | |
final String _debugLabel; | |
@override | |
String toString() { | |
final String label = _debugLabel != null ? ' $_debugLabel' : ''; | |
if (runtimeType == LabeledGlobalKey) | |
return '[GlobalKey#${shortHash(this)}$label]'; | |
return '[${describeIdentity(this)}$label]'; | |
} | |
} | |
/// A global key that takes its identity from the object used as its value. | |
/// | |
/// Used to tie the identity of a widget to the identity of an object used to | |
/// generate that widget. | |
/// | |
/// If the object is not private, then it is possible that collisions will occur | |
/// where independent widgets will reuse the same object as their | |
/// [GlobalObjectKey] value in a different part of the tree, leading to a global | |
/// key conflict. To avoid this problem, create a private [GlobalObjectKey] | |
/// subclass, as in: | |
/// | |
/// ```dart | |
/// class _MyKey extends GlobalObjectKey { | |
/// const _MyKey(Object value) : super(value); | |
/// } | |
/// ``` | |
/// | |
/// Since the [runtimeType] of the key is part of its identity, this will | |
/// prevent clashes with other [GlobalObjectKey]s even if they have the same | |
/// value. | |
/// | |
/// Any [GlobalObjectKey] created for the same value will match. | |
@optionalTypeArgs | |
class GlobalObjectKey<T extends State<StatefulWidget>> extends GlobalKey<T> { | |
/// Creates a global key that uses [identical] on [value] for its [operator==]. | |
const GlobalObjectKey(this.value) : super.constructor(); | |
/// The object whose identity is used by this key's [operator==]. | |
final Object value; | |
@override | |
bool operator ==(Object other) { | |
if (other.runtimeType != runtimeType) | |
return false; | |
return other is GlobalObjectKey<T> | |
&& identical(other.value, value); | |
} | |
@override | |
int get hashCode => identityHashCode(value); | |
@override | |
String toString() { | |
String selfType = objectRuntimeType(this, 'GlobalObjectKey'); | |
// The runtimeType string of a GlobalObjectKey() returns 'GlobalObjectKey<State<StatefulWidget>>' | |
// because GlobalObjectKey is instantiated to its bounds. To avoid cluttering the output | |
// we remove the suffix. | |
const String suffix = '<State<StatefulWidget>>'; | |
if (selfType.endsWith(suffix)) { | |
selfType = selfType.substring(0, selfType.length - suffix.length); | |
} | |
return '[$selfType ${describeIdentity(value)}]'; | |
} | |
} | |
/// This class is a work-around for the "is" operator not accepting a variable value as its right operand. | |
/// | |
/// This class is deprecated. It will be deleted soon. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'TypeMatcher has been deprecated because it is no longer used in framework(only in deprecated methods). ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
@optionalTypeArgs | |
class TypeMatcher<T> { | |
/// Creates a type matcher for the given type parameter. | |
const TypeMatcher(); | |
/// Returns true if the given object is of type `T`. | |
bool check(dynamic object) => object is T; | |
} | |
/// Describes the configuration for an [Element]. | |
/// | |
/// Widgets are the central class hierarchy in the Flutter framework. A widget | |
/// is an immutable description of part of a user interface. Widgets can be | |
/// inflated into elements, which manage the underlying render tree. | |
/// | |
/// Widgets themselves have no mutable state (all their fields must be final). | |
/// If you wish to associate mutable state with a widget, consider using a | |
/// [StatefulWidget], which creates a [State] object (via | |
/// [StatefulWidget.createState]) whenever it is inflated into an element and | |
/// incorporated into the tree. | |
/// | |
/// A given widget can be included in the tree zero or more times. In particular | |
/// a given widget can be placed in the tree multiple times. Each time a widget | |
/// is placed in the tree, it is inflated into an [Element], which means a | |
/// widget that is incorporated into the tree multiple times will be inflated | |
/// multiple times. | |
/// | |
/// The [key] property controls how one widget replaces another widget in the | |
/// tree. If the [runtimeType] and [key] properties of the two widgets are | |
/// [operator==], respectively, then the new widget replaces the old widget by | |
/// updating the underlying element (i.e., by calling [Element.update] with the | |
/// new widget). Otherwise, the old element is removed from the tree, the new | |
/// widget is inflated into an element, and the new element is inserted into the | |
/// tree. | |
/// | |
/// See also: | |
/// | |
/// * [StatefulWidget] and [State], for widgets that can build differently | |
/// several times over their lifetime. | |
/// * [InheritedWidget], for widgets that introduce ambient state that can | |
/// be read by descendant widgets. | |
/// * [StatelessWidget], for widgets that always build the same way given a | |
/// particular configuration and ambient state. | |
@immutable | |
abstract class Widget extends DiagnosticableTree { | |
/// Initializes [key] for subclasses. | |
const Widget({ this.key }); | |
/// Controls how one widget replaces another widget in the tree. | |
/// | |
/// If the [runtimeType] and [key] properties of the two widgets are | |
/// [operator==], respectively, then the new widget replaces the old widget by | |
/// updating the underlying element (i.e., by calling [Element.update] with the | |
/// new widget). Otherwise, the old element is removed from the tree, the new | |
/// widget is inflated into an element, and the new element is inserted into the | |
/// tree. | |
/// | |
/// In addition, using a [GlobalKey] as the widget's [key] allows the element | |
/// to be moved around the tree (changing parent) without losing state. When a | |
/// new widget is found (its key and type do not match a previous widget in | |
/// the same location), but there was a widget with that same global key | |
/// elsewhere in the tree in the previous frame, then that widget's element is | |
/// moved to the new location. | |
/// | |
/// Generally, a widget that is the only child of another widget does not need | |
/// an explicit key. | |
/// | |
/// See also: | |
/// | |
/// * The discussions at [Key] and [GlobalKey]. | |
final Key key; | |
/// Inflates this configuration to a concrete instance. | |
/// | |
/// A given widget can be included in the tree zero or more times. In particular | |
/// a given widget can be placed in the tree multiple times. Each time a widget | |
/// is placed in the tree, it is inflated into an [Element], which means a | |
/// widget that is incorporated into the tree multiple times will be inflated | |
/// multiple times. | |
@protected | |
@factory | |
Element createElement(); | |
/// A short, textual description of this widget. | |
@override | |
String toStringShort() { | |
final String type = objectRuntimeType(this, 'Widget'); | |
return key == null ? type : '$type-$key'; | |
} | |
@override | |
void debugFillProperties(DiagnosticPropertiesBuilder properties) { | |
super.debugFillProperties(properties); | |
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.dense; | |
} | |
@override | |
@nonVirtual | |
bool operator ==(Object other) => super == other; | |
@override | |
@nonVirtual | |
int get hashCode => super.hashCode; | |
/// Whether the `newWidget` can be used to update an [Element] that currently | |
/// has the `oldWidget` as its configuration. | |
/// | |
/// An element that uses a given widget as its configuration can be updated to | |
/// use another widget as its configuration if, and only if, the two widgets | |
/// have [runtimeType] and [key] properties that are [operator==]. | |
/// | |
/// If the widgets have no key (their key is null), then they are considered a | |
/// match if they have the same type, even if their children are completely | |
/// different. | |
static bool canUpdate(Widget oldWidget, Widget newWidget) { | |
return oldWidget.runtimeType == newWidget.runtimeType | |
&& oldWidget.key == newWidget.key; | |
} | |
// Return a numeric encoding of the specific `Widget` concrete subtype. | |
// This is used in `Element.updateChild` to determine if a hot reload modified the | |
// superclass of a mounted element's configuration. The encoding of each `Widget` | |
// must match the corresponding `Element` encoding in `Element._debugConcreteSubtype`. | |
static int _debugConcreteSubtype(Widget widget) { | |
return widget is StatefulWidget ? 1 : | |
widget is StatelessWidget ? 2 : | |
0; | |
} | |
} | |
/// A widget that does not require mutable state. | |
/// | |
/// A stateless widget is a widget that describes part of the user interface by | |
/// building a constellation of other widgets that describe the user interface | |
/// more concretely. The building process continues recursively until the | |
/// description of the user interface is fully concrete (e.g., consists | |
/// entirely of [RenderObjectWidget]s, which describe concrete [RenderObject]s). | |
/// | |
/// {@youtube 560 315 https://www.youtube.com/watch?v=wE7khGHVkYY} | |
/// | |
/// Stateless widget are useful when the part of the user interface you are | |
/// describing does not depend on anything other than the configuration | |
/// information in the object itself and the [BuildContext] in which the widget | |
/// is inflated. For compositions that can change dynamically, e.g. due to | |
/// having an internal clock-driven state, or depending on some system state, | |
/// consider using [StatefulWidget]. | |
/// | |
/// ## Performance considerations | |
/// | |
/// The [build] method of a stateless widget is typically only called in three | |
/// situations: the first time the widget is inserted in the tree, when the | |
/// widget's parent changes its configuration, and when an [InheritedWidget] it | |
/// depends on changes. | |
/// | |
/// If a widget's parent will regularly change the widget's configuration, or if | |
/// it depends on inherited widgets that frequently change, then it is important | |
/// to optimize the performance of the [build] method to maintain a fluid | |
/// rendering performance. | |
/// | |
/// There are several techniques one can use to minimize the impact of | |
/// rebuilding a stateless widget: | |
/// | |
/// * Minimize the number of nodes transitively created by the build method and | |
/// any widgets it creates. For example, instead of an elaborate arrangement | |
/// of [Row]s, [Column]s, [Padding]s, and [SizedBox]es to position a single | |
/// child in a particularly fancy manner, consider using just an [Align] or a | |
/// [CustomSingleChildLayout]. Instead of an intricate layering of multiple | |
/// [Container]s and with [Decoration]s to draw just the right graphical | |
/// effect, consider a single [CustomPaint] widget. | |
/// | |
/// * Use `const` widgets where possible, and provide a `const` constructor for | |
/// the widget so that users of the widget can also do so. | |
/// | |
/// * Consider refactoring the stateless widget into a stateful widget so that | |
/// it can use some of the techniques described at [StatefulWidget], such as | |
/// caching common parts of subtrees and using [GlobalKey]s when changing the | |
/// tree structure. | |
/// | |
/// * If the widget is likely to get rebuilt frequently due to the use of | |
/// [InheritedWidget]s, consider refactoring the stateless widget into | |
/// multiple widgets, with the parts of the tree that change being pushed to | |
/// the leaves. For example instead of building a tree with four widgets, the | |
/// inner-most widget depending on the [Theme], consider factoring out the | |
/// part of the build function that builds the inner-most widget into its own | |
/// widget, so that only the inner-most widget needs to be rebuilt when the | |
/// theme changes. | |
/// | |
/// {@tool snippet} | |
/// | |
/// The following is a skeleton of a stateless widget subclass called `GreenFrog`. | |
/// | |
/// Normally, widgets have more constructor arguments, each of which corresponds | |
/// to a `final` property. | |
/// | |
/// ```dart | |
/// class GreenFrog extends StatelessWidget { | |
/// const GreenFrog({ Key key }) : super(key: key); | |
/// | |
/// @override | |
/// Widget build(BuildContext context) { | |
/// return Container(color: const Color(0xFF2DBD3A)); | |
/// } | |
/// } | |
/// ``` | |
/// {@end-tool} | |
/// | |
/// {@tool snippet} | |
/// | |
/// This next example shows the more generic widget `Frog` which can be given | |
/// a color and a child: | |
/// | |
/// ```dart | |
/// class Frog extends StatelessWidget { | |
/// const Frog({ | |
/// Key key, | |
/// this.color = const Color(0xFF2DBD3A), | |
/// this.child, | |
/// }) : super(key: key); | |
/// | |
/// final Color color; | |
/// final Widget child; | |
/// | |
/// @override | |
/// Widget build(BuildContext context) { | |
/// return Container(color: color, child: child); | |
/// } | |
/// } | |
/// ``` | |
/// {@end-tool} | |
/// | |
/// By convention, widget constructors only use named arguments. Named arguments | |
/// can be marked as required using [@required]. Also by convention, the first | |
/// argument is [key], and the last argument is `child`, `children`, or the | |
/// equivalent. | |
/// | |
/// See also: | |
/// | |
/// * [StatefulWidget] and [State], for widgets that can build differently | |
/// several times over their lifetime. | |
/// * [InheritedWidget], for widgets that introduce ambient state that can | |
/// be read by descendant widgets. | |
abstract class StatelessWidget extends Widget { | |
/// Initializes [key] for subclasses. | |
const StatelessWidget({ Key key }) : super(key: key); | |
/// Creates a [StatelessElement] to manage this widget's location in the tree. | |
/// | |
/// It is uncommon for subclasses to override this method. | |
@override | |
StatelessElement createElement() => StatelessElement(this); | |
/// Describes the part of the user interface represented by this widget. | |
/// | |
/// The framework calls this method when this widget is inserted into the tree | |
/// in a given [BuildContext] and when the dependencies of this widget change | |
/// (e.g., an [InheritedWidget] referenced by this widget changes). This | |
/// method can potentially be called in every frame and should not have any side | |
/// effects beyond building a widget. | |
/// | |
/// The framework replaces the subtree below this widget with the widget | |
/// returned by this method, either by updating the existing subtree or by | |
/// removing the subtree and inflating a new subtree, depending on whether the | |
/// widget returned by this method can update the root of the existing | |
/// subtree, as determined by calling [Widget.canUpdate]. | |
/// | |
/// Typically implementations return a newly created constellation of widgets | |
/// that are configured with information from this widget's constructor and | |
/// from the given [BuildContext]. | |
/// | |
/// The given [BuildContext] contains information about the location in the | |
/// tree at which this widget is being built. For example, the context | |
/// provides the set of inherited widgets for this location in the tree. A | |
/// given widget might be built with multiple different [BuildContext] | |
/// arguments over time if the widget is moved around the tree or if the | |
/// widget is inserted into the tree in multiple places at once. | |
/// | |
/// The implementation of this method must only depend on: | |
/// | |
/// * the fields of the widget, which themselves must not change over time, | |
/// and | |
/// * any ambient state obtained from the `context` using | |
/// [BuildContext.dependOnInheritedWidgetOfExactType]. | |
/// | |
/// If a widget's [build] method is to depend on anything else, use a | |
/// [StatefulWidget] instead. | |
/// | |
/// See also: | |
/// | |
/// * [StatelessWidget], which contains the discussion on performance considerations. | |
@protected | |
Widget build(BuildContext context); | |
} | |
/// A widget that has mutable state. | |
/// | |
/// State is information that (1) can be read synchronously when the widget is | |
/// built and (2) might change during the lifetime of the widget. It is the | |
/// responsibility of the widget implementer to ensure that the [State] is | |
/// promptly notified when such state changes, using [State.setState]. | |
/// | |
/// A stateful widget is a widget that describes part of the user interface by | |
/// building a constellation of other widgets that describe the user interface | |
/// more concretely. The building process continues recursively until the | |
/// description of the user interface is fully concrete (e.g., consists | |
/// entirely of [RenderObjectWidget]s, which describe concrete [RenderObject]s). | |
/// | |
/// Stateful widgets are useful when the part of the user interface you are | |
/// describing can change dynamically, e.g. due to having an internal | |
/// clock-driven state, or depending on some system state. For compositions that | |
/// depend only on the configuration information in the object itself and the | |
/// [BuildContext] in which the widget is inflated, consider using | |
/// [StatelessWidget]. | |
/// | |
/// {@youtube 560 315 https://www.youtube.com/watch?v=AqCMFXEmf3w} | |
/// | |
/// [StatefulWidget] instances themselves are immutable and store their mutable | |
/// state either in separate [State] objects that are created by the | |
/// [createState] method, or in objects to which that [State] subscribes, for | |
/// example [Stream] or [ChangeNotifier] objects, to which references are stored | |
/// in final fields on the [StatefulWidget] itself. | |
/// | |
/// The framework calls [createState] whenever it inflates a | |
/// [StatefulWidget], which means that multiple [State] objects might be | |
/// associated with the same [StatefulWidget] if that widget has been inserted | |
/// into the tree in multiple places. Similarly, if a [StatefulWidget] is | |
/// removed from the tree and later inserted in to the tree again, the framework | |
/// will call [createState] again to create a fresh [State] object, simplifying | |
/// the lifecycle of [State] objects. | |
/// | |
/// A [StatefulWidget] keeps the same [State] object when moving from one | |
/// location in the tree to another if its creator used a [GlobalKey] for its | |
/// [key]. Because a widget with a [GlobalKey] can be used in at most one | |
/// location in the tree, a widget that uses a [GlobalKey] has at most one | |
/// associated element. The framework takes advantage of this property when | |
/// moving a widget with a global key from one location in the tree to another | |
/// by grafting the (unique) subtree associated with that widget from the old | |
/// location to the new location (instead of recreating the subtree at the new | |
/// location). The [State] objects associated with [StatefulWidget] are grafted | |
/// along with the rest of the subtree, which means the [State] object is reused | |
/// (instead of being recreated) in the new location. However, in order to be | |
/// eligible for grafting, the widget must be inserted into the new location in | |
/// the same animation frame in which it was removed from the old location. | |
/// | |
/// ## Performance considerations | |
/// | |
/// There are two primary categories of [StatefulWidget]s. | |
/// | |
/// The first is one which allocates resources in [State.initState] and disposes | |
/// of them in [State.dispose], but which does not depend on [InheritedWidget]s | |
/// or call [State.setState]. Such widgets are commonly used at the root of an | |
/// application or page, and communicate with subwidgets via [ChangeNotifier]s, | |
/// [Stream]s, or other such objects. Stateful widgets following such a pattern | |
/// are relatively cheap (in terms of CPU and GPU cycles), because they are | |
/// built once then never update. They can, therefore, have somewhat complicated | |
/// and deep build methods. | |
/// | |
/// The second category is widgets that use [State.setState] or depend on | |
/// [InheritedWidget]s. These will typically rebuild many times during the | |
/// application's lifetime, and it is therefore important to minimize the impact | |
/// of rebuilding such a widget. (They may also use [State.initState] or | |
/// [State.didChangeDependencies] and allocate resources, but the important part | |
/// is that they rebuild.) | |
/// | |
/// There are several techniques one can use to minimize the impact of | |
/// rebuilding a stateful widget: | |
/// | |
/// * Push the state to the leaves. For example, if your page has a ticking | |
/// clock, rather than putting the state at the top of the page and | |
/// rebuilding the entire page each time the clock ticks, create a dedicated | |
/// clock widget that only updates itself. | |
/// | |
/// * Minimize the number of nodes transitively created by the build method and | |
/// any widgets it creates. Ideally, a stateful widget would only create a | |
/// single widget, and that widget would be a [RenderObjectWidget]. | |
/// (Obviously this isn't always practical, but the closer a widget gets to | |
/// this ideal, the more efficient it will be.) | |
/// | |
/// * If a subtree does not change, cache the widget that represents that | |
/// subtree and re-use it each time it can be used. It is massively more | |
/// efficient for a widget to be re-used than for a new (but | |
/// identically-configured) widget to be created. Factoring out the stateful | |
/// part into a widget that takes a child argument is a common way of doing | |
/// this. | |
/// | |
/// * Use `const` widgets where possible. (This is equivalent to caching a | |
/// widget and re-using it.) | |
/// | |
/// * Avoid changing the depth of any created subtrees or changing the type of | |
/// any widgets in the subtree. For example, rather than returning either the | |
/// child or the child wrapped in an [IgnorePointer], always wrap the child | |
/// widget in an [IgnorePointer] and control the [IgnorePointer.ignoring] | |
/// property. This is because changing the depth of the subtree requires | |
/// rebuilding, laying out, and painting the entire subtree, whereas just | |
/// changing the property will require the least possible change to the | |
/// render tree (in the case of [IgnorePointer], for example, no layout or | |
/// repaint is necessary at all). | |
/// | |
/// * If the depth must be changed for some reason, consider wrapping the | |
/// common parts of the subtrees in widgets that have a [GlobalKey] that | |
/// remains consistent for the life of the stateful widget. (The | |
/// [KeyedSubtree] widget may be useful for this purpose if no other widget | |
/// can conveniently be assigned the key.) | |
/// | |
/// {@tool snippet} | |
/// | |
/// This is a skeleton of a stateful widget subclass called `YellowBird`. | |
/// | |
/// In this example. the [State] has no actual state. State is normally | |
/// represented as private member fields. Also, normally widgets have more | |
/// constructor arguments, each of which corresponds to a `final` property. | |
/// | |
/// ```dart | |
/// class YellowBird extends StatefulWidget { | |
/// const YellowBird({ Key key }) : super(key: key); | |
/// | |
/// @override | |
/// _YellowBirdState createState() => _YellowBirdState(); | |
/// } | |
/// | |
/// class _YellowBirdState extends State<YellowBird> { | |
/// @override | |
/// Widget build(BuildContext context) { | |
/// return Container(color: const Color(0xFFFFE306)); | |
/// } | |
/// } | |
/// ``` | |
/// {@end-tool} | |
/// {@tool snippet} | |
/// | |
/// This example shows the more generic widget `Bird` which can be given a | |
/// color and a child, and which has some internal state with a method that | |
/// can be called to mutate it: | |
/// | |
/// ```dart | |
/// class Bird extends StatefulWidget { | |
/// const Bird({ | |
/// Key key, | |
/// this.color = const Color(0xFFFFE306), | |
/// this.child, | |
/// }) : super(key: key); | |
/// | |
/// final Color color; | |
/// final Widget child; | |
/// | |
/// _BirdState createState() => _BirdState(); | |
/// } | |
/// | |
/// class _BirdState extends State<Bird> { | |
/// double _size = 1.0; | |
/// | |
/// void grow() { | |
/// setState(() { _size += 0.1; }); | |
/// } | |
/// | |
/// @override | |
/// Widget build(BuildContext context) { | |
/// return Container( | |
/// color: widget.color, | |
/// transform: Matrix4.diagonal3Values(_size, _size, 1.0), | |
/// child: widget.child, | |
/// ); | |
/// } | |
/// } | |
/// ``` | |
/// {@end-tool} | |
/// | |
/// By convention, widget constructors only use named arguments. Named arguments | |
/// can be marked as required using [@required]. Also by convention, the first | |
/// argument is [key], and the last argument is `child`, `children`, or the | |
/// equivalent. | |
/// | |
/// See also: | |
/// | |
/// * [State], where the logic behind a [StatefulWidget] is hosted. | |
/// * [StatelessWidget], for widgets that always build the same way given a | |
/// particular configuration and ambient state. | |
/// * [InheritedWidget], for widgets that introduce ambient state that can | |
/// be read by descendant widgets. | |
abstract class StatefulWidget extends Widget { | |
/// Initializes [key] for subclasses. | |
const StatefulWidget({ Key key }) : super(key: key); | |
/// Creates a [StatefulElement] to manage this widget's location in the tree. | |
/// | |
/// It is uncommon for subclasses to override this method. | |
@override | |
StatefulElement createElement() => StatefulElement(this); | |
/// Creates the mutable state for this widget at a given location in the tree. | |
/// | |
/// Subclasses should override this method to return a newly created | |
/// instance of their associated [State] subclass: | |
/// | |
/// ```dart | |
/// @override | |
/// _MyState createState() => _MyState(); | |
/// ``` | |
/// | |
/// The framework can call this method multiple times over the lifetime of | |
/// a [StatefulWidget]. For example, if the widget is inserted into the tree | |
/// in multiple locations, the framework will create a separate [State] object | |
/// for each location. Similarly, if the widget is removed from the tree and | |
/// later inserted into the tree again, the framework will call [createState] | |
/// again to create a fresh [State] object, simplifying the lifecycle of | |
/// [State] objects. | |
@protected | |
@factory | |
State createState(); | |
} | |
/// Tracks the lifecycle of [State] objects when asserts are enabled. | |
enum _StateLifecycle { | |
/// The [State] object has been created. [State.initState] is called at this | |
/// time. | |
created, | |
/// The [State.initState] method has been called but the [State] object is | |
/// not yet ready to build. [State.didChangeDependencies] is called at this time. | |
initialized, | |
/// The [State] object is ready to build and [State.dispose] has not yet been | |
/// called. | |
ready, | |
/// The [State.dispose] method has been called and the [State] object is | |
/// no longer able to build. | |
defunct, | |
} | |
/// The signature of [State.setState] functions. | |
typedef StateSetter = void Function(VoidCallback fn); | |
/// The logic and internal state for a [StatefulWidget]. | |
/// | |
/// State is information that (1) can be read synchronously when the widget is | |
/// built and (2) might change during the lifetime of the widget. It is the | |
/// responsibility of the widget implementer to ensure that the [State] is | |
/// promptly notified when such state changes, using [State.setState]. | |
/// | |
/// [State] objects are created by the framework by calling the | |
/// [StatefulWidget.createState] method when inflating a [StatefulWidget] to | |
/// insert it into the tree. Because a given [StatefulWidget] instance can be | |
/// inflated multiple times (e.g., the widget is incorporated into the tree in | |
/// multiple places at once), there might be more than one [State] object | |
/// associated with a given [StatefulWidget] instance. Similarly, if a | |
/// [StatefulWidget] is removed from the tree and later inserted in to the tree | |
/// again, the framework will call [StatefulWidget.createState] again to create | |
/// a fresh [State] object, simplifying the lifecycle of [State] objects. | |
/// | |
/// [State] objects have the following lifecycle: | |
/// | |
/// * The framework creates a [State] object by calling | |
/// [StatefulWidget.createState]. | |
/// * The newly created [State] object is associated with a [BuildContext]. | |
/// This association is permanent: the [State] object will never change its | |
/// [BuildContext]. However, the [BuildContext] itself can be moved around | |
/// the tree along with its subtree. At this point, the [State] object is | |
/// considered [mounted]. | |
/// * The framework calls [initState]. Subclasses of [State] should override | |
/// [initState] to perform one-time initialization that depends on the | |
/// [BuildContext] or the widget, which are available as the [context] and | |
/// [widget] properties, respectively, when the [initState] method is | |
/// called. | |
/// * The framework calls [didChangeDependencies]. Subclasses of [State] should | |
/// override [didChangeDependencies] to perform initialization involving | |
/// [InheritedWidget]s. If [BuildContext.dependOnInheritedWidgetOfExactType] is | |
/// called, the [didChangeDependencies] method will be called again if the | |
/// inherited widgets subsequently change or if the widget moves in the tree. | |
/// * At this point, the [State] object is fully initialized and the framework | |
/// might call its [build] method any number of times to obtain a | |
/// description of the user interface for this subtree. [State] objects can | |
/// spontaneously request to rebuild their subtree by callings their | |
/// [setState] method, which indicates that some of their internal state | |
/// has changed in a way that might impact the user interface in this | |
/// subtree. | |
/// * During this time, a parent widget might rebuild and request that this | |
/// location in the tree update to display a new widget with the same | |
/// [runtimeType] and [Widget.key]. When this happens, the framework will | |
/// update the [widget] property to refer to the new widget and then call the | |
/// [didUpdateWidget] method with the previous widget as an argument. [State] | |
/// objects should override [didUpdateWidget] to respond to changes in their | |
/// associated widget (e.g., to start implicit animations). The framework | |
/// always calls [build] after calling [didUpdateWidget], which means any | |
/// calls to [setState] in [didUpdateWidget] are redundant. | |
/// * During development, if a hot reload occurs (whether initiated from the | |
/// command line `flutter` tool by pressing `r`, or from an IDE), the | |
/// [reassemble] method is called. This provides an opportunity to | |
/// reinitialize any data that was prepared in the [initState] method. | |
/// * If the subtree containing the [State] object is removed from the tree | |
/// (e.g., because the parent built a widget with a different [runtimeType] | |
/// or [Widget.key]), the framework calls the [deactivate] method. Subclasses | |
/// should override this method to clean up any links between this object | |
/// and other elements in the tree (e.g. if you have provided an ancestor | |
/// with a pointer to a descendant's [RenderObject]). | |
/// * At this point, the framework might reinsert this subtree into another | |
/// part of the tree. If that happens, the framework will ensure that it | |
/// calls [build] to give the [State] object a chance to adapt to its new | |
/// location in the tree. If the framework does reinsert this subtree, it | |
/// will do so before the end of the animation frame in which the subtree was | |
/// removed from the tree. For this reason, [State] objects can defer | |
/// releasing most resources until the framework calls their [dispose] | |
/// method. | |
/// * If the framework does not reinsert this subtree by the end of the current | |
/// animation frame, the framework will call [dispose], which indicates that | |
/// this [State] object will never build again. Subclasses should override | |
/// this method to release any resources retained by this object (e.g., | |
/// stop any active animations). | |
/// * After the framework calls [dispose], the [State] object is considered | |
/// unmounted and the [mounted] property is false. It is an error to call | |
/// [setState] at this point. This stage of the lifecycle is terminal: there | |
/// is no way to remount a [State] object that has been disposed. | |
/// | |
/// See also: | |
/// | |
/// * [StatefulWidget], where the current configuration of a [State] is hosted, | |
/// and whose documentation has sample code for [State]. | |
/// * [StatelessWidget], for widgets that always build the same way given a | |
/// particular configuration and ambient state. | |
/// * [InheritedWidget], for widgets that introduce ambient state that can | |
/// be read by descendant widgets. | |
/// * [Widget], for an overview of widgets in general. | |
@optionalTypeArgs | |
abstract class State<T extends StatefulWidget> with Diagnosticable { | |
/// The current configuration. | |
/// | |
/// A [State] object's configuration is the corresponding [StatefulWidget] | |
/// instance. This property is initialized by the framework before calling | |
/// [initState]. If the parent updates this location in the tree to a new | |
/// widget with the same [runtimeType] and [Widget.key] as the current | |
/// configuration, the framework will update this property to refer to the new | |
/// widget and then call [didUpdateWidget], passing the old configuration as | |
/// an argument. | |
T get widget => _widget; | |
T _widget; | |
/// The current stage in the lifecycle for this state object. | |
/// | |
/// This field is used by the framework when asserts are enabled to verify | |
/// that [State] objects move through their lifecycle in an orderly fashion. | |
_StateLifecycle _debugLifecycleState = _StateLifecycle.created; | |
/// Verifies that the [State] that was created is one that expects to be | |
/// created for that particular [Widget]. | |
bool _debugTypesAreRight(Widget widget) => widget is T; | |
/// The location in the tree where this widget builds. | |
/// | |
/// The framework associates [State] objects with a [BuildContext] after | |
/// creating them with [StatefulWidget.createState] and before calling | |
/// [initState]. The association is permanent: the [State] object will never | |
/// change its [BuildContext]. However, the [BuildContext] itself can be moved | |
/// around the tree. | |
/// | |
/// After calling [dispose], the framework severs the [State] object's | |
/// connection with the [BuildContext]. | |
BuildContext get context => _element; | |
StatefulElement _element; | |
/// Whether this [State] object is currently in a tree. | |
/// | |
/// After creating a [State] object and before calling [initState], the | |
/// framework "mounts" the [State] object by associating it with a | |
/// [BuildContext]. The [State] object remains mounted until the framework | |
/// calls [dispose], after which time the framework will never ask the [State] | |
/// object to [build] again. | |
/// | |
/// It is an error to call [setState] unless [mounted] is true. | |
bool get mounted => _element != null; | |
/// Called when this object is inserted into the tree. | |
/// | |
/// The framework will call this method exactly once for each [State] object | |
/// it creates. | |
/// | |
/// Override this method to perform initialization that depends on the | |
/// location at which this object was inserted into the tree (i.e., [context]) | |
/// or on the widget used to configure this object (i.e., [widget]). | |
/// | |
/// {@template flutter.widgets.subscriptions} | |
/// If a [State]'s [build] method depends on an object that can itself | |
/// change state, for example a [ChangeNotifier] or [Stream], or some | |
/// other object to which one can subscribe to receive notifications, then | |
/// be sure to subscribe and unsubscribe properly in [initState], | |
/// [didUpdateWidget], and [dispose]: | |
/// | |
/// * In [initState], subscribe to the object. | |
/// * In [didUpdateWidget] unsubscribe from the old object and subscribe | |
/// to the new one if the updated widget configuration requires | |
/// replacing the object. | |
/// * In [dispose], unsubscribe from the object. | |
/// | |
/// {@endtemplate} | |
/// | |
/// You cannot use [BuildContext.dependOnInheritedWidgetOfExactType] from this | |
/// method. However, [didChangeDependencies] will be called immediately | |
/// following this method, and [BuildContext.dependOnInheritedWidgetOfExactType] can | |
/// be used there. | |
/// | |
/// If you override this, make sure your method starts with a call to | |
/// super.initState(). | |
@protected | |
@mustCallSuper | |
void initState() { | |
assert(_debugLifecycleState == _StateLifecycle.created); | |
} | |
/// Called whenever the widget configuration changes. | |
/// | |
/// If the parent widget rebuilds and request that this location in the tree | |
/// update to display a new widget with the same [runtimeType] and | |
/// [Widget.key], the framework will update the [widget] property of this | |
/// [State] object to refer to the new widget and then call this method | |
/// with the previous widget as an argument. | |
/// | |
/// Override this method to respond when the [widget] changes (e.g., to start | |
/// implicit animations). | |
/// | |
/// The framework always calls [build] after calling [didUpdateWidget], which | |
/// means any calls to [setState] in [didUpdateWidget] are redundant. | |
/// | |
/// {@macro flutter.widgets.subscriptions} | |
/// | |
/// If you override this, make sure your method starts with a call to | |
/// super.didUpdateWidget(oldWidget). | |
@mustCallSuper | |
@protected | |
void didUpdateWidget(covariant T oldWidget) { } | |
/// {@macro flutter.widgets.reassemble} | |
/// | |
/// In addition to this method being invoked, it is guaranteed that the | |
/// [build] method will be invoked when a reassemble is signaled. Most | |
/// widgets therefore do not need to do anything in the [reassemble] method. | |
/// | |
/// See also: | |
/// | |
/// * [Element.reassemble] | |
/// * [BindingBase.reassembleApplication] | |
/// * [Image], which uses this to reload images. | |
@protected | |
@mustCallSuper | |
void reassemble() { } | |
/// Notify the framework that the internal state of this object has changed. | |
/// | |
/// Whenever you change the internal state of a [State] object, make the | |
/// change in a function that you pass to [setState]: | |
/// | |
/// ```dart | |
/// setState(() { _myState = newValue; }); | |
/// ``` | |
/// | |
/// The provided callback is immediately called synchronously. It must not | |
/// return a future (the callback cannot be `async`), since then it would be | |
/// unclear when the state was actually being set. | |
/// | |
/// Calling [setState] notifies the framework that the internal state of this | |
/// object has changed in a way that might impact the user interface in this | |
/// subtree, which causes the framework to schedule a [build] for this [State] | |
/// object. | |
/// | |
/// If you just change the state directly without calling [setState], the | |
/// framework might not schedule a [build] and the user interface for this | |
/// subtree might not be updated to reflect the new state. | |
/// | |
/// Generally it is recommended that the `setState` method only be used to | |
/// wrap the actual changes to the state, not any computation that might be | |
/// associated with the change. For example, here a value used by the [build] | |
/// function is incremented, and then the change is written to disk, but only | |
/// the increment is wrapped in the `setState`: | |
/// | |
/// ```dart | |
/// Future<void> _incrementCounter() async { | |
/// setState(() { | |
/// _counter++; | |
/// }); | |
/// Directory directory = await getApplicationDocumentsDirectory(); | |
/// final String dirName = directory.path; | |
/// await File('$dir/counter.txt').writeAsString('$_counter'); | |
/// } | |
/// ``` | |
/// | |
/// It is an error to call this method after the framework calls [dispose]. | |
/// You can determine whether it is legal to call this method by checking | |
/// whether the [mounted] property is true. | |
@protected | |
void setState(VoidCallback fn) { | |
assert(fn != null); | |
assert(() { | |
if (_debugLifecycleState == _StateLifecycle.defunct) { | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('setState() called after dispose(): $this'), | |
ErrorDescription( | |
'This error happens if you call setState() on a State object for a widget that ' | |
'no longer appears in the widget tree (e.g., whose parent widget no longer ' | |
'includes the widget in its build). This error can occur when code calls ' | |
'setState() from a timer or an animation callback.' | |
), | |
ErrorHint( | |
'The preferred solution is ' | |
'to cancel the timer or stop listening to the animation in the dispose() ' | |
'callback. Another solution is to check the "mounted" property of this ' | |
'object before calling setState() to ensure the object is still in the ' | |
'tree.' | |
), | |
ErrorHint( | |
'This error might indicate a memory leak if setState() is being called ' | |
'because another object is retaining a reference to this State object ' | |
'after it has been removed from the tree. To avoid memory leaks, ' | |
'consider breaking the reference to this object during dispose().' | |
), | |
]); | |
} | |
if (_debugLifecycleState == _StateLifecycle.created && !mounted) { | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('setState() called in constructor: $this'), | |
ErrorHint( | |
'This happens when you call setState() on a State object for a widget that ' | |
"hasn't been inserted into the widget tree yet. It is not necessary to call " | |
'setState() in the constructor, since the state is already assumed to be dirty ' | |
'when it is initially created.' | |
), | |
]); | |
} | |
return true; | |
}()); | |
final dynamic result = fn() as dynamic; | |
assert(() { | |
if (result is Future) { | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('setState() callback argument returned a Future.'), | |
ErrorDescription( | |
'The setState() method on $this was called with a closure or method that ' | |
'returned a Future. Maybe it is marked as "async".' | |
), | |
ErrorHint( | |
'Instead of performing asynchronous work inside a call to setState(), first ' | |
'execute the work (without updating the widget state), and then synchronously ' | |
'update the state inside a call to setState().' | |
), | |
]); | |
} | |
// We ignore other types of return values so that you can do things like: | |
// setState(() => x = 3); | |
return true; | |
}()); | |
_element.markNeedsBuild(); | |
} | |
/// Called when this object is removed from the tree. | |
/// | |
/// The framework calls this method whenever it removes this [State] object | |
/// from the tree. In some cases, the framework will reinsert the [State] | |
/// object into another part of the tree (e.g., if the subtree containing this | |
/// [State] object is grafted from one location in the tree to another). If | |
/// that happens, the framework will ensure that it calls [build] to give the | |
/// [State] object a chance to adapt to its new location in the tree. If | |
/// the framework does reinsert this subtree, it will do so before the end of | |
/// the animation frame in which the subtree was removed from the tree. For | |
/// this reason, [State] objects can defer releasing most resources until the | |
/// framework calls their [dispose] method. | |
/// | |
/// Subclasses should override this method to clean up any links between | |
/// this object and other elements in the tree (e.g. if you have provided an | |
/// ancestor with a pointer to a descendant's [RenderObject]). | |
/// | |
/// If you override this, make sure to end your method with a call to | |
/// super.deactivate(). | |
/// | |
/// See also: | |
/// | |
/// * [dispose], which is called after [deactivate] if the widget is removed | |
/// from the tree permanently. | |
@protected | |
@mustCallSuper | |
void deactivate() { } | |
/// Called when this object is removed from the tree permanently. | |
/// | |
/// The framework calls this method when this [State] object will never | |
/// build again. After the framework calls [dispose], the [State] object is | |
/// considered unmounted and the [mounted] property is false. It is an error | |
/// to call [setState] at this point. This stage of the lifecycle is terminal: | |
/// there is no way to remount a [State] object that has been disposed. | |
/// | |
/// Subclasses should override this method to release any resources retained | |
/// by this object (e.g., stop any active animations). | |
/// | |
/// {@macro flutter.widgets.subscriptions} | |
/// | |
/// If you override this, make sure to end your method with a call to | |
/// super.dispose(). | |
/// | |
/// See also: | |
/// | |
/// * [deactivate], which is called prior to [dispose]. | |
@protected | |
@mustCallSuper | |
void dispose() { | |
assert(_debugLifecycleState == _StateLifecycle.ready); | |
assert(() { | |
_debugLifecycleState = _StateLifecycle.defunct; | |
return true; | |
}()); | |
} | |
/// Describes the part of the user interface represented by this widget. | |
/// | |
/// The framework calls this method in a number of different situations. For | |
/// example: | |
/// | |
/// * After calling [initState]. | |
/// * After calling [didUpdateWidget]. | |
/// * After receiving a call to [setState]. | |
/// * After a dependency of this [State] object changes (e.g., an | |
/// [InheritedWidget] referenced by the previous [build] changes). | |
/// * After calling [deactivate] and then reinserting the [State] object into | |
/// the tree at another location. | |
/// | |
/// This method can potentially be called in every frame and should not have | |
/// any side effects beyond building a widget. | |
/// | |
/// The framework replaces the subtree below this widget with the widget | |
/// returned by this method, either by updating the existing subtree or by | |
/// removing the subtree and inflating a new subtree, depending on whether the | |
/// widget returned by this method can update the root of the existing | |
/// subtree, as determined by calling [Widget.canUpdate]. | |
/// | |
/// Typically implementations return a newly created constellation of widgets | |
/// that are configured with information from this widget's constructor, the | |
/// given [BuildContext], and the internal state of this [State] object. | |
/// | |
/// The given [BuildContext] contains information about the location in the | |
/// tree at which this widget is being built. For example, the context | |
/// provides the set of inherited widgets for this location in the tree. The | |
/// [BuildContext] argument is always the same as the [context] property of | |
/// this [State] object and will remain the same for the lifetime of this | |
/// object. The [BuildContext] argument is provided redundantly here so that | |
/// this method matches the signature for a [WidgetBuilder]. | |
/// | |
/// ## Design discussion | |
/// | |
/// ### Why is the [build] method on [State], and not [StatefulWidget]? | |
/// | |
/// Putting a `Widget build(BuildContext context)` method on [State] rather | |
/// than putting a `Widget build(BuildContext context, State state)` method | |
/// on [StatefulWidget] gives developers more flexibility when subclassing | |
/// [StatefulWidget]. | |
/// | |
/// For example, [AnimatedWidget] is a subclass of [StatefulWidget] that | |
/// introduces an abstract `Widget build(BuildContext context)` method for its | |
/// subclasses to implement. If [StatefulWidget] already had a [build] method | |
/// that took a [State] argument, [AnimatedWidget] would be forced to provide | |
/// its [State] object to subclasses even though its [State] object is an | |
/// internal implementation detail of [AnimatedWidget]. | |
/// | |
/// Conceptually, [StatelessWidget] could also be implemented as a subclass of | |
/// [StatefulWidget] in a similar manner. If the [build] method were on | |
/// [StatefulWidget] rather than [State], that would not be possible anymore. | |
/// | |
/// Putting the [build] function on [State] rather than [StatefulWidget] also | |
/// helps avoid a category of bugs related to closures implicitly capturing | |
/// `this`. If you defined a closure in a [build] function on a | |
/// [StatefulWidget], that closure would implicitly capture `this`, which is | |
/// the current widget instance, and would have the (immutable) fields of that | |
/// instance in scope: | |
/// | |
/// ```dart | |
/// class MyButton extends StatefulWidget { | |
/// ... | |
/// final Color color; | |
/// | |
/// @override | |
/// Widget build(BuildContext context, MyButtonState state) { | |
/// ... () { print("color: $color"); } ... | |
/// } | |
/// } | |
/// ``` | |
/// | |
/// For example, suppose the parent builds `MyButton` with `color` being blue, | |
/// the `$color` in the print function refers to blue, as expected. Now, | |
/// suppose the parent rebuilds `MyButton` with green. The closure created by | |
/// the first build still implicitly refers to the original widget and the | |
/// `$color` still prints blue even through the widget has been updated to | |
/// green. | |
/// | |
/// In contrast, with the [build] function on the [State] object, closures | |
/// created during [build] implicitly capture the [State] instance instead of | |
/// the widget instance: | |
/// | |
/// ```dart | |
/// class MyButtonState extends State<MyButton> { | |
/// ... | |
/// @override | |
/// Widget build(BuildContext context) { | |
/// ... () { print("color: ${widget.color}"); } ... | |
/// } | |
/// } | |
/// ``` | |
/// | |
/// Now when the parent rebuilds `MyButton` with green, the closure created by | |
/// the first build still refers to [State] object, which is preserved across | |
/// rebuilds, but the framework has updated that [State] object's [widget] | |
/// property to refer to the new `MyButton` instance and `${widget.color}` | |
/// prints green, as expected. | |
/// | |
/// See also: | |
/// | |
/// * [StatefulWidget], which contains the discussion on performance considerations. | |
@protected | |
Widget build(BuildContext context); | |
/// Called when a dependency of this [State] object changes. | |
/// | |
/// For example, if the previous call to [build] referenced an | |
/// [InheritedWidget] that later changed, the framework would call this | |
/// method to notify this object about the change. | |
/// | |
/// This method is also called immediately after [initState]. It is safe to | |
/// call [BuildContext.dependOnInheritedWidgetOfExactType] from this method. | |
/// | |
/// Subclasses rarely override this method because the framework always | |
/// calls [build] after a dependency changes. Some subclasses do override | |
/// this method because they need to do some expensive work (e.g., network | |
/// fetches) when their dependencies change, and that work would be too | |
/// expensive to do for every build. | |
@protected | |
@mustCallSuper | |
void didChangeDependencies() { } | |
@override | |
void debugFillProperties(DiagnosticPropertiesBuilder properties) { | |
super.debugFillProperties(properties); | |
assert(() { | |
properties.add(EnumProperty<_StateLifecycle>('lifecycle state', _debugLifecycleState, defaultValue: _StateLifecycle.ready)); | |
return true; | |
}()); | |
properties.add(ObjectFlagProperty<T>('_widget', _widget, ifNull: 'no widget')); | |
properties.add(ObjectFlagProperty<StatefulElement>('_element', _element, ifNull: 'not mounted')); | |
} | |
} | |
/// A widget that has a child widget provided to it, instead of building a new | |
/// widget. | |
/// | |
/// Useful as a base class for other widgets, such as [InheritedWidget] and | |
/// [ParentDataWidget]. | |
/// | |
/// See also: | |
/// | |
/// * [InheritedWidget], for widgets that introduce ambient state that can | |
/// be read by descendant widgets. | |
/// * [ParentDataWidget], for widgets that populate the | |
/// [RenderObject.parentData] slot of their child's [RenderObject] to | |
/// configure the parent widget's layout. | |
/// * [StatefulWidget] and [State], for widgets that can build differently | |
/// several times over their lifetime. | |
/// * [StatelessWidget], for widgets that always build the same way given a | |
/// particular configuration and ambient state. | |
/// * [Widget], for an overview of widgets in general. | |
abstract class ProxyWidget extends Widget { | |
/// Creates a widget that has exactly one child widget. | |
const ProxyWidget({ Key key, @required this.child }) : super(key: key); | |
/// The widget below this widget in the tree. | |
/// | |
/// {@template flutter.widgets.child} | |
/// This widget can only have one child. To lay out multiple children, let this | |
/// widget's child be a widget such as [Row], [Column], or [Stack], which have a | |
/// `children` property, and then provide the children to that widget. | |
/// {@endtemplate} | |
final Widget child; | |
} | |
/// Base class for widgets that hook [ParentData] information to children of | |
/// [RenderObjectWidget]s. | |
/// | |
/// This can be used to provide per-child configuration for | |
/// [RenderObjectWidget]s with more than one child. For example, [Stack] uses | |
/// the [Positioned] parent data widget to position each child. | |
/// | |
/// A [ParentDataWidget] is specific to a particular kind of [ParentData]. That | |
/// class is `T`, the [ParentData] type argument. | |
/// | |
/// {@tool snippet} | |
/// | |
/// This example shows how you would build a [ParentDataWidget] to configure a | |
/// `FrogJar` widget's children by specifying a [Size] for each one. | |
/// | |
/// ```dart | |
/// class FrogSize extends ParentDataWidget<FrogJarParentData> { | |
/// FrogSize({ | |
/// Key key, | |
/// @required this.size, | |
/// @required Widget child, | |
/// }) : assert(child != null), | |
/// assert(size != null), | |
/// super(key: key, child: child); | |
/// | |
/// final Size size; | |
/// | |
/// @override | |
/// void applyParentData(RenderObject renderObject) { | |
/// final FrogJarParentData parentData = renderObject.parentData; | |
/// if (parentData.size != size) { | |
/// parentData.size = size; | |
/// final RenderFrogJar targetParent = renderObject.parent; | |
/// targetParent.markNeedsLayout(); | |
/// } | |
/// } | |
/// | |
/// @override | |
/// Type get debugTypicalAncestorWidgetClass => FrogJar; | |
/// } | |
/// ``` | |
/// {@end-tool} | |
/// | |
/// See also: | |
/// | |
/// * [RenderObject], the superclass for layout algorithms. | |
/// * [RenderObject.parentData], the slot that this class configures. | |
/// * [ParentData], the superclass of the data that will be placed in | |
/// [RenderObject.parentData] slots. The `T` type parameter for | |
/// [ParentDataWidget] is a [ParentData]. | |
/// * [RenderObjectWidget], the class for widgets that wrap [RenderObject]s. | |
/// * [StatefulWidget] and [State], for widgets that can build differently | |
/// several times over their lifetime. | |
abstract class ParentDataWidget<T extends ParentData> extends ProxyWidget { | |
/// Abstract const constructor. This constructor enables subclasses to provide | |
/// const constructors so that they can be used in const expressions. | |
const ParentDataWidget({ Key key, Widget child }) | |
: super(key: key, child: child); | |
@override | |
ParentDataElement<T> createElement() => ParentDataElement<T>(this); | |
/// Checks if this widget can apply its parent data to the provided | |
/// `renderObject`. | |
/// | |
/// The [RenderObject.parentData] of the provided `renderObject` is | |
/// typically set up by an ancestor [RenderObjectWidget] of the type returned | |
/// by [debugTypicalAncestorWidgetClass]. | |
/// | |
/// This is called just before [applyParentData] is invoked with the same | |
/// [RenderObject] provided to that method. | |
bool debugIsValidRenderObject(RenderObject renderObject) { | |
assert(T != dynamic); | |
assert(T != ParentData); | |
return renderObject.parentData is T; | |
} | |
/// The [RenderObjectWidget] that is typically used to set up the [ParentData] | |
/// that [applyParentData] will write to. | |
/// | |
/// This is only used in error messages to tell users what widget typically | |
/// wraps this ParentDataWidget. | |
Type get debugTypicalAncestorWidgetClass; | |
Iterable<DiagnosticsNode> _debugDescribeIncorrectParentDataType({ | |
@required ParentData parentData, | |
RenderObjectWidget parentDataCreator, | |
DiagnosticsNode ownershipChain, | |
}) sync* { | |
assert(T != dynamic); | |
assert(T != ParentData); | |
assert(debugTypicalAncestorWidgetClass != null); | |
final String description = 'The ParentDataWidget $this wants to apply ParentData of type $T to a RenderObject'; | |
if (parentData == null) { | |
yield ErrorDescription( | |
'$description, which has not been set up to receive any ParentData.' | |
); | |
} else { | |
yield ErrorDescription( | |
'$description, which has been set up to accept ParentData of incompatible type ${parentData.runtimeType}.' | |
); | |
} | |
yield ErrorHint( | |
'Usually, this means that the $runtimeType widget has the wrong ancestor RenderObjectWidget. ' | |
'Typically, $runtimeType widgets are placed directly inside $debugTypicalAncestorWidgetClass widgets.' | |
); | |
if (parentDataCreator != null) { | |
yield ErrorHint( | |
'The offending $runtimeType is currently placed inside a ${parentDataCreator.runtimeType} widget.' | |
); | |
} | |
if (ownershipChain != null) { | |
yield ErrorDescription( | |
'The ownership chain for the RenderObject that received the incompatible parent data was:\n $ownershipChain' | |
); | |
} | |
} | |
/// Write the data from this widget into the given render object's parent data. | |
/// | |
/// The framework calls this function whenever it detects that the | |
/// [RenderObject] associated with the [child] has outdated | |
/// [RenderObject.parentData]. For example, if the render object was recently | |
/// inserted into the render tree, the render object's parent data might not | |
/// match the data in this widget. | |
/// | |
/// Subclasses are expected to override this function to copy data from their | |
/// fields into the [RenderObject.parentData] field of the given render | |
/// object. The render object's parent is guaranteed to have been created by a | |
/// widget of type `T`, which usually means that this function can assume that | |
/// the render object's parent data object inherits from a particular class. | |
/// | |
/// If this function modifies data that can change the parent's layout or | |
/// painting, this function is responsible for calling | |
/// [RenderObject.markNeedsLayout] or [RenderObject.markNeedsPaint] on the | |
/// parent, as appropriate. | |
@protected | |
void applyParentData(RenderObject renderObject); | |
/// Whether the [ParentDataElement.applyWidgetOutOfTurn] method is allowed | |
/// with this widget. | |
/// | |
/// This should only return true if this widget represents a [ParentData] | |
/// configuration that will have no impact on the layout or paint phase. | |
/// | |
/// See also: | |
/// | |
/// * [ParentDataElement.applyWidgetOutOfTurn], which verifies this in debug | |
/// mode. | |
@protected | |
bool debugCanApplyOutOfTurn() => false; | |
} | |
/// Base class for widgets that efficiently propagate information down the tree. | |
/// | |
/// To obtain the nearest instance of a particular type of inherited widget from | |
/// a build context, use [BuildContext.dependOnInheritedWidgetOfExactType]. | |
/// | |
/// Inherited widgets, when referenced in this way, will cause the consumer to | |
/// rebuild when the inherited widget itself changes state. | |
/// | |
/// {@youtube 560 315 https://www.youtube.com/watch?v=Zbm3hjPjQMk} | |
/// | |
/// {@tool snippet} | |
/// | |
/// The following is a skeleton of an inherited widget called `FrogColor`: | |
/// | |
/// ```dart | |
/// class FrogColor extends InheritedWidget { | |
/// const FrogColor({ | |
/// Key key, | |
/// @required this.color, | |
/// @required Widget child, | |
/// }) : assert(color != null), | |
/// assert(child != null), | |
/// super(key: key, child: child); | |
/// | |
/// final Color color; | |
/// | |
/// static FrogColor of(BuildContext context) { | |
/// return context.dependOnInheritedWidgetOfExactType<FrogColor>(); | |
/// } | |
/// | |
/// @override | |
/// bool updateShouldNotify(FrogColor old) => color != old.color; | |
/// } | |
/// ``` | |
/// {@end-tool} | |
/// | |
/// The convention is to provide a static method `of` on the [InheritedWidget] | |
/// which does the call to [BuildContext.dependOnInheritedWidgetOfExactType]. This | |
/// allows the class to define its own fallback logic in case there isn't | |
/// a widget in scope. In the example above, the value returned will be | |
/// null in that case, but it could also have defaulted to a value. | |
/// | |
/// Sometimes, the `of` method returns the data rather than the inherited | |
/// widget; for example, in this case it could have returned a [Color] instead | |
/// of the `FrogColor` widget. | |
/// | |
/// Occasionally, the inherited widget is an implementation detail of another | |
/// class, and is therefore private. The `of` method in that case is typically | |
/// put on the public class instead. For example, [Theme] is implemented as a | |
/// [StatelessWidget] that builds a private inherited widget; [Theme.of] looks | |
/// for that inherited widget using [BuildContext.dependOnInheritedWidgetOfExactType] | |
/// and then returns the [ThemeData]. | |
/// | |
/// {@youtube 560 315 https://www.youtube.com/watch?v=1t-8rBCGBYw} | |
/// | |
/// See also: | |
/// | |
/// * [StatefulWidget] and [State], for widgets that can build differently | |
/// several times over their lifetime. | |
/// * [StatelessWidget], for widgets that always build the same way given a | |
/// particular configuration and ambient state. | |
/// * [Widget], for an overview of widgets in general. | |
/// * [InheritedNotifier], an inherited widget whose value can be a | |
/// [Listenable], and which will notify dependents whenever the value | |
/// sends notifications. | |
/// * [InheritedModel], an inherited widget that allows clients to subscribe | |
/// to changes for subparts of the value. | |
abstract class InheritedWidget extends ProxyWidget { | |
/// Abstract const constructor. This constructor enables subclasses to provide | |
/// const constructors so that they can be used in const expressions. | |
const InheritedWidget({ Key key, Widget child }) | |
: super(key: key, child: child); | |
@override | |
InheritedElement createElement() => InheritedElement(this); | |
/// Whether the framework should notify widgets that inherit from this widget. | |
/// | |
/// When this widget is rebuilt, sometimes we need to rebuild the widgets that | |
/// inherit from this widget but sometimes we do not. For example, if the data | |
/// held by this widget is the same as the data held by `oldWidget`, then we | |
/// do not need to rebuild the widgets that inherited the data held by | |
/// `oldWidget`. | |
/// | |
/// The framework distinguishes these cases by calling this function with the | |
/// widget that previously occupied this location in the tree as an argument. | |
/// The given widget is guaranteed to have the same [runtimeType] as this | |
/// object. | |
@protected | |
bool updateShouldNotify(covariant InheritedWidget oldWidget); | |
} | |
/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s, | |
/// which wrap [RenderObject]s, which provide the actual rendering of the | |
/// application. | |
abstract class RenderObjectWidget extends Widget { | |
/// Abstract const constructor. This constructor enables subclasses to provide | |
/// const constructors so that they can be used in const expressions. | |
const RenderObjectWidget({ Key key }) : super(key: key); | |
/// RenderObjectWidgets always inflate to a [RenderObjectElement] subclass. | |
@override | |
@factory | |
RenderObjectElement createElement(); | |
/// Creates an instance of the [RenderObject] class that this | |
/// [RenderObjectWidget] represents, using the configuration described by this | |
/// [RenderObjectWidget]. | |
/// | |
/// This method should not do anything with the children of the render object. | |
/// That should instead be handled by the method that overrides | |
/// [RenderObjectElement.mount] in the object rendered by this object's | |
/// [createElement] method. See, for example, | |
/// [SingleChildRenderObjectElement.mount]. | |
@protected | |
@factory | |
RenderObject createRenderObject(BuildContext context); | |
/// Copies the configuration described by this [RenderObjectWidget] to the | |
/// given [RenderObject], which will be of the same type as returned by this | |
/// object's [createRenderObject]. | |
/// | |
/// This method should not do anything to update the children of the render | |
/// object. That should instead be handled by the method that overrides | |
/// [RenderObjectElement.update] in the object rendered by this object's | |
/// [createElement] method. See, for example, | |
/// [SingleChildRenderObjectElement.update]. | |
@protected | |
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { } | |
/// A render object previously associated with this widget has been removed | |
/// from the tree. The given [RenderObject] will be of the same type as | |
/// returned by this object's [createRenderObject]. | |
@protected | |
void didUnmountRenderObject(covariant RenderObject renderObject) { } | |
} | |
/// A superclass for RenderObjectWidgets that configure RenderObject subclasses | |
/// that have no children. | |
abstract class LeafRenderObjectWidget extends RenderObjectWidget { | |
/// Abstract const constructor. This constructor enables subclasses to provide | |
/// const constructors so that they can be used in const expressions. | |
const LeafRenderObjectWidget({ Key key }) : super(key: key); | |
@override | |
LeafRenderObjectElement createElement() => LeafRenderObjectElement(this); | |
} | |
/// A superclass for [RenderObjectWidget]s that configure [RenderObject] subclasses | |
/// that have a single child slot. (This superclass only provides the storage | |
/// for that child, it doesn't actually provide the updating logic.) | |
/// | |
/// Typically, the render object assigned to this widget will make use of | |
/// [RenderObjectWithChildMixin] to implement a single-child model. The mixin | |
/// exposes a [RenderObjectWithChildMixin.child] property that allows | |
/// retrieving the render object belonging to the [child] widget. | |
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget { | |
/// Abstract const constructor. This constructor enables subclasses to provide | |
/// const constructors so that they can be used in const expressions. | |
const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key); | |
/// The widget below this widget in the tree. | |
/// | |
/// {@macro flutter.widgets.child} | |
final Widget child; | |
@override | |
SingleChildRenderObjectElement createElement() => SingleChildRenderObjectElement(this); | |
} | |
/// A superclass for [RenderObjectWidget]s that configure [RenderObject] subclasses | |
/// that have a single list of children. (This superclass only provides the | |
/// storage for that child list, it doesn't actually provide the updating | |
/// logic.) | |
/// | |
/// This will return a [RenderObject] mixing in [ContainerRenderObjectMixin], | |
/// which provides the necessary functionality to visit the children of the | |
/// container render object (the render object belonging to the [children] widgets). | |
/// Typically, this is a [RenderBox] with [RenderBoxContainerDefaultsMixin]. | |
/// | |
/// See also: | |
/// | |
/// * [Stack], which uses [MultiChildRenderObjectWidget]. | |
/// * [RenderStack], for an example implementation of the associated render object. | |
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget { | |
/// Initializes fields for subclasses. | |
/// | |
/// The [children] argument must not be null and must not contain any null | |
/// objects. | |
MultiChildRenderObjectWidget({ Key key, this.children = const <Widget>[] }) | |
: assert(children != null), | |
assert(() { | |
final int index = children.indexOf(null); | |
if (index >= 0) { | |
throw FlutterError( | |
"$runtimeType's children must not contain any null values, " | |
'but a null value was found at index $index' | |
); | |
} | |
return true; | |
}()), // https://github.com/dart-lang/sdk/issues/29276 | |
super(key: key); | |
/// The widgets below this widget in the tree. | |
/// | |
/// If this list is going to be mutated, it is usually wise to put a [Key] on | |
/// each of the child widgets, so that the framework can match old | |
/// configurations to new configurations and maintain the underlying render | |
/// objects. | |
/// | |
/// Also, a [Widget] in Flutter is immutable, so directly modifying the | |
/// [children] such as `someMultiChildRenderObjectWidget.children.add(...)` or | |
/// as the example code below will result in incorrect behaviors. Whenever the | |
/// children list is modified, a new list object should be provided. | |
/// | |
/// ```dart | |
/// class SomeWidgetState extends State<SomeWidget> { | |
/// List<Widget> _children; | |
/// | |
/// void initState() { | |
/// _children = []; | |
/// } | |
/// | |
/// void someHandler() { | |
/// setState(() { | |
/// _children.add(...); | |
/// }); | |
/// } | |
/// | |
/// Widget build(...) { | |
/// // Reusing `List<Widget> _children` here is problematic. | |
/// return Row(children: _children); | |
/// } | |
/// } | |
/// ``` | |
/// | |
/// The following code corrects the problem mentioned above. | |
/// | |
/// ```dart | |
/// class SomeWidgetState extends State<SomeWidget> { | |
/// List<Widget> _children; | |
/// | |
/// void initState() { | |
/// _children = []; | |
/// } | |
/// | |
/// void someHandler() { | |
/// setState(() { | |
/// // The key here allows Flutter to reuse the underlying render | |
/// // objects even if the children list is recreated. | |
/// _children.add(ChildWidget(key: ...)); | |
/// }); | |
/// } | |
/// | |
/// Widget build(...) { | |
/// // Always create a new list of children as a Widget is immutable. | |
/// return Row(children: List.from(_children)); | |
/// } | |
/// } | |
/// ``` | |
final List<Widget> children; | |
@override | |
MultiChildRenderObjectElement createElement() => MultiChildRenderObjectElement(this); | |
} | |
// ELEMENTS | |
enum _ElementLifecycle { | |
initial, | |
active, | |
inactive, | |
defunct, | |
} | |
class _InactiveElements { | |
bool _locked = false; | |
final Set<Element> _elements = HashSet<Element>(); | |
void _unmount(Element element) { | |
assert(element._debugLifecycleState == _ElementLifecycle.inactive); | |
assert(() { | |
if (debugPrintGlobalKeyedWidgetLifecycle) { | |
if (element.widget.key is GlobalKey) | |
debugPrint('Discarding $element from inactive elements list.'); | |
} | |
return true; | |
}()); | |
element.visitChildren((Element child) { | |
assert(child._parent == element); | |
_unmount(child); | |
}); | |
element.unmount(); | |
assert(element._debugLifecycleState == _ElementLifecycle.defunct); | |
} | |
void _unmountAll() { | |
_locked = true; | |
final List<Element> elements = _elements.toList()..sort(Element._sort); | |
_elements.clear(); | |
try { | |
elements.reversed.forEach(_unmount); | |
} finally { | |
assert(_elements.isEmpty); | |
_locked = false; | |
} | |
} | |
static void _deactivateRecursively(Element element) { | |
assert(element._debugLifecycleState == _ElementLifecycle.active); | |
element.deactivate(); | |
assert(element._debugLifecycleState == _ElementLifecycle.inactive); | |
element.visitChildren(_deactivateRecursively); | |
assert(() { | |
element.debugDeactivated(); | |
return true; | |
}()); | |
} | |
void add(Element element) { | |
assert(!_locked); | |
assert(!_elements.contains(element)); | |
assert(element._parent == null); | |
if (element._active) | |
_deactivateRecursively(element); | |
_elements.add(element); | |
} | |
void remove(Element element) { | |
assert(!_locked); | |
assert(_elements.contains(element)); | |
assert(element._parent == null); | |
_elements.remove(element); | |
assert(!element._active); | |
} | |
bool debugContains(Element element) { | |
bool result; | |
assert(() { | |
result = _elements.contains(element); | |
return true; | |
}()); | |
return result; | |
} | |
} | |
/// Signature for the callback to [BuildContext.visitChildElements]. | |
/// | |
/// The argument is the child being visited. | |
/// | |
/// It is safe to call `element.visitChildElements` reentrantly within | |
/// this callback. | |
typedef ElementVisitor = void Function(Element element); | |
/// A handle to the location of a widget in the widget tree. | |
/// | |
/// This class presents a set of methods that can be used from | |
/// [StatelessWidget.build] methods and from methods on [State] objects. | |
/// | |
/// [BuildContext] objects are passed to [WidgetBuilder] functions (such as | |
/// [StatelessWidget.build]), and are available from the [State.context] member. | |
/// Some static functions (e.g. [showDialog], [Theme.of], and so forth) also | |
/// take build contexts so that they can act on behalf of the calling widget, or | |
/// obtain data specifically for the given context. | |
/// | |
/// Each widget has its own [BuildContext], which becomes the parent of the | |
/// widget returned by the [StatelessWidget.build] or [State.build] function. | |
/// (And similarly, the parent of any children for [RenderObjectWidget]s.) | |
/// | |
/// In particular, this means that within a build method, the build context of | |
/// the widget of the build method is not the same as the build context of the | |
/// widgets returned by that build method. This can lead to some tricky cases. | |
/// For example, [Theme.of(context)] looks for the nearest enclosing [Theme] of | |
/// the given build context. If a build method for a widget Q includes a [Theme] | |
/// within its returned widget tree, and attempts to use [Theme.of] passing its | |
/// own context, the build method for Q will not find that [Theme] object. It | |
/// will instead find whatever [Theme] was an ancestor to the widget Q. If the | |
/// build context for a subpart of the returned tree is needed, a [Builder] | |
/// widget can be used: the build context passed to the [Builder.builder] | |
/// callback will be that of the [Builder] itself. | |
/// | |
/// For example, in the following snippet, the [ScaffoldState.showSnackBar] | |
/// method is called on the [Scaffold] widget that the build method itself | |
/// creates. If a [Builder] had not been used, and instead the `context` | |
/// argument of the build method itself had been used, no [Scaffold] would have | |
/// been found, and the [Scaffold.of] function would have returned null. | |
/// | |
/// ```dart | |
/// @override | |
/// Widget build(BuildContext context) { | |
/// // here, Scaffold.of(context) returns null | |
/// return Scaffold( | |
/// appBar: AppBar(title: Text('Demo')), | |
/// body: Builder( | |
/// builder: (BuildContext context) { | |
/// return FlatButton( | |
/// child: Text('BUTTON'), | |
/// onPressed: () { | |
/// // here, Scaffold.of(context) returns the locally created Scaffold | |
/// Scaffold.of(context).showSnackBar(SnackBar( | |
/// content: Text('Hello.') | |
/// )); | |
/// } | |
/// ); | |
/// } | |
/// ) | |
/// ); | |
/// } | |
/// ``` | |
/// | |
/// The [BuildContext] for a particular widget can change location over time as | |
/// the widget is moved around the tree. Because of this, values returned from | |
/// the methods on this class should not be cached beyond the execution of a | |
/// single synchronous function. | |
/// | |
/// [BuildContext] objects are actually [Element] objects. The [BuildContext] | |
/// interface is used to discourage direct manipulation of [Element] objects. | |
abstract class BuildContext { | |
/// The current configuration of the [Element] that is this [BuildContext]. | |
Widget get widget; | |
/// The [BuildOwner] for this context. The [BuildOwner] is in charge of | |
/// managing the rendering pipeline for this context. | |
BuildOwner get owner; | |
/// Whether the [widget] is currently updating the widget or render tree. | |
/// | |
/// For [StatefulWidget]s and [StatelessWidget]s this flag is true while | |
/// their respective build methods are executing. | |
/// [RenderObjectWidget]s set this to true while creating or configuring their | |
/// associated [RenderObject]s. | |
/// Other [Widget] types may set this to true for conceptually similar phases | |
/// of their lifecycle. | |
/// | |
/// When this is true, it is safe for [widget] to establish a dependency to an | |
/// [InheritedWidget] by calling [dependOnInheritedElement] or | |
/// [dependOnInheritedWidgetOfExactType]. | |
/// | |
/// Accessing this flag in release mode is not valid. | |
bool get debugDoingBuild; | |
/// The current [RenderObject] for the widget. If the widget is a | |
/// [RenderObjectWidget], this is the render object that the widget created | |
/// for itself. Otherwise, it is the render object of the first descendant | |
/// [RenderObjectWidget]. | |
/// | |
/// This method will only return a valid result after the build phase is | |
/// complete. It is therefore not valid to call this from a build method. | |
/// It should only be called from interaction event handlers (e.g. | |
/// gesture callbacks) or layout or paint callbacks. | |
/// | |
/// If the render object is a [RenderBox], which is the common case, then the | |
/// size of the render object can be obtained from the [size] getter. This is | |
/// only valid after the layout phase, and should therefore only be examined | |
/// from paint callbacks or interaction event handlers (e.g. gesture | |
/// callbacks). | |
/// | |
/// For details on the different phases of a frame, see the discussion at | |
/// [WidgetsBinding.drawFrame]. | |
/// | |
/// Calling this method is theoretically relatively expensive (O(N) in the | |
/// depth of the tree), but in practice is usually cheap because the tree | |
/// usually has many render objects and therefore the distance to the nearest | |
/// render object is usually short. | |
RenderObject findRenderObject(); | |
/// The size of the [RenderBox] returned by [findRenderObject]. | |
/// | |
/// This getter will only return a valid result after the layout phase is | |
/// complete. It is therefore not valid to call this from a build method. | |
/// It should only be called from paint callbacks or interaction event | |
/// handlers (e.g. gesture callbacks). | |
/// | |
/// For details on the different phases of a frame, see the discussion at | |
/// [WidgetsBinding.drawFrame]. | |
/// | |
/// This getter will only return a valid result if [findRenderObject] actually | |
/// returns a [RenderBox]. If [findRenderObject] returns a render object that | |
/// is not a subtype of [RenderBox] (e.g., [RenderView]), this getter will | |
/// throw an exception in checked mode and will return null in release mode. | |
/// | |
/// Calling this getter is theoretically relatively expensive (O(N) in the | |
/// depth of the tree), but in practice is usually cheap because the tree | |
/// usually has many render objects and therefore the distance to the nearest | |
/// 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. | |
/// | |
/// This method is deprecated. Please use [dependOnInheritedElement] instead. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'Use dependOnInheritedElement instead. ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
InheritedWidget inheritFromElement(InheritedElement ancestor, { Object aspect }); | |
/// 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 | |
/// [dependOnInheritedWidgetOfExactType], which calls this method after finding | |
/// the appropriate [InheritedElement] ancestor. | |
/// | |
/// All of the qualifications about when [dependOnInheritedWidgetOfExactType] can | |
/// be called apply to this method as well. | |
InheritedWidget dependOnInheritedElement(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 | |
/// type is introduced, or the widget goes away), this build context is | |
/// rebuilt so that it can obtain new values from that widget. | |
/// | |
/// This method is deprecated. Please use [dependOnInheritedWidgetOfExactType] instead. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'Use dependOnInheritedWidgetOfExactType instead. ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
InheritedWidget inheritFromWidgetOfExactType(Type targetType, { Object aspect }); | |
/// Obtains the nearest widget of the given type [T], 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 | |
/// type is introduced, or the widget goes away), this build context is | |
/// rebuilt so that it can obtain new values from that widget. | |
/// | |
/// This is typically called implicitly from `of()` static methods, e.g. | |
/// [Theme.of]. | |
/// | |
/// This method 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 | |
/// (directly or indirectly) from build methods, layout and paint callbacks, or | |
/// from [State.didChangeDependencies]. | |
/// | |
/// This method should not be called from [State.dispose] because the element | |
/// tree is no longer stable at that time. To refer to an ancestor from that | |
/// method, save a reference to the ancestor in [State.didChangeDependencies]. | |
/// 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. | |
/// | |
/// Calling this method is O(1) with a small constant factor, but will lead to | |
/// the widget being rebuilt more often. | |
/// | |
/// Once a widget registers a dependency on a particular type by calling this | |
/// method, it will be rebuilt, and [State.didChangeDependencies] will be | |
/// 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 [T] is an | |
/// [InheritedWidget] subclasses that supports partial updates, like | |
/// [InheritedModel]. It specifies what "aspect" of the inherited | |
/// widget this context depends on. | |
T dependOnInheritedWidgetOfExactType<T extends InheritedWidget>({ Object aspect }); | |
/// Obtains the element corresponding to the nearest widget of the given type, | |
/// which must be the type of a concrete [InheritedWidget] subclass. | |
/// | |
/// This method is deprecated. Please use [getElementForInheritedWidgetOfExactType] instead. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'Use getElementForInheritedWidgetOfExactType instead. ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
InheritedElement ancestorInheritedElementForWidgetOfExactType(Type targetType); | |
/// Obtains the element corresponding to the nearest widget of the given type [T], | |
/// which must be the type of a concrete [InheritedWidget] subclass. | |
/// | |
/// Returns null if no such element is found. | |
/// | |
/// Calling this method is O(1) with a small constant factor. | |
/// | |
/// This method does not establish a relationship with the target in the way | |
/// that [dependOnInheritedWidgetOfExactType] does. | |
/// | |
/// This method should not be called from [State.dispose] because the element | |
/// tree is no longer stable at that time. To refer to an ancestor from that | |
/// method, save a reference to the ancestor by calling | |
/// [dependOnInheritedWidgetOfExactType] in [State.didChangeDependencies]. It is | |
/// safe to use this method from [State.deactivate], which is called whenever | |
/// the widget is removed from the tree. | |
InheritedElement getElementForInheritedWidgetOfExactType<T extends InheritedWidget>(); | |
/// Returns the nearest ancestor widget of the given type, which must be the | |
/// type of a concrete [Widget] subclass. | |
/// | |
/// This method is deprecated. Please use [findAncestorWidgetOfExactType] instead. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'Use findAncestorWidgetOfExactType instead. ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
Widget ancestorWidgetOfExactType(Type targetType); | |
/// Returns the nearest ancestor widget of the given type [T], which must be the | |
/// type of a concrete [Widget] subclass. | |
/// | |
/// In general, [dependOnInheritedWidgetOfExactType] is more useful, since | |
/// inherited widgets will trigger consumers to rebuild when they change. This | |
/// method is appropriate when used in interaction event handlers (e.g. | |
/// gesture callbacks) or for performing one-off tasks such as asserting that | |
/// you have or don't have a widget of a specific type as an ancestor. The | |
/// return value of a Widget's build method should not depend on the value | |
/// returned by this method, because the build context will not rebuild if the | |
/// return value of this method changes. This could lead to a situation where | |
/// data used in the build method changes, but the widget is not rebuilt. | |
/// | |
/// Calling this method is relatively expensive (O(N) in the depth of the | |
/// tree). Only call this method if the distance from this widget to the | |
/// desired ancestor is known to be small and bounded. | |
/// | |
/// This method should not be called from [State.deactivate] or [State.dispose] | |
/// because the widget tree is no longer stable at that time. To refer to | |
/// an ancestor from one of those methods, save a reference to the ancestor | |
/// by calling [findAncestorWidgetOfExactType] in [State.didChangeDependencies]. | |
/// | |
/// Returns null if a widget of the requested type does not appear in the | |
/// ancestors of this context. | |
T findAncestorWidgetOfExactType<T extends Widget>(); | |
/// Returns the [State] object of the nearest ancestor [StatefulWidget] widget | |
/// that matches the given [TypeMatcher]. | |
/// | |
/// This method is deprecated. Please use [findAncestorStateOfType] instead. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'Use findAncestorStateOfType instead. ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
State ancestorStateOfType(TypeMatcher matcher); | |
/// Returns the [State] object of the nearest ancestor [StatefulWidget] widget | |
/// that is an instance of the given type [T]. | |
/// | |
/// This should not be used from build methods, because the build context will | |
/// not be rebuilt if the value that would be returned by this method changes. | |
/// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such | |
/// cases. This method is useful for changing the state of an ancestor widget in | |
/// a one-off manner, for example, to cause an ancestor scrolling list to | |
/// scroll this build context's widget into view, or to move the focus in | |
/// response to user interaction. | |
/// | |
/// In general, though, consider using a callback that triggers a stateful | |
/// change in the ancestor rather than using the imperative style implied by | |
/// this method. This will usually lead to more maintainable and reusable code | |
/// since it decouples widgets from each other. | |
/// | |
/// Calling this method is relatively expensive (O(N) in the depth of the | |
/// tree). Only call this method if the distance from this widget to the | |
/// desired ancestor is known to be small and bounded. | |
/// | |
/// This method should not be called from [State.deactivate] or [State.dispose] | |
/// because the widget tree is no longer stable at that time. To refer to | |
/// an ancestor from one of those methods, save a reference to the ancestor | |
/// by calling [findAncestorStateOfType] in [State.didChangeDependencies]. | |
/// | |
/// {@tool snippet} | |
/// | |
/// ```dart | |
/// ScrollableState scrollable = context.findAncestorStateOfType<ScrollableState>(); | |
/// ``` | |
/// {@end-tool} | |
T findAncestorStateOfType<T extends State>(); | |
/// Returns the [State] object of the furthest ancestor [StatefulWidget] widget | |
/// that matches the given [TypeMatcher]. | |
/// | |
/// This method is deprecated. Please use [findRootAncestorStateOfType] instead. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'Use findRootAncestorStateOfType instead. ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
State rootAncestorStateOfType(TypeMatcher matcher); | |
/// Returns the [State] object of the furthest ancestor [StatefulWidget] widget | |
/// that is an instance of the given type [T]. | |
/// | |
/// Functions the same way as [findAncestorStateOfType] but keeps visiting subsequent | |
/// ancestors until there are none of the type instance of [T] remaining. | |
/// Then returns the last one found. | |
/// | |
/// This operation is O(N) as well though N is the entire widget tree rather than | |
/// a subtree. | |
T findRootAncestorStateOfType<T extends State>(); | |
/// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget | |
/// that matches the given [TypeMatcher]. | |
/// | |
/// This method is deprecated. Please use [findAncestorRenderObjectOfType] instead. | |
// TODO(a14n): Remove this when it goes to stable, https://github.com/flutter/flutter/pull/44189 | |
@Deprecated( | |
'Use findAncestorRenderObjectOfType instead. ' | |
'This feature was deprecated after v1.12.1.' | |
) | |
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher); | |
/// Returns the [RenderObject] object of the nearest ancestor [RenderObjectWidget] widget | |
/// that is an instance of the given type [T]. | |
/// | |
/// This should not be used from build methods, because the build context will | |
/// not be rebuilt if the value that would be returned by this method changes. | |
/// In general, [dependOnInheritedWidgetOfExactType] is more appropriate for such | |
/// cases. This method is useful only in esoteric cases where a widget needs | |
/// to cause an ancestor to change its layout or paint behavior. For example, | |
/// it is used by [Material] so that [InkWell] widgets can trigger the ink | |
/// splash on the [Material]'s actual render object. | |
/// | |
/// Calling this method is relatively expensive (O(N) in the depth of the | |
/// tree). Only call this method if the distance from this widget to the | |
/// desired ancestor is known to be small and bounded. | |
/// | |
/// This method should not be called from [State.deactivate] or [State.dispose] | |
/// because the widget tree is no longer stable at that time. To refer to | |
/// an ancestor from one of those methods, save a reference to the ancestor | |
/// by calling [findAncestorRenderObjectOfType] in [State.didChangeDependencies]. | |
T findAncestorRenderObjectOfType<T extends RenderObject>(); | |
/// Walks the ancestor chain, starting with the parent of this build context's | |
/// widget, invoking the argument for each ancestor. The callback is given a | |
/// reference to the ancestor widget's corresponding [Element] object. The | |
/// walk stops when it reaches the root widget or when the callback returns | |
/// false. The callback must not return null. | |
/// | |
/// This is useful for inspecting the widget tree. | |
/// | |
/// Calling this method is relatively expensive (O(N) in the depth of the tree). | |
/// | |
/// This method should not be called from [State.deactivate] or [State.dispose] | |
/// because the element tree is no longer stable at that time. To refer to | |
/// an ancestor from one of those methods, save a reference to the ancestor | |
/// by calling [visitAncestorElements] in [State.didChangeDependencies]. | |
void visitAncestorElements(bool visitor(Element element)); | |
/// Walks the children of this widget. | |
/// | |
/// This is useful for applying changes to children after they are built | |
/// without waiting for the next frame, especially if the children are known, | |
/// and especially if there is exactly one child (as is always the case for | |
/// [StatefulWidget]s or [StatelessWidget]s). | |
/// | |
/// Calling this method is very cheap for build contexts that correspond to | |
/// [StatefulWidget]s or [StatelessWidget]s (O(1), since there's only one | |
/// child). | |
/// | |
/// Calling this method is potentially expensive for build contexts that | |
/// correspond to [RenderObjectWidget]s (O(N) in the number of children). | |
/// | |
/// Calling this method recursively is extremely expensive (O(N) in the number | |
/// of descendants), and should be avoided if possible. Generally it is | |
/// significantly cheaper to use an [InheritedWidget] and have the descendants | |
/// pull data down, than it is to use [visitChildElements] recursively to push | |
/// data down to them. | |
void visitChildElements(ElementVisitor visitor); | |
/// Returns a description of an [Element] from the current build context. | |
DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}); | |
/// Returns a description of the [Widget] associated with the current build context. | |
DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}); | |
/// Adds a description of a specific type of widget missing from the current | |
/// build context's ancestry tree. | |
/// | |
/// You can find an example of using this method in [debugCheckHasMaterial]. | |
List<DiagnosticsNode> describeMissingAncestor({ @required Type expectedAncestorType }); | |
/// Adds a description of the ownership chain from a specific [Element] | |
/// to the error report. | |
/// | |
/// The ownership chain is useful for debugging the source of an element. | |
DiagnosticsNode describeOwnershipChain(String name); | |
} | |
/// Manager class for the widgets framework. | |
/// | |
/// This class tracks which widgets need rebuilding, and handles other tasks | |
/// that apply to widget trees as a whole, such as managing the inactive element | |
/// list for the tree and triggering the "reassemble" command when necessary | |
/// during hot reload when debugging. | |
/// | |
/// The main build owner is typically owned by the [WidgetsBinding], and is | |
/// driven from the operating system along with the rest of the | |
/// build/layout/paint pipeline. | |
/// | |
/// Additional build owners can be built to manage off-screen widget trees. | |
/// | |
/// To assign a build owner to a tree, use the | |
/// [RootRenderObjectElement.assignOwner] method on the root element of the | |
/// widget tree. | |
class BuildOwner { | |
/// Creates an object that manages widgets. | |
BuildOwner({ this.onBuildScheduled }); | |
/// Called on each build pass when the first buildable element is marked | |
/// dirty. | |
VoidCallback onBuildScheduled; | |
final _InactiveElements _inactiveElements = _InactiveElements(); | |
final List<Element> _dirtyElements = <Element>[]; | |
bool _scheduledFlushDirtyElements = false; | |
/// Whether [_dirtyElements] need to be sorted again as a result of more | |
/// elements becoming dirty during the build. | |
/// | |
/// This is necessary to preserve the sort order defined by [Element._sort]. | |
/// | |
/// This field is set to null when [buildScope] is not actively rebuilding | |
/// the widget tree. | |
bool _dirtyElementsNeedsResorting; | |
/// Whether [buildScope] is actively rebuilding the widget tree. | |
/// | |
/// [scheduleBuildFor] should only be called when this value is true. | |
bool get _debugIsInBuildScope => _dirtyElementsNeedsResorting != null; | |
/// The object in charge of the focus tree. | |
/// | |
/// Rarely used directly. Instead, consider using [FocusScope.of] to obtain | |
/// the [FocusScopeNode] for a given [BuildContext]. | |
/// | |
/// See [FocusManager] for more details. | |
FocusManager focusManager = FocusManager(); | |
/// Adds an element to the dirty elements list so that it will be rebuilt | |
/// when [WidgetsBinding.drawFrame] calls [buildScope]. | |
void scheduleBuildFor(Element element) { | |
assert(element != null); | |
assert(element.owner == this); | |
assert(() { | |
if (debugPrintScheduleBuildForStacks) | |
debugPrintStack(label: 'scheduleBuildFor() called for $element${_dirtyElements.contains(element) ? " (ALREADY IN LIST)" : ""}'); | |
if (!element.dirty) { | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('scheduleBuildFor() called for a widget that is not marked as dirty.'), | |
element.describeElement('The method was called for the following element'), | |
ErrorDescription( | |
'This element is not current marked as dirty. Make sure to set the dirty flag before ' | |
'calling scheduleBuildFor().'), | |
ErrorHint( | |
'If you did not attempt to call scheduleBuildFor() yourself, then this probably ' | |
'indicates a bug in the widgets framework. Please report it:\n' | |
' https://github.com/flutter/flutter/issues/new?template=BUG.md' | |
), | |
]); | |
} | |
return true; | |
}()); | |
if (element._inDirtyList) { | |
assert(() { | |
if (debugPrintScheduleBuildForStacks) | |
debugPrintStack(label: 'BuildOwner.scheduleBuildFor() called; _dirtyElementsNeedsResorting was $_dirtyElementsNeedsResorting (now true); dirty list is: $_dirtyElements'); | |
if (!_debugIsInBuildScope) { | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('BuildOwner.scheduleBuildFor() called inappropriately.'), | |
ErrorHint( | |
'The BuildOwner.scheduleBuildFor() method should only be called while the ' | |
'buildScope() method is actively rebuilding the widget tree.' | |
), | |
]); | |
} | |
return true; | |
}()); | |
_dirtyElementsNeedsResorting = true; | |
return; | |
} | |
if (!_scheduledFlushDirtyElements && onBuildScheduled != null) { | |
_scheduledFlushDirtyElements = true; | |
onBuildScheduled(); | |
} | |
_dirtyElements.add(element); | |
element._inDirtyList = true; | |
assert(() { | |
if (debugPrintScheduleBuildForStacks) | |
debugPrint('...dirty list is now: $_dirtyElements'); | |
return true; | |
}()); | |
} | |
int _debugStateLockLevel = 0; | |
bool get _debugStateLocked => _debugStateLockLevel > 0; | |
/// Whether this widget tree is in the build phase. | |
/// | |
/// Only valid when asserts are enabled. | |
bool get debugBuilding => _debugBuilding; | |
bool _debugBuilding = false; | |
Element _debugCurrentBuildTarget; | |
/// Establishes a scope in which calls to [State.setState] are forbidden, and | |
/// calls the given `callback`. | |
/// | |
/// This mechanism is used to ensure that, for instance, [State.dispose] does | |
/// not call [State.setState]. | |
void lockState(void callback()) { | |
assert(callback != null); | |
assert(_debugStateLockLevel >= 0); | |
assert(() { | |
_debugStateLockLevel += 1; | |
return true; | |
}()); | |
try { | |
callback(); | |
} finally { | |
assert(() { | |
_debugStateLockLevel -= 1; | |
return true; | |
}()); | |
} | |
assert(_debugStateLockLevel >= 0); | |
} | |
/// Establishes a scope for updating the widget tree, and calls the given | |
/// `callback`, if any. Then, builds all the elements that were marked as | |
/// dirty using [scheduleBuildFor], in depth order. | |
/// | |
/// This mechanism prevents build methods from transitively requiring other | |
/// build methods to run, potentially causing infinite loops. | |
/// | |
/// The dirty list is processed after `callback` returns, building all the | |
/// elements that were marked as dirty using [scheduleBuildFor], in depth | |
/// order. If elements are marked as dirty while this method is running, they | |
/// must be deeper than the `context` node, and deeper than any | |
/// previously-built node in this pass. | |
/// | |
/// To flush the current dirty list without performing any other work, this | |
/// function can be called with no callback. This is what the framework does | |
/// each frame, in [WidgetsBinding.drawFrame]. | |
/// | |
/// Only one [buildScope] can be active at a time. | |
/// | |
/// A [buildScope] implies a [lockState] scope as well. | |
/// | |
/// To print a console message every time this method is called, set | |
/// [debugPrintBuildScope] to true. This is useful when debugging problems | |
/// involving widgets not getting marked dirty, or getting marked dirty too | |
/// often. | |
void buildScope(Element context, [ VoidCallback callback ]) { | |
if (callback == null && _dirtyElements.isEmpty) | |
return; | |
assert(context != null); | |
assert(_debugStateLockLevel >= 0); | |
assert(!_debugBuilding); | |
assert(() { | |
if (debugPrintBuildScope) | |
debugPrint('buildScope called with context $context; dirty list is: $_dirtyElements'); | |
_debugStateLockLevel += 1; | |
_debugBuilding = true; | |
return true; | |
}()); | |
Timeline.startSync('Build', arguments: timelineArgumentsIndicatingLandmarkEvent); | |
try { | |
_scheduledFlushDirtyElements = true; | |
if (callback != null) { | |
assert(_debugStateLocked); | |
Element debugPreviousBuildTarget; | |
assert(() { | |
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(true); | |
debugPreviousBuildTarget = _debugCurrentBuildTarget; | |
_debugCurrentBuildTarget = context; | |
return true; | |
}()); | |
_dirtyElementsNeedsResorting = false; | |
try { | |
callback(); | |
} finally { | |
assert(() { | |
context._debugSetAllowIgnoredCallsToMarkNeedsBuild(false); | |
assert(_debugCurrentBuildTarget == context); | |
_debugCurrentBuildTarget = debugPreviousBuildTarget; | |
_debugElementWasRebuilt(context); | |
return true; | |
}()); | |
} | |
} | |
_dirtyElements.sort(Element._sort); | |
_dirtyElementsNeedsResorting = false; | |
int dirtyCount = _dirtyElements.length; | |
int index = 0; | |
while (index < dirtyCount) { | |
assert(_dirtyElements[index] != null); | |
assert(_dirtyElements[index]._inDirtyList); | |
assert(() { | |
if (_dirtyElements[index]._active && !_dirtyElements[index]._debugIsInScope(context)) { | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('Tried to build dirty widget in the wrong build scope.'), | |
ErrorDescription( | |
'A widget which was marked as dirty and is still active was scheduled to be built, ' | |
'but the current build scope unexpectedly does not contain that widget.', | |
), | |
ErrorHint( | |
'Sometimes this is detected when an element is removed from the widget tree, but the ' | |
'element somehow did not get marked as inactive. In that case, it might be caused by ' | |
'an ancestor element failing to implement visitChildren correctly, thus preventing ' | |
'some or all of its descendants from being correctly deactivated.', | |
), | |
DiagnosticsProperty<Element>( | |
'The root of the build scope was', | |
context, | |
style: DiagnosticsTreeStyle.errorProperty, | |
), | |
DiagnosticsProperty<Element>( | |
'The offending element (which does not appear to be a descendant of the root of the build scope) was', | |
_dirtyElements[index], | |
style: DiagnosticsTreeStyle.errorProperty, | |
), | |
]); | |
} | |
return true; | |
}()); | |
try { | |
_dirtyElements[index].rebuild(); | |
} catch (e, stack) { | |
_debugReportException( | |
ErrorDescription('while rebuilding dirty elements'), | |
e, | |
stack, | |
informationCollector: () sync* { | |
yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index])); | |
yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount'); | |
}, | |
); | |
} | |
index += 1; | |
if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) { | |
_dirtyElements.sort(Element._sort); | |
_dirtyElementsNeedsResorting = false; | |
dirtyCount = _dirtyElements.length; | |
while (index > 0 && _dirtyElements[index - 1].dirty) { | |
// It is possible for previously dirty but inactive widgets to move right in the list. | |
// We therefore have to move the index left in the list to account for this. | |
// We don't know how many could have moved. However, we do know that the only possible | |
// change to the list is that nodes that were previously to the left of the index have | |
// now moved to be to the right of the right-most cleaned node, and we do know that | |
// all the clean nodes were to the left of the index. So we move the index left | |
// until just after the right-most clean node. | |
index -= 1; | |
} | |
} | |
} | |
assert(() { | |
if (_dirtyElements.any((Element element) => element._active && element.dirty)) { | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('buildScope missed some dirty elements.'), | |
ErrorHint('This probably indicates that the dirty list should have been resorted but was not.'), | |
Element.describeElements('The list of dirty elements at the end of the buildScope call was', _dirtyElements), | |
]); | |
} | |
return true; | |
}()); | |
} finally { | |
for (final Element element in _dirtyElements) { | |
assert(element._inDirtyList); | |
element._inDirtyList = false; | |
} | |
_dirtyElements.clear(); | |
_scheduledFlushDirtyElements = false; | |
_dirtyElementsNeedsResorting = null; | |
Timeline.finishSync(); | |
assert(_debugBuilding); | |
assert(() { | |
_debugBuilding = false; | |
_debugStateLockLevel -= 1; | |
if (debugPrintBuildScope) | |
debugPrint('buildScope finished'); | |
return true; | |
}()); | |
} | |
assert(_debugStateLockLevel >= 0); | |
} | |
Map<Element, Set<GlobalKey>> _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans; | |
void _debugTrackElementThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans(Element node, GlobalKey key) { | |
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans ??= HashMap<Element, Set<GlobalKey>>(); | |
final Set<GlobalKey> keys = _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans | |
.putIfAbsent(node, () => HashSet<GlobalKey>()); | |
keys.add(key); | |
} | |
void _debugElementWasRebuilt(Element node) { | |
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans?.remove(node); | |
} | |
/// Complete the element build pass by unmounting any elements that are no | |
/// longer active. | |
/// | |
/// This is called by [WidgetsBinding.drawFrame]. | |
/// | |
/// In debug mode, this also runs some sanity checks, for example checking for | |
/// duplicate global keys. | |
/// | |
/// After the current call stack unwinds, a microtask that notifies listeners | |
/// about changes to global keys will run. | |
void finalizeTree() { | |
Timeline.startSync('Finalize tree', arguments: timelineArgumentsIndicatingLandmarkEvent); | |
try { | |
lockState(() { | |
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys | |
}); | |
assert(() { | |
try { | |
GlobalKey._debugVerifyGlobalKeyReservation(); | |
GlobalKey._debugVerifyIllFatedPopulation(); | |
if (_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans != null && | |
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.isNotEmpty) { | |
final Set<GlobalKey> keys = HashSet<GlobalKey>(); | |
for (final Element element in _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.keys) { | |
if (element._debugLifecycleState != _ElementLifecycle.defunct) | |
keys.addAll(_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans[element]); | |
} | |
if (keys.isNotEmpty) { | |
final Map<String, int> keyStringCount = HashMap<String, int>(); | |
for (final String key in keys.map<String>((GlobalKey key) => key.toString())) { | |
if (keyStringCount.containsKey(key)) { | |
keyStringCount[key] += 1; | |
} else { | |
keyStringCount[key] = 1; | |
} | |
} | |
final List<String> keyLabels = <String>[]; | |
keyStringCount.forEach((String key, int count) { | |
if (count == 1) { | |
keyLabels.add(key); | |
} else { | |
keyLabels.add('$key ($count different affected keys had this toString representation)'); | |
} | |
}); | |
final Iterable<Element> elements = _debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans.keys; | |
final Map<String, int> elementStringCount = HashMap<String, int>(); | |
for (final String element in elements.map<String>((Element element) => element.toString())) { | |
if (elementStringCount.containsKey(element)) { | |
elementStringCount[element] += 1; | |
} else { | |
elementStringCount[element] = 1; | |
} | |
} | |
final List<String> elementLabels = <String>[]; | |
elementStringCount.forEach((String element, int count) { | |
if (count == 1) { | |
elementLabels.add(element); | |
} else { | |
elementLabels.add('$element ($count different affected elements had this toString representation)'); | |
} | |
}); | |
assert(keyLabels.isNotEmpty); | |
final String the = keys.length == 1 ? ' the' : ''; | |
final String s = keys.length == 1 ? '' : 's'; | |
final String were = keys.length == 1 ? 'was' : 'were'; | |
final String their = keys.length == 1 ? 'its' : 'their'; | |
final String respective = elementLabels.length == 1 ? '' : ' respective'; | |
final String those = keys.length == 1 ? 'that' : 'those'; | |
final String s2 = elementLabels.length == 1 ? '' : 's'; | |
final String those2 = elementLabels.length == 1 ? 'that' : 'those'; | |
final String they = elementLabels.length == 1 ? 'it' : 'they'; | |
final String think = elementLabels.length == 1 ? 'thinks' : 'think'; | |
final String are = elementLabels.length == 1 ? 'is' : 'are'; | |
// TODO(jacobr): make this error more structured to better expose which widgets had problems. | |
throw FlutterError.fromParts(<DiagnosticsNode>[ | |
ErrorSummary('Duplicate GlobalKey$s detected in widget tree.'), | |
// TODO(jacobr): refactor this code so the elements are clickable | |
// in GUI debug tools. | |
ErrorDescription( | |
'The following GlobalKey$s $were specified multiple times in the widget tree. This will lead to ' | |
'parts of the widget tree being truncated unexpectedly, because the second time a key is seen, ' | |
'the previous instance is moved to the new location. The key$s $were:\n' | |
'- ${keyLabels.join("\n ")}\n' | |
'This was determined by noticing that after$the widget$s with the above global key$s $were moved ' | |
'out of $their$respective previous parent$s2, $those2 previous parent$s2 never updated during this frame, meaning ' | |
'that $they either did not update at all or updated before the widget$s $were moved, in either case ' | |
'implying that $they still $think that $they should have a child with $those global key$s.\n' | |
'The specific parent$s2 that did not update after having one or more children forcibly removed ' | |
'due to GlobalKey reparenting $are:\n' | |
'- ${elementLabels.join("\n ")}' | |
'\nA GlobalKey can only be specified on one widget at a time in the widget tree.' | |
), | |
]); | |
} | |
} | |
} finally { | |
_debugElementsThatWillNeedToBeRebuiltDueToGlobalKeyShenanigans?.clear(); | |
} | |
return true; | |
}()); | |
} catch (e, stack) { | |
// Catching the exception directly to avoid activating the ErrorWidget. | |
// Since the tree is in a broken state, adding the ErrorWidget would | |
// cause more exceptions. | |
_debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack); | |
} finally { | |
Timeline.finishSync(); | |
} | |
} | |
/// Cause the entire subtree rooted at the given [Element] to be entirely | |
/// rebuilt. This is used by development tools when the application code has | |
/// changed and is being hot-reloaded, to cause the widget tree to pick up any | |
/// changed implementations. | |
/// | |
/// This is expensive and should not be called except during development. | |
void reassemble(Element root) { | |
Timeline.startSync('Dirty Element Tree'); | |
try { | |
assert(root._parent == null); | |
assert(root.owner == this); | |
root.reassemble(); | |
} finally { | |
Timeline.finishSync(); | |
} | |
} | |
} | |
/// An instantiation of a [Widget] at a particular location in the tree. | |
/// | |
/// Widgets describe how to configure a subtree but the same widget can be used | |
/// to configure multiple subtrees simultaneously because widgets are immutable. | |
/// An [Element] represents the use of a widget to configure a specific location | |
/// in the tree. Over time, the widget associated with a given element can | |
/// change, for example, if the parent widget rebuilds and creates a new widget | |
/// for this location. | |
/// | |
/// Elements form a tree. Most elements have a unique child, but some widgets | |
/// (e.g., subclasses of [RenderObjectElement]) can have multiple children. | |
/// | |
/// Elements have the following lifecycle: | |
/// | |
/// * The framework creates an element by calling [Widget.createElement] on the | |
/// widget that will be used as the element's initial configuration. | |
/// * The framework calls [mount] to add the newly created element to the tree | |
/// at a given slot in a given parent. The [mount] method is responsible for | |
/// inflating any child widgets and calling [attachRenderObject] as | |
/// necessary to attach any associated render objects to the render tree. | |
/// * At this point, the element is considered "active" and might appear on | |
/// screen. | |
/// * At some point, the parent might decide to change the widget used to | |
/// configure this element, for example because the parent rebuilt with new | |
/// state. When this happens, the framework will call [update] with the new | |
/// widget. The new widget will always have the same [runtimeType] and key as | |
/// old widget. If the parent wishes to change the [runtimeType] or key of | |
/// the widget at this location in the tree, it can do so by unmounting this | |
/// element and inflating the new widget at this location. | |
/// * At some point, an ancestor might decide to remove this element (or an | |
/// intermediate ancestor) from the tree, which the ancestor does by calling | |
/// [deactivateChild] on itself. Deactivating the intermediate ancestor will | |
/// remove that element's render object from the render tree and add this | |
/// element to the [owner]'s list of inactive elements, causing the framework | |
/// to call [deactivate] on this element. | |
/// * At this point, the element is considered "inactive" and will not appear | |
/// on screen. An element can remain in the inactive state only until | |
/// the end of the current animation frame. At the end of the animation | |
/// frame, any elements that are still inactive will be unmounted. | |
/// * If the element gets reincorporated into the tree (e.g., because it or one | |
/// of its ancestors has a global key that is reused), the framework will | |
/// remove the element from the [owner]'s list of inactive elements, call | |
/// [activate] on the element, and reattach the element's render object to | |
/// the render tree. (At this point, the element is again considered "active" | |
/// and might appear on screen.) | |
/// * If the element does not get reincorporated into the tree by the end of | |
/// the current animation frame, the framework will call [unmount] on the | |
/// element. | |
/// * At this point, the element is considered "defunct" and will not be | |
/// incorporated into the tree in the future. | |
abstract class Element extends DiagnosticableTree implements BuildContext { | |
/// Creates an element that uses the given widget as its configuration. | |
/// | |
/// Typically called by an override of [Widget.createElement]. | |
Element(Widget widget) | |
: assert(widget != null), | |
_widget = widget; | |
Element _parent; | |
// Custom implementation of `operator ==` optimized for the ".of" pattern | |
// used with `InheritedWidgets`. | |
@nonVirtual | |
@override | |
// ignore: avoid_equals_and_hash_code_on_mutable_classes | |
bool operator ==(Object other) => identical(this, other); | |
// Custom implementation of hash code optimized for the ".of" pattern used | |
// with `InheritedWidgets`. | |
// | |
// `Element.dependOnInheritedWidgetOfExactType` relies heavily on hash-based | |
// `Set` look-ups, putting this getter on the performance critical path. | |
// | |
// The value is designed to fit within the SMI representation. This makes | |
// the cached value use less memory (one field and no extra heap objects) and | |
// cheap to compare (no indirection). | |
// | |
// See also: | |
// | |
// * https://dart.dev/articles/dart-vm/numeric-computation, which | |
// explains how numbers are represented in Dart. | |
@nonVirtual | |
@override | |
// ignore: avoid_equals_and_hash_code_on_mutable_classes | |
int get hashCode => _cachedHash; | |
final int _cachedHash = _nextHashCode = (_nextHashCode + 1) % 0xffffff; | |
static int _nextHashCode = 1; | |
/// Information set by parent to define where this child fits in its parent's | |
/// child list. | |
/// | |
/// Subclasses of Element that only have one child should use null for | |
/// the slot for that child. | |
dynamic get slot => _slot; | |
dynamic _slot; | |
/// An integer that is guaranteed to be greater than the parent's, if any. | |
/// The element at the root of the tree must have a depth greater than 0. | |
int get depth => _depth; | |
int _depth; | |
static int _sort(Element a, Element b) { | |
if (a.depth < b.depth) | |
return -1; | |
if (b.depth < a.depth) | |
return 1; | |
if (b.dirty && !a.dirty) | |
return -1; | |
if (a.dirty && !b.dirty) | |
return 1; | |
return 0; | |
} | |
// Return a numeric encoding of the specific `Element` concrete subtype. | |
// This is used in `Element.updateChild` to determine if a hot reload modified the | |
// superclass of a mounted element's configuration. The encoding of each `Element` | |
// must match the corresponding `Widget` encoding in `Widget._debugConcreteSubtype`. | |
static int _debugConcreteSubtype(Element element) { | |
return element is StatefulElement ? 1 : | |
element is StatelessElement ? 2 : | |
0; | |
} | |
/// The configuration for this element. | |
@override | |
Widget get widget => _widget; | |
Widget _widget; | |
/// The object that manages the lifecycle of this element. | |
@override | |
BuildOwner get owner => _owner; | |
BuildOwner _owner; | |
bool _active = false; | |
/// {@template flutter.widgets.reassemble} | |
/// Called whenever the application is reassembled during debugging, for | |
/// example during hot reload. | |
/// | |
/// This method should rerun any initialization logic that depends on global | |
/// state, for example, image loading from asset bundles (since the asset | |
/// bundle may have changed). | |
/// | |
/// This function will only be called during development. In release builds, | |
/// the `ext.flutter.reassemble` hook is not available, and so this code will | |
/// never execute. | |
/// | |
/// Implementers should not rely on any ordering for hot reload source update, | |
/// reassemble, and build methods after a hot reload has been initiated. It is | |
/// possible that a [Timer] (e.g. an [Animation]) or a debugging session | |
/// attached to the isolate could trigger a build with reloaded code _before_ | |
/// reassemble is called. Code that expects preconditions to be set by | |
/// reassemble after a hot reload must be resilient to being called out of | |
/// order, e.g. by fizzling instead of throwing. That said, once reassemble is | |
/// called, build will be called after it at least once. | |
/// {@endtemplate} | |
/// | |
/// See also: | |
/// | |
/// * [State.reassemble] | |
/// * [BindingBase.reassembleApplication] | |
/// * [Image], which uses this to reload images. | |
@mustCallSuper | |
@protected | |
void reassemble() { | |
markNeedsBuild(); | |
visitChildren((Element child) { | |
child.reassemble(); | |
}); | |
} | |
bool _debugIsInScope(Element target) { | |
Element current = this; | |
while (current != null) { | |
if (target == current) | |
return true; | |
current = current._parent; | |
} | |
return false; | |
} | |
/// The render object at (or below) this location in the tree. | |
/// | |
/// If this object is a [RenderObjectElement], the render object is the one at | |
/// this location in the tree. Otherwise, this getter will walk down the tree | |
/// until it finds a [RenderObjectElement]. | |
RenderObject get renderObject { | |
RenderObject result; | |
void visit(Element element) { | |
assert(result == null); // this verifies that there's only one child | |
if (element is RenderObjectElement) | |
result = element.renderObject; | |
else | |
element.visitChildren(visit); | |
} | |
visit(this); | |
return result; | |
} | |
@override | |
List<DiagnosticsNode> describeMissingAncestor({ @required Type expectedAncestorType }) { | |
final List<DiagnosticsNode> information = <DiagnosticsNode>[]; | |
final List<Element> ancestors = <Element>[]; | |
visitAncestorElements((Element element) { | |
ancestors.add(element); | |
return true; | |
}); | |
information.add(DiagnosticsProperty<Element>( | |
'The specific widget that could not find a $expectedAncestorType ancestor was', | |
this, | |
style: DiagnosticsTreeStyle.errorProperty, | |
)); | |
if (ancestors.isNotEmpty) { | |
information.add(describeElements('The ancestors of this widget were', ancestors)); | |
} else { | |
information.add(ErrorDescription( | |
'This widget is the root of the tree, so it has no ' | |
'ancestors, let alone a "$expectedAncestorType" ancestor.' | |
)); | |
} | |
return information; | |
} | |
/// Returns a list of [Element]s from the current build context to the error report. | |
static DiagnosticsNode describeElements(String name, Iterable<Element> elements) { | |
return DiagnosticsBlock( | |
name: name, | |
children: elements.map<DiagnosticsNode>((Element element) => DiagnosticsProperty<Element>('', element)).toList(), | |
allowTruncate: true, | |
); | |
} | |
@override | |
DiagnosticsNode describeElement(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) { | |
return DiagnosticsProperty<Element>(name, this, style: style); | |
} | |
@override | |
DiagnosticsNode describeWidget(String name, {DiagnosticsTreeStyle style = DiagnosticsTreeStyle.errorProperty}) { | |
return DiagnosticsProperty<Element>(name, this, style: style); | |
} | |
@override | |
DiagnosticsNode describeOwnershipChain(String name) { | |
// TODO(jacobr): make this structured so clients can support clicks on | |
// individual entries. For example, is this an iterable with arrows as | |
// separators? | |
return StringProperty(name, debugGetCreatorChain(10)); | |
} | |
// This is used to verify that Element objects move through life in an | |
// orderly fashion. | |
_ElementLifecycle _debugLifecycleState = _ElementLifecycle.initial; | |
/// Calls the argument for each child. Must be overridden by subclasses that | |
/// support having children. | |
/// | |
/// There is no guaranteed order in which the children will be visited, though | |
/// it should be consistent over time. | |
/// | |
/// Calling this during build is dangerous: the child list might still be | |
/// being updated at that point, so the children might not be constructed yet, | |
/// or might be old children that are going to be replaced. This method should | |
/// only be called if it is provable that the children are available. | |
void visitChildren(ElementVisitor visitor) { } | |
/// Calls the argument for each child considered onstage. | |
/// | |
/// Classes like [Offstage] and [Overlay] override this method to hide their | |
/// children. | |
/// | |
/// Being onstage affects the element's discoverability during testing when | |
/// you use Flutter's [Finder] objects. For example, when you instruct the | |
/// test framework to tap on a widget, by default the finder will look for | |
/// onstage elements and ignore the offstage ones. | |
/// | |
/// The default implementation defers to [visitChildren] and therefore treats | |
/// the element as onstage. | |
/// | |
/// See also: | |
/// | |
/// * [Offstage] widget that hides its children. | |
/// * [Finder] that skips offstage widgets by default. | |
/// * [RenderObject.visitChildrenForSemantics], in contrast to this method, | |
/// designed specifically for excluding parts of the UI from the semantics | |
/// tree. | |
void debugVisitOnstageChildren(ElementVisitor visitor) => visitChildren(visitor); | |
/// Wrapper around [visitChildren] for [BuildContext]. | |
@override | |
void visitChildElements(ElementVisitor visitor) { | |
assert(() { | |
if (owner == null || |