blob: 4e61e8645601b9543f1ee8a9f374e4547af118df [file]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:collection';
import 'package:flutter/rendering.dart';
export 'dart:ui' show hashValues, hashList;
export 'package:flutter/rendering.dart' show RenderObject, RenderBox, debugPrint;
// KEYS
/// A Key is an identifier for [Widget]s and [Element]s. A new Widget will only
/// be used to reconfigure an existing Element if its Key is the same as its
/// original Widget's Key.
///
/// Keys must be unique amongst the Elements with the same parent.
abstract class Key {
/// Construct a ValueKey<String> with the given String.
/// This is the simplest way to create keys.
factory Key(String value) => new ValueKey<String>(value);
/// Default constructor, used by subclasses.
const Key.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor
}
/// A kind of [Key] that uses a value of a particular type to identify itself.
///
/// For example, a ValueKey<String> is equal to another ValueKey<String> if
/// their values match.
class ValueKey<T> extends Key {
const ValueKey(this.value) : super.constructor();
final T value;
@override
bool operator ==(dynamic other) {
if (other is! ValueKey<T>)
return false;
final ValueKey<T> typedOther = other;
return value == typedOther.value;
}
@override
int get hashCode => value.hashCode;
@override
String toString() => '[\'$value\']';
}
/// A [Key] that is only equal to itself.
class UniqueKey extends Key {
const UniqueKey() : super.constructor();
@override
String toString() => '[$hashCode]';
}
/// A kind of [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.
class ObjectKey extends Key {
const ObjectKey(this.value) : super.constructor();
final Object value;
@override
bool operator ==(dynamic other) {
if (other is! ObjectKey)
return false;
final ObjectKey typedOther = other;
return identical(value, typedOther.value);
}
@override
int get hashCode => identityHashCode(value);
@override
String toString() => '[${value.runtimeType}(${value.hashCode})]';
}
typedef void GlobalKeyRemoveListener(GlobalKey key);
/// A GlobalKey is one that must be unique across the entire application. It is
/// used by widgets that need to communicate with other widgets across the
/// application's element tree.
abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
/// Constructs a LabeledGlobalKey, which is a GlobalKey with a label used for debugging.
/// The label is not used for comparing the identity of the key.
factory GlobalKey({ String debugLabel }) => new LabeledGlobalKey<T>(debugLabel); // the label is purely for debugging purposes and is otherwise ignored
const GlobalKey.constructor() : super.constructor(); // so that subclasses can call us, since the Key() factory constructor shadows the implicit constructor
static final Map<GlobalKey, Element> _registry = new Map<GlobalKey, Element>();
static final Map<GlobalKey, int> _debugDuplicates = new Map<GlobalKey, int>();
static final Map<GlobalKey, Set<GlobalKeyRemoveListener>> _removeListeners = new Map<GlobalKey, Set<GlobalKeyRemoveListener>>();
static final Set<GlobalKey> _removedKeys = new HashSet<GlobalKey>();
void _register(Element element) {
assert(() {
if (_registry.containsKey(this)) {
int oldCount = _debugDuplicates.putIfAbsent(this, () => 1);
assert(oldCount >= 1);
_debugDuplicates[this] = oldCount + 1;
}
return true;
});
_registry[this] = element;
}
void _unregister(Element element) {
assert(() {
if (_registry.containsKey(this) && _debugDuplicates.containsKey(this)) {
int oldCount = _debugDuplicates[this];
assert(oldCount >= 2);
if (oldCount == 2) {
_debugDuplicates.remove(this);
} else {
_debugDuplicates[this] = oldCount - 1;
}
}
return true;
});
if (_registry[this] == element) {
_registry.remove(this);
_removedKeys.add(this);
}
}
Element get _currentElement => _registry[this];
BuildContext get currentContext => _currentElement;
Widget get currentWidget => _currentElement?.widget;
T get currentState {
Element element = _currentElement;
if (element is StatefulElement) {
StatefulElement statefulElement = element;
return statefulElement.state;
}
return null;
}
static void registerRemoveListener(GlobalKey key, GlobalKeyRemoveListener listener) {
assert(key != null);
Set<GlobalKeyRemoveListener> listeners =
_removeListeners.putIfAbsent(key, () => new HashSet<GlobalKeyRemoveListener>());
bool added = listeners.add(listener);
assert(added);
}
static void unregisterRemoveListener(GlobalKey key, GlobalKeyRemoveListener listener) {
assert(key != null);
assert(_removeListeners.containsKey(key));
bool removed = _removeListeners[key].remove(listener);
if (_removeListeners[key].isEmpty)
_removeListeners.remove(key);
assert(removed);
}
static bool _debugCheckForDuplicates() {
String message = '';
for (GlobalKey key in _debugDuplicates.keys) {
message += 'The following GlobalKey was found multiple times among mounted elements: $key (${_debugDuplicates[key]} instances)\n';
message += 'The most recently registered instance is: ${_registry[key]}\n';
}
if (_debugDuplicates.isNotEmpty) {
throw new WidgetError(
'Incorrect GlobalKey usage.\n'
'$message'
);
}
return true;
}
static void _notifyListeners() {
if (_removedKeys.isEmpty)
return;
try {
for (GlobalKey key in _removedKeys) {
if (!_registry.containsKey(key) && _removeListeners.containsKey(key)) {
Set<GlobalKeyRemoveListener> localListeners = new HashSet<GlobalKeyRemoveListener>.from(_removeListeners[key]);
for (GlobalKeyRemoveListener listener in localListeners)
listener(key);
}
}
} finally {
_removedKeys.clear();
}
}
}
/// Each LabeledGlobalKey instance is a unique key.
/// The optional label can be used for documentary purposes. It does not affect
/// the key's identity.
class LabeledGlobalKey<T extends State<StatefulWidget>> extends GlobalKey<T> {
const LabeledGlobalKey(this._debugLabel) : super.constructor();
final String _debugLabel;
@override
String toString() => '[GlobalKey ${_debugLabel != null ? _debugLabel : hashCode}]';
}
/// A kind of [GlobalKey] 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.
class GlobalObjectKey extends GlobalKey {
const GlobalObjectKey(this.value) : super.constructor();
final Object value;
@override
bool operator ==(dynamic other) {
if (other is! GlobalObjectKey)
return false;
final GlobalObjectKey typedOther = other;
return identical(value, typedOther.value);
}
@override
int get hashCode => identityHashCode(value);
@override
String toString() => '[$runtimeType ${value.runtimeType}(${value.hashCode})]';
}
/// This class is a work-around for the "is" operator not accepting a variable value as its right operand
class TypeMatcher<T> {
const TypeMatcher();
bool check(dynamic object) => object is T;
}
// WIDGETS
/// A Widget object describes the configuration for an [Element].
/// Widget subclasses should be immutable with const constructors.
/// Widgets form a tree that is then inflated into an Element tree.
abstract class Widget {
const Widget({ this.key });
final Key key;
/// Inflates this configuration to a concrete instance.
Element createElement();
String toStringShort() {
return key == null ? '$runtimeType' : '$runtimeType-$key';
}
@override
String toString() {
final String name = toStringShort();
final List<String> data = <String>[];
debugFillDescription(data);
if (data.isEmpty)
return '$name';
return '$name(${data.join("; ")})';
}
void debugFillDescription(List<String> description) { }
static bool canUpdate(Widget oldWidget, Widget newWidget) {
return oldWidget.runtimeType == newWidget.runtimeType &&
oldWidget.key == newWidget.key;
}
}
/// StatelessWidgets describe a way to compose other Widgets to form reusable
/// parts, which doesn't depend on anything other than the configuration
/// information in the object itself. (For compositions that can change
/// dynamically, e.g. due to having an internal clock-driven state, or depending
/// on some system state, use [StatefulWidget].)
abstract class StatelessWidget extends Widget {
const StatelessWidget({ Key key }) : super(key: key);
/// StatelessWidget always use [StatelessElement]s to represent
/// themselves in the Element tree.
@override
StatelessElement createElement() => new StatelessElement(this);
/// Returns another Widget out of which this StatelessWidget is built.
/// Typically that Widget will have been configured with further children,
/// such that really this function returns a tree of configuration.
///
/// The given build context object 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.
Widget build(BuildContext context);
}
/// StatefulWidgets provide the configuration for
/// [StatefulElement]s, which wrap [State]s, which hold mutable state
/// and can dynamically and spontaneously ask to be rebuilt.
abstract class StatefulWidget extends Widget {
const StatefulWidget({ Key key }) : super(key: key);
/// StatefulWidget always use [StatefulElement]s to represent
/// themselves in the Element tree.
@override
StatefulElement createElement() => new StatefulElement(this);
/// Returns an instance of the state to which this StatefulWidget is
/// related, using this object as the configuration. Subclasses should
/// override this to return a new instance of the State class associated with
/// this StatefulWidget class, like this:
///
/// _MyState createState() => new _MyState();
State createState();
}
enum _StateLifecycle {
created,
initialized,
ready,
defunct,
}
/// The signature of setState() methods.
typedef void StateSetter(VoidCallback fn);
/// The logic and internal state for a [StatefulWidget].
abstract class State<T extends StatefulWidget> {
/// The current configuration (an instance of the corresponding
/// [StatefulWidget] class).
T get config => _config;
T _config;
/// This is used to verify that State objects move through life 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;
/// Pointer to the owner Element object
StatefulElement _element;
/// The context in which this object will be built
BuildContext get context => _element;
bool get mounted => _element != null;
/// Called when this object is inserted into the tree. Override this function
/// to perform initialization that depends on the location at which this
/// object was inserted into the tree or on the widget configuration object.
///
/// If you override this, make sure your method starts with a call to
/// super.initState().
void initState() {
assert(_debugLifecycleState == _StateLifecycle.created);
assert(() { _debugLifecycleState = _StateLifecycle.initialized; return true; });
}
/// Called whenever the configuration changes. Override this method to update
/// additional state when the config field's value is changed.
void didUpdateConfig(T oldConfig) { }
/// Whenever you need to change internal state for a State object, make the
/// change in a function that you pass to setState(), as in:
///
/// setState(() { myState = newValue });
///
/// If you just change the state directly without calling setState(), then the
/// widget will not be scheduled for rebuilding, meaning that its rendering
/// will not be updated.
void setState(VoidCallback fn) {
assert(() {
if (_debugLifecycleState == _StateLifecycle.defunct) {
throw new WidgetError(
'setState() called after dipose(): $this\n'
'This error happens if you call setState() on State object for a widget that\n'
'no longer appears in the widget tree (e.g., whose parent widget no longer\n'
'includes the widget in its build). This error can occur when code calls\n'
'setState() from a timer or an animation callback. The preferred solution is\n'
'to cancel the timer or stop listening to the animation in the dispose()\n'
'callback. Another solution is to check the "mounted" property of this\n'
'object before calling setState() to ensure the object is still in the tree.'
);
}
return true;
});
fn();
_element.markNeedsBuild();
}
/// Called when this object is removed from the tree.
/// The object might momentarily be reattached to the tree elsewhere.
///
/// Use this to clean up any links between this state and other
/// elements in the tree (e.g. if you have provided an ancestor with
/// a pointer to a descendant's renderObject).
void deactivate() { }
/// Called when this object is removed from the tree permanently.
/// Override this to clean up any resources allocated by this
/// object.
///
/// If you override this, make sure to end your method with a call to
/// super.dispose().
void dispose() {
assert(_debugLifecycleState == _StateLifecycle.ready);
assert(() { _debugLifecycleState = _StateLifecycle.defunct; return true; });
}
/// Returns another Widget out of which this [StatefulWidget] is built.
/// Typically that Widget will have been configured with further children,
/// such that really this function returns a tree of configuration.
///
/// The given build context object 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.
Widget build(BuildContext context);
/// Called when an Inherited widget in the ancestor chain has changed. Usually
/// there is nothing to do here; whenever this is called, build() is also
/// called.
void dependenciesChanged(Type affectedWidgetType) { }
@override
String toString() {
final List<String> data = <String>[];
debugFillDescription(data);
return '$runtimeType(${data.join("; ")})';
}
void debugFillDescription(List<String> description) {
description.add('$hashCode');
assert(() {
if (_debugLifecycleState != _StateLifecycle.ready)
description.add('$_debugLifecycleState');
return true;
});
if (_config == null)
description.add('no config');
if (_element == null)
description.add('not mounted');
}
}
abstract class _ProxyWidget extends Widget {
const _ProxyWidget({ Key key, this.child }) : super(key: key);
final Widget child;
}
abstract class ParentDataWidget<T extends RenderObjectWidget> extends _ProxyWidget {
const ParentDataWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
ParentDataElement<T> createElement() => new ParentDataElement<T>(this);
/// Subclasses should override this function to return true if the given
/// ancestor is a RenderObjectWidget that wraps a RenderObject that can handle
/// the kind of ParentData widget that the ParentDataWidget subclass handles.
///
/// The default implementation uses the type argument.
bool debugIsValidAncestor(RenderObjectWidget ancestor) {
assert(T != dynamic);
assert(T != RenderObjectWidget);
return ancestor is T;
}
/// Subclasses should override this to describe the requirements for using the
/// ParentDataWidget subclass. It is called when debugIsValidAncestor()
/// returned false for an ancestor, or when there are extraneous
/// ParentDataWidgets in the ancestor chain.
String debugDescribeInvalidAncestorChain({ String description, String ownershipChain, bool foundValidAncestor, Iterable<Widget> badAncestors }) {
assert(T != dynamic);
assert(T != RenderObjectWidget);
String result;
if (!foundValidAncestor) {
result = '$runtimeType widgets must be placed inside $T widgets.\n'
'$description has no $T ancestor at all.\n';
} else {
assert(badAncestors.isNotEmpty);
result = '$runtimeType widgets must be placed directly inside $T widgets.\n'
'$description has a $T ancestor, but there are other widgets between them:\n';
for (Widget ancestor in badAncestors) {
if (ancestor.runtimeType == runtimeType) {
result += ' $ancestor (this is a different $runtimeType than the one with the problem)\n';
} else {
result += ' $ancestor\n';
}
}
result += 'These widgets cannot come between a $runtimeType and its $T.\n';
}
result += 'The ownership chain for the parent of the offending $runtimeType was:\n $ownershipChain';
return result;
}
void applyParentData(RenderObject renderObject);
}
abstract class InheritedWidget extends _ProxyWidget {
const InheritedWidget({ Key key, Widget child })
: super(key: key, child: child);
@override
InheritedElement createElement() => new InheritedElement(this);
bool updateShouldNotify(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 {
const RenderObjectWidget({ Key key }) : super(key: key);
/// RenderObjectWidgets always inflate to a RenderObjectElement subclass.
@override
RenderObjectElement createElement();
/// Constructs an instance of the RenderObject class that this
/// RenderObjectWidget represents, using the configuration described by this
/// RenderObjectWidget.
RenderObject createRenderObject(BuildContext context);
/// Copies the configuration described by this RenderObjectWidget to the given
/// RenderObject, which must be of the same type as returned by this class'
/// createRenderObject(BuildContext context).
void updateRenderObject(BuildContext context, RenderObject renderObject) { }
void didUnmountRenderObject(RenderObject renderObject) { }
}
/// A superclass for RenderObjectWidgets that configure RenderObject subclasses
/// that have no children.
abstract class LeafRenderObjectWidget extends RenderObjectWidget {
const LeafRenderObjectWidget({ Key key }) : super(key: key);
@override
LeafRenderObjectElement createElement() => new LeafRenderObjectElement(this);
}
/// A superclass for RenderObjectWidgets 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.)
abstract class SingleChildRenderObjectWidget extends RenderObjectWidget {
const SingleChildRenderObjectWidget({ Key key, this.child }) : super(key: key);
final Widget child;
@override
SingleChildRenderObjectElement createElement() => new SingleChildRenderObjectElement(this);
}
/// A superclass for RenderObjectWidgets 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.)
abstract class MultiChildRenderObjectWidget extends RenderObjectWidget {
MultiChildRenderObjectWidget({ Key key, this.children })
: super(key: key) {
assert(children != null);
assert(!children.any((Widget child) => child == null));
}
final List<Widget> children;
@override
MultiChildRenderObjectElement createElement() => new MultiChildRenderObjectElement(this);
}
// ELEMENTS
enum _ElementLifecycle {
initial,
active,
inactive,
defunct,
}
class _InactiveElements {
bool _locked = false;
final Set<Element> _elements = new HashSet<Element>();
void _unmount(Element element) {
assert(element._debugLifecycleState == _ElementLifecycle.inactive);
element.unmount();
assert(element._debugLifecycleState == _ElementLifecycle.defunct);
element.visitChildren((Element child) {
assert(child._parent == element);
_unmount(child);
});
}
void unmountAll() {
BuildableElement.lockState(() {
try {
_locked = true;
for (Element element in _elements)
_unmount(element);
} finally {
_elements.clear();
_locked = false;
}
});
}
void _deactivate(Element element) {
assert(element._debugLifecycleState == _ElementLifecycle.active);
element.deactivate();
assert(element._debugLifecycleState == _ElementLifecycle.inactive);
element.visitChildren(_deactivate);
assert(() { element.debugDeactivated(); return true; });
}
void add(Element element) {
assert(!_locked);
assert(!_elements.contains(element));
assert(element._parent == null);
if (element._active)
_deactivate(element);
_elements.add(element);
}
void remove(Element element) {
assert(!_locked);
assert(_elements.contains(element));
assert(element._parent == null);
_elements.remove(element);
assert(!element._active);
}
}
final _InactiveElements _inactiveElements = new _InactiveElements();
typedef void ElementVisitor(Element element);
abstract class BuildContext {
Widget get widget;
RenderObject findRenderObject();
InheritedWidget inheritFromWidgetOfExactType(Type targetType);
Widget ancestorWidgetOfExactType(Type targetType);
State ancestorStateOfType(TypeMatcher matcher);
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher);
void visitAncestorElements(bool visitor(Element element));
void visitChildElements(void visitor(Element element));
}
/// Elements are the instantiations of Widget configurations.
///
/// Elements can, in principle, have children. Only subclasses of
/// RenderObjectElement are allowed to have more than one child.
abstract class Element implements BuildContext {
Element(Widget widget) : _widget = widget {
assert(widget != null);
}
Element _parent;
/// 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;
/// The configuration for this element.
@override
Widget get widget => _widget;
Widget _widget;
bool _active = false;
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;
}
/// 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.
void visitChildren(ElementVisitor visitor) { }
/// Wrapper around visitChildren for BuildContext.
@override
void visitChildElements(void visitor(Element element)) {
// don't allow visitChildElements() during build, since children aren't necessarily built yet
assert(!BuildableElement._debugStateLocked);
visitChildren(visitor);
}
bool detachChild(Element child) => false;
/// This method is the core of the system.
///
/// It is called each time we are to add, update, or remove a child based on
/// an updated configuration.
///
/// If the child is null, and the newWidget is not null, then we have a new
/// child for which we need to create an Element, configured with newWidget.
///
/// If the newWidget is null, and the child is not null, then we need to
/// remove it because it no longer has a configuration.
///
/// If neither are null, then we need to update the child's configuration to
/// be the new configuration given by newWidget. If newWidget can be given to
/// the existing child, then it is so given. Otherwise, the old child needs
/// to be disposed and a new child created for the new configuration.
///
/// If both are null, then we don't have a child and won't have a child, so
/// we do nothing.
///
/// The updateChild() method returns the new child, if it had to create one,
/// or the child that was passed in, if it just had to update the child, or
/// null, if it removed the child and did not replace it.
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
if (newWidget == null) {
if (child != null)
_deactivateChild(child);
return null;
}
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
return child;
}
if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
return child;
}
_deactivateChild(child);
assert(child._parent == null);
}
return _inflateWidget(newWidget, newSlot);
}
static void finalizeTree() {
_inactiveElements.unmountAll();
assert(GlobalKey._debugCheckForDuplicates);
scheduleMicrotask(GlobalKey._notifyListeners);
}
/// Called when an Element is given a new parent shortly after having been
/// created. Use this to initialize state that depends on having a parent. For
/// state that is independent of the position in the tree, it's better to just
/// initialize the Element in the constructor.
void mount(Element parent, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.initial);
assert(widget != null);
assert(_parent == null);
assert(parent == null || parent._debugLifecycleState == _ElementLifecycle.active);
assert(slot == null);
assert(depth == null);
assert(!_active);
_parent = parent;
_slot = newSlot;
_depth = _parent != null ? _parent.depth + 1 : 1;
_active = true;
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._register(this);
}
_updateInheritance();
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; });
}
/// Called when an Element receives a new configuration widget.
void update(Widget newWidget) {
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(widget != null);
assert(newWidget != null);
assert(newWidget != widget);
assert(depth != null);
assert(_active);
assert(Widget.canUpdate(widget, newWidget));
_widget = newWidget;
}
/// Called by MultiChildRenderObjectElement, and other RenderObjectElement
/// subclasses that have multiple children, to update the slot of a particular
/// child when the child is moved in its child list.
void updateSlotForChild(Element child, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(child != null);
assert(child._parent == this);
void visit(Element element) {
element._updateSlot(newSlot);
if (element is! RenderObjectElement)
element.visitChildren(visit);
}
visit(child);
}
void _updateSlot(dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(widget != null);
assert(_parent != null);
assert(_parent._debugLifecycleState == _ElementLifecycle.active);
assert(depth != null);
_slot = newSlot;
}
void _updateDepth() {
int expectedDepth = _parent.depth + 1;
if (_depth < expectedDepth) {
_depth = expectedDepth;
visitChildren((Element child) {
child._updateDepth();
});
}
}
void detachRenderObject() {
visitChildren((Element child) {
child.detachRenderObject();
});
_slot = null;
}
void attachRenderObject(dynamic newSlot) {
assert(_slot == null);
visitChildren((Element child) {
child.attachRenderObject(newSlot);
});
_slot = newSlot;
}
Element _retakeInactiveElement(GlobalKey key, Widget newWidget) {
Element element = key._currentElement;
if (element == null)
return null;
if (!Widget.canUpdate(element.widget, newWidget))
return null;
if (element._parent != null && !element._parent.detachChild(element))
return null;
assert(element._parent == null);
_inactiveElements.remove(element);
return element;
}
Element _inflateWidget(Widget newWidget, dynamic newSlot) {
Key key = newWidget.key;
if (key is GlobalKey) {
Element newChild = _retakeInactiveElement(key, newWidget);
if (newChild != null) {
assert(newChild._parent == null);
assert(() { _debugCheckForCycles(newChild); return true; });
newChild.activate(this, newSlot);
Element updatedChild = updateChild(newChild, newWidget, newSlot);
assert(newChild == updatedChild);
return updatedChild;
}
}
Element newChild = newWidget.createElement();
assert(() { _debugCheckForCycles(newChild); return true; });
newChild.mount(this, newSlot);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
return newChild;
}
void _debugCheckForCycles(Element newChild) {
assert(newChild._parent == null);
assert(() {
Element node = this;
while (node._parent != null)
node = node._parent;
assert(node != newChild); // indicates we are about to create a cycle
return true;
});
}
void _deactivateChild(Element child) {
assert(child != null);
assert(child._parent == this);
child._parent = null;
child.detachRenderObject();
_inactiveElements.add(child); // this eventually calls child.deactivate()
}
void activate(Element parent, dynamic newSlot) {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
_reactivate();
_parent = parent;
_updateDepth();
_updateInheritance();
attachRenderObject(newSlot);
assert(_debugLifecycleState == _ElementLifecycle.active);
}
void _reactivate() {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null);
assert(depth != null);
assert(!_active);
_active = true;
assert(() { _debugLifecycleState = _ElementLifecycle.active; return true; });
visitChildren((Element child) => child._reactivate());
}
void deactivate() {
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(widget != null);
assert(depth != null);
assert(_active);
if (_dependencies != null) {
for (InheritedElement dependency in _dependencies)
dependency._dependants.remove(this);
_dependencies.clear();
}
_active = false;
assert(() { _debugLifecycleState = _ElementLifecycle.inactive; return true; });
}
/// Called after children have been deactivated.
void debugDeactivated() {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
}
/// Called when an Element is removed from the tree permanently.
void unmount() {
assert(_debugLifecycleState == _ElementLifecycle.inactive);
assert(widget != null);
assert(depth != null);
assert(!_active);
if (widget.key is GlobalKey) {
final GlobalKey key = widget.key;
key._unregister(this);
}
assert(() { _debugLifecycleState = _ElementLifecycle.defunct; return true; });
}
@override
RenderObject findRenderObject() => renderObject;
Map<Type, InheritedElement> _inheritedWidgets;
Set<InheritedElement> _dependencies;
@override
InheritedWidget inheritFromWidgetOfExactType(Type targetType) {
InheritedElement ancestor = _inheritedWidgets == null ? null : _inheritedWidgets[targetType];
if (ancestor != null) {
assert(ancestor is InheritedElement);
_dependencies ??= new HashSet<InheritedElement>();
_dependencies.add(ancestor);
ancestor._dependants.add(this);
return ancestor.widget;
}
return null;
}
void _updateInheritance() {
_inheritedWidgets = _parent?._inheritedWidgets;
}
@override
Widget ancestorWidgetOfExactType(Type targetType) {
Element ancestor = _parent;
while (ancestor != null && ancestor.widget.runtimeType != targetType)
ancestor = ancestor._parent;
return ancestor?.widget;
}
@override
State ancestorStateOfType(TypeMatcher matcher) {
Element ancestor = _parent;
while (ancestor != null) {
if (ancestor is StatefulElement && matcher.check(ancestor.state))
break;
ancestor = ancestor._parent;
}
StatefulElement statefulAncestor = ancestor;
return statefulAncestor?.state;
}
@override
RenderObject ancestorRenderObjectOfType(TypeMatcher matcher) {
Element ancestor = _parent;
while (ancestor != null) {
if (ancestor is RenderObjectElement && matcher.check(ancestor.renderObject))
break;
ancestor = ancestor._parent;
}
RenderObjectElement renderObjectAncestor = ancestor;
return renderObjectAncestor?.renderObject;
}
@override
void visitAncestorElements(bool visitor(Element element)) {
Element ancestor = _parent;
while (ancestor != null && visitor(ancestor))
ancestor = ancestor._parent;
}
void dependenciesChanged(Type affectedWidgetType) {
assert(false);
}
String debugGetOwnershipChain(int limit) {
List<String> chain = <String>[];
Element node = this;
while (chain.length < limit && node != null) {
chain.add(node.toStringShort());
node = node._parent;
}
if (node != null)
chain.add('\u22EF');
return chain.join(' \u2190 ');
}
String toStringShort() {
return widget != null ? '${widget.toStringShort()}' : '[$runtimeType]';
}
@override
String toString() {
final List<String> data = <String>[];
debugFillDescription(data);
final String name = widget != null ? '${widget.runtimeType}' : '[$runtimeType]';
return '$name(${data.join("; ")})';
}
void debugFillDescription(List<String> description) {
if (depth == null)
description.add('no depth');
if (widget == null) {
description.add('no widget');
} else {
if (widget.key != null)
description.add('${widget.key}');
widget.debugFillDescription(description);
}
}
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
String result = '$prefixLineOne$this\n';
List<Element> children = <Element>[];
visitChildren(children.add);
if (children.length > 0) {
Element last = children.removeLast();
for (Element child in children)
result += '${child.toStringDeep("$prefixOtherLines \u251C", "$prefixOtherLines \u2502")}';
result += '${last.toStringDeep("$prefixOtherLines \u2514", "$prefixOtherLines ")}';
}
return result;
}
}
/// A widget that renders an exception's message. This widget is used when a
/// build function fails, to help with determining where the problem lies.
/// Exceptions are also logged to the console, which you can read using `flutter
/// logs`. The console will also include additional information such as the
/// stack trace for the exception.
class ErrorWidget extends LeafRenderObjectWidget {
ErrorWidget(
Object exception
) : message = _stringify(exception),
super(key: new UniqueKey());
final String message;
static String _stringify(Object exception) {
try {
return exception.toString();
} catch (e) { }
return 'Error';
}
@override
RenderBox createRenderObject(BuildContext context) => new RenderErrorBox(message);
}
typedef void BuildScheduler(BuildableElement element);
/// Base class for instantiations of widgets that have builders and can be
/// marked dirty.
abstract class BuildableElement extends Element {
BuildableElement(Widget widget) : super(widget);
/// Returns true if the element has been marked as needing rebuilding.
bool get dirty => _dirty;
bool _dirty = true;
// We let widget authors call setState from initState, didUpdateConfig, and
// build even when state is locked because its convenient and a no-op anyway.
// This flag ensures that this convenience is only allowed on the element
// currently undergoing initState, didUpdateConfig, or build.
bool _debugAllowIgnoredCallsToMarkNeedsBuild = false;
bool _debugSetAllowIgnoredCallsToMarkNeedsBuild(bool value) {
assert(_debugAllowIgnoredCallsToMarkNeedsBuild == !value);
_debugAllowIgnoredCallsToMarkNeedsBuild = value;
return true;
}
static BuildScheduler scheduleBuildFor;
static int _debugStateLockLevel = 0;
static bool get _debugStateLocked => _debugStateLockLevel > 0;
static bool _debugBuilding = false;
static BuildableElement _debugCurrentBuildTarget;
/// Establishes a scope in which widget build functions can run.
///
/// Inside a build scope, widget build functions are allowed to run, but
/// State.setState() is forbidden. This mechanism prevents build functions
/// from transitively requiring other build functions to run, potentially
/// causing infinite loops.
///
/// After unwinding the last build scope on the stack, the framework verifies
/// that each global key is used at most once and notifies listeners about
/// changes to global keys.
static void lockState(void callback(), { bool building: false }) {
assert(_debugStateLockLevel >= 0);
assert(() {
if (building) {
assert(!_debugBuilding);
assert(_debugCurrentBuildTarget == null);
_debugBuilding = true;
}
_debugStateLockLevel += 1;
return true;
});
try {
callback();
} finally {
assert(() {
_debugStateLockLevel -= 1;
if (building) {
assert(_debugBuilding);
assert(_debugCurrentBuildTarget == null);
_debugBuilding = false;
}
return true;
});
}
assert(_debugStateLockLevel >= 0);
}
/// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
///
/// Since it is inefficient to build an element twice in one frame,
/// applications and widgets should be structured so as to only mark
/// widgets dirty during event handlers before the frame begins, not during
/// the build itself.
void markNeedsBuild() {
assert(_debugLifecycleState != _ElementLifecycle.defunct);
if (!_active)
return;
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(() {
if (_debugBuilding) {
bool foundTarget = false;
visitAncestorElements((Element element) {
if (element == _debugCurrentBuildTarget) {
foundTarget = true;
return false;
}
return true;
});
if (foundTarget)
return true;
}
if (_debugStateLocked && (!_debugAllowIgnoredCallsToMarkNeedsBuild || !dirty)) {
throw new WidgetError(
'setState() or markNeedsBuild() called during build.\n'
'This widget cannot be marked as needing to build because the framework '
'is already in the process of building widgets. A widget can be marked as '
'needing to be built during the build phase only if one if its ancestors '
'is currently building. This exception is allowed because the framework '
'builds parent widgets before children, which means a dirty descendant '
'will always be built. Otherwise, the framework might not visit this '
'widget during this build phase.'
);
}
return true;
});
if (dirty)
return;
_dirty = true;
assert(scheduleBuildFor != null);
scheduleBuildFor(this);
}
/// Called by the binding when scheduleBuild() has been called to mark this
/// element dirty, and, in components, by update() when the widget has
/// changed.
void rebuild() {
assert(_debugLifecycleState != _ElementLifecycle.initial);
if (!_active || !_dirty) {
_dirty = false;
return;
}
assert(_debugLifecycleState == _ElementLifecycle.active);
assert(_debugStateLocked);
BuildableElement debugPreviousBuildTarget;
assert(() {
debugPreviousBuildTarget = _debugCurrentBuildTarget;
_debugCurrentBuildTarget = this;
return true;
});
try {
performRebuild();
} catch (e, stack) {
_debugReportException('rebuilding $this', e, stack);
} finally {
assert(() {
assert(_debugCurrentBuildTarget == this);
_debugCurrentBuildTarget = debugPreviousBuildTarget;
return true;
});
}
assert(!_dirty);
}
/// Called by rebuild() after the appropriate checks have been made.
void performRebuild();
@override
void dependenciesChanged(Type affectedWidgetType) {
markNeedsBuild();
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (dirty)
description.add('dirty');
}
}
typedef Widget WidgetBuilder(BuildContext context);
/// Base class for the instantiation of [StatelessWidget], [StatefulWidget],
/// and [_ProxyWidget] widgets.
abstract class ComponentElement extends BuildableElement {
ComponentElement(Widget widget) : super(widget);
WidgetBuilder _builder;
Element _child;
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
assert(_child == null);
assert(_active);
_firstBuild();
assert(_child != null);
}
void _firstBuild() {
rebuild();
}
/// Reinvokes the build() method of the [StatelessWidget] object (for
/// stateless widgets) or the [State] object (for stateful widgets) and
/// then updates the widget tree.
///
/// Called automatically during mount() to generate the first build, and by
/// rebuild() when the element needs updating.
@override
void performRebuild() {
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(true));
Widget built;
try {
built = _builder(this);
assert(() {
if (built == null) {
throw new WidgetError(
'A build function returned null.\n'
'The offending widget is: $widget\n'
'Build functions must never return null. '
'To return an empty space that causes the building widget to fill available room, return "new Container()". '
'To return an empty space that takes as little room as possible, return "new Container(width: 0.0, height: 0.0)".'
);
}
return true;
});
} catch (e, stack) {
_debugReportException('building $_widget', e, stack);
built = new ErrorWidget(e);
} finally {
// We delay marking the element as clean until after calling _builder so
// that attempts to markNeedsBuild() during build() will be ignored.
_dirty = false;
assert(_debugSetAllowIgnoredCallsToMarkNeedsBuild(false));
}
try {
_child = updateChild(_child, built, slot);
assert(_child != null);
} catch (e, stack) {
_debugReportException('building $_widget', e, stack);
built = new ErrorWidget(e);
_child = updateChild(null, built, slot);
}
}
@override
void visitChildren(ElementVisitor visitor) {
if (_child != null)
visitor(_child);
}
@override
bool detachChild(Element child) {
assert(child == _child);
_deactivateChild(_child);
_child = null;
return true;
}
}
/// Instantiation of [StatelessWidget]s.
class StatelessElement extends ComponentElement {
StatelessElement(StatelessWidget widget) : super(widget) {
_builder = widget.build;
}
@override
StatelessWidget get widget => super.widget;
@override
void update(StatelessWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_builder = widget.build;
_dirty = true;
rebuild();
}
}
/// Instantiation of [StatefulWidget]s.
class StatefulElement extends ComponentElement {
StatefulElement(StatefulWidget widget)
: _state = widget.createState(), super(widget) {
assert(_state._debugTypesAreRight(widget));
assert(_state._element == null);
_state._element = this;
assert(_builder == null);
_builder = _state.build;
assert(_state._config == null);
_state._config = widget;
assert(_state._debugLifecycleState == _StateLifecycle.created);
}
State<StatefulWidget> get state => _state;
State<StatefulWidget> _state;
@override
void _firstBuild() {
assert(_state._debugLifecycleState == _StateLifecycle.created);
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
_state.initState();
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.initialized)
return true;
throw new WidgetError(
'${_state.runtimeType}.initState failed to call super.initState.\n'
'initState() implementations must always call their superclass initState() method, to ensure '
'that the entire widget is initialized correctly.'
);
});
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
super._firstBuild();
}
@override
void update(StatefulWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
StatefulWidget oldConfig = _state._config;
// Notice that we mark ourselves as dirty before calling didUpdateConfig to
// let authors call setState from within didUpdateConfig without triggering
// asserts.
_dirty = true;
_state._config = widget;
try {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
_state.didUpdateConfig(oldConfig);
} finally {
_debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
}
rebuild();
}
@override
void deactivate() {
_state.deactivate();
super.deactivate();
}
@override
void unmount() {
super.unmount();
_state.dispose();
assert(() {
if (_state._debugLifecycleState == _StateLifecycle.defunct)
return true;
throw new WidgetError(
'${_state.runtimeType}.dispose failed to call super.dispose.\n'
'dispose() implementations must always call their superclass dispose() method, to ensure '
'that all the resources used by the widget are fully released.'
);
});
assert(!dirty); // See BuildableElement.unmount for why this is important.
_state._element = null;
_state = null;
}
@override
void dependenciesChanged(Type affectedWidgetType) {
super.dependenciesChanged(affectedWidgetType);
_state.dependenciesChanged(affectedWidgetType);
}
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (state != null)
description.add('state: $state');
}
}
abstract class _ProxyElement extends ComponentElement {
_ProxyElement(_ProxyWidget widget) : super(widget) {
_builder = _build;
}
@override
_ProxyWidget get widget => super.widget;
Widget _build(BuildContext context) => widget.child;
@override
void update(_ProxyWidget newWidget) {
_ProxyWidget oldWidget = widget;
assert(widget != null);
assert(widget != newWidget);
super.update(newWidget);
assert(widget == newWidget);
notifyDescendants(oldWidget);
_dirty = true;
rebuild();
}
void notifyDescendants(_ProxyWidget oldWidget);
}
class ParentDataElement<T extends RenderObjectWidget> extends _ProxyElement {
ParentDataElement(ParentDataWidget<T> widget) : super(widget);
@override
ParentDataWidget<T> get widget => super.widget;
@override
void mount(Element parent, dynamic slot) {
assert(() {
List<Widget> badAncestors = <Widget>[];
Element ancestor = parent;
while (ancestor != null) {
if (ancestor is ParentDataElement<RenderObjectWidget>) {
badAncestors.add(ancestor.widget);
} else if (ancestor is RenderObjectElement) {
if (widget.debugIsValidAncestor(ancestor.widget))
break;
badAncestors.add(ancestor.widget);
}
ancestor = ancestor._parent;
}
if (ancestor != null && badAncestors.isEmpty)
return true;
throw new WidgetError(
'Incorrect use of ParentDataWidget.\n' +
widget.debugDescribeInvalidAncestorChain(
description: "$this",
ownershipChain: parent.debugGetOwnershipChain(10),
foundValidAncestor: ancestor != null,
badAncestors: badAncestors
)
);
});
super.mount(parent, slot);
}
@override
void notifyDescendants(ParentDataWidget<T> oldWidget) {
void notifyChildren(Element child) {
if (child is RenderObjectElement) {
child.updateParentData(widget);
} else {
assert(child is! ParentDataElement<RenderObjectWidget>);
child.visitChildren(notifyChildren);
}
}
visitChildren(notifyChildren);
}
}
class InheritedElement extends _ProxyElement {
InheritedElement(InheritedWidget widget) : super(widget);
@override
InheritedWidget get widget => super.widget;
final Set<Element> _dependants = new HashSet<Element>();
@override
void _updateInheritance() {
final Map<Type, InheritedElement> incomingWidgets = _parent?._inheritedWidgets;
if (incomingWidgets != null)
_inheritedWidgets = new Map<Type, InheritedElement>.from(incomingWidgets);
else
_inheritedWidgets = new Map<Type, InheritedElement>();
_inheritedWidgets[widget.runtimeType] = this;
}
@override
void debugDeactivated() {
assert(() {
assert(_dependants.isEmpty);
return true;
});
super.debugDeactivated();
}
@override
void notifyDescendants(InheritedWidget oldWidget) {
if (!widget.updateShouldNotify(oldWidget))
return;
final Type ourRuntimeType = widget.runtimeType;
for (Element dependant in _dependants) {
dependant.dependenciesChanged(ourRuntimeType);
assert(() {
// check that it really is our descendant
Element ancestor = dependant._parent;
while (ancestor != this && ancestor != null)
ancestor = ancestor._parent;
return ancestor == this;
});
}
}
}
/// Base class for instantiations of RenderObjectWidget subclasses
abstract class RenderObjectElement extends BuildableElement {
RenderObjectElement(RenderObjectWidget widget) : super(widget);
@override
RenderObjectWidget get widget => super.widget;
/// The underlying [RenderObject] for this element
@override
RenderObject get renderObject => _renderObject;
RenderObject _renderObject;
RenderObjectElement _ancestorRenderObjectElement;
RenderObjectElement _findAncestorRenderObjectElement() {
Element ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement)
ancestor = ancestor._parent;
return ancestor;
}
ParentDataElement<RenderObjectWidget> _findAncestorParentDataElement() {
Element ancestor = _parent;
while (ancestor != null && ancestor is! RenderObjectElement) {
if (ancestor is ParentDataElement<RenderObjectWidget>)
return ancestor;
ancestor = ancestor._parent;
}
return null;
}
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_renderObject = widget.createRenderObject(this);
assert(() { debugUpdateRenderObjectOwner(); return true; });
assert(_slot == newSlot);
attachRenderObject(newSlot);
_dirty = false;
}
@override
void update(RenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
assert(() { debugUpdateRenderObjectOwner(); return true; });
widget.updateRenderObject(this, renderObject);
_dirty = false;
}
void debugUpdateRenderObjectOwner() {
_renderObject.debugOwner = debugGetOwnershipChain(10);
}
@override
void performRebuild() {
_dirty = false;
}
/// Utility function for subclasses that have one or more lists of children.
/// Attempts to update the given old children list using the given new
/// widgets, removing obsolete elements and introducing new ones as necessary,
/// and then returns the new child list.
List<Element> updateChildren(List<Element> oldChildren, List<Widget> newWidgets, { Set<Element> detachedChildren }) {
assert(oldChildren != null);
assert(newWidgets != null);
Element replaceWithNullIfDetached(Element child) {
return detachedChildren != null && detachedChildren.contains(child) ? null : child;
}
// This attempts to diff the new child list (this.children) with
// the old child list (old.children), and update our renderObject
// accordingly.
// The cases it tries to optimise for are:
// - the old list is empty
// - the lists are identical
// - there is an insertion or removal of one or more widgets in
// only one place in the list
// If a widget with a key is in both lists, it will be synced.
// Widgets without keys might be synced but there is no guarantee.
// The general approach is to sync the entire new list backwards, as follows:
// 1. Walk the lists from the top, syncing nodes, until you no longer have
// matching nodes.
// 2. Walk the lists from the bottom, without syncing nodes, until you no
// longer have matching nodes. We'll sync these nodes at the end. We
// don't sync them now because we want to sync all the nodes in order
// from beginning ot end.
// At this point we narrowed the old and new lists to the point
// where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of
// keys and sync null with non-keyed items.
// 4. Walk the narrowed part of the new list forwards:
// * Sync unkeyed items with null
// * Sync keyed items with the source if it exists, else with null.
// 5. Walk the bottom of the list again, syncing the nodes.
// 6. Sync null with any items in the list of keys that are still
// mounted.
int newChildrenTop = 0;
int oldChildrenTop = 0;
int newChildrenBottom = newWidgets.length - 1;
int oldChildrenBottom = oldChildren.length - 1;
List<Element> newChildren = oldChildren.length == newWidgets.length ?
oldChildren : new List<Element>(newWidgets.length);
Element previousChild;
// Update the top of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = replaceWithNullIfDetached(oldChildren[oldChildrenTop]);
Widget newWidget = newWidgets[newChildrenTop];
assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// Scan the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = replaceWithNullIfDetached(oldChildren[oldChildrenBottom]);
Widget newWidget = newWidgets[newChildrenBottom];
assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
if (oldChild == null || !Widget.canUpdate(oldChild.widget, newWidget))
break;
oldChildrenBottom -= 1;
newChildrenBottom -= 1;
}
// Scan the old children in the middle of the list.
bool haveOldChildren = oldChildrenTop <= oldChildrenBottom;
Map<Key, Element> oldKeyedChildren;
if (haveOldChildren) {
oldKeyedChildren = new Map<Key, Element>();
while (oldChildrenTop <= oldChildrenBottom) {
Element oldChild = replaceWithNullIfDetached(oldChildren[oldChildrenTop]);
assert(oldChild == null || oldChild._debugLifecycleState == _ElementLifecycle.active);
if (oldChild != null) {
if (oldChild.widget.key != null)
oldKeyedChildren[oldChild.widget.key] = oldChild;
else
_deactivateChild(oldChild);
}
oldChildrenTop += 1;
}
}
// Update the middle of the list.
while (newChildrenTop <= newChildrenBottom) {
Element oldChild;
Widget newWidget = newWidgets[newChildrenTop];
if (haveOldChildren) {
Key key = newWidget.key;
if (key != null) {
oldChild = oldKeyedChildren[newWidget.key];
if (oldChild != null) {
if (Widget.canUpdate(oldChild.widget, newWidget)) {
// we found a match!
// remove it from oldKeyedChildren so we don't unsync it later
oldKeyedChildren.remove(key);
} else {
// Not a match, let's pretend we didn't see it for now.
oldChild = null;
}
}
}
}
assert(oldChild == null || Widget.canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
}
// We've scaned the whole list.
assert(oldChildrenTop == oldChildrenBottom + 1);
assert(newChildrenTop == newChildrenBottom + 1);
assert(newWidgets.length - newChildrenTop == oldChildren.length - oldChildrenTop);
newChildrenBottom = newWidgets.length - 1;
oldChildrenBottom = oldChildren.length - 1;
// Update the bottom of the list.
while ((oldChildrenTop <= oldChildrenBottom) && (newChildrenTop <= newChildrenBottom)) {
Element oldChild = oldChildren[oldChildrenTop];
assert(replaceWithNullIfDetached(oldChild) != null);
assert(oldChild._debugLifecycleState == _ElementLifecycle.active);
Widget newWidget = newWidgets[newChildrenTop];
assert(Widget.canUpdate(oldChild.widget, newWidget));
Element newChild = updateChild(oldChild, newWidget, previousChild);
assert(newChild._debugLifecycleState == _ElementLifecycle.active);
assert(oldChild == newChild || oldChild == null || oldChild._debugLifecycleState != _ElementLifecycle.active);
newChildren[newChildrenTop] = newChild;
previousChild = newChild;
newChildrenTop += 1;
oldChildrenTop += 1;
}
// clean up any of the remaining middle nodes from the old list
if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (Element oldChild in oldKeyedChildren.values) {
if (detachedChildren == null || !detachedChildren.contains(oldChild))
_deactivateChild(oldChild);
}
}
return newChildren;
}
@override
void deactivate() {
super.deactivate();
assert(!renderObject.attached);
}
@override
void unmount() {
super.unmount();
assert(!renderObject.attached);
widget.didUnmountRenderObject(renderObject);
}
void updateParentData(ParentDataWidget<RenderObjectWidget> parentData) {
parentData.applyParentData(renderObject);
}
@override
void _updateSlot(dynamic newSlot) {
assert(slot != newSlot);
super._updateSlot(newSlot);
assert(slot == newSlot);
_ancestorRenderObjectElement.moveChildRenderObject(renderObject, slot);
}
@override
void attachRenderObject(dynamic newSlot) {
assert(_ancestorRenderObjectElement == null);
_slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null)
updateParentData(parentDataElement.widget);
}
@override
void detachRenderObject() {
if (_ancestorRenderObjectElement != null) {
_ancestorRenderObjectElement.removeChildRenderObject(renderObject);
_ancestorRenderObjectElement = null;
}
_slot = null;
}
void insertChildRenderObject(RenderObject child, dynamic slot);
void moveChildRenderObject(RenderObject child, dynamic slot);
void removeChildRenderObject(RenderObject child);
@override
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
if (renderObject != null)
description.add('renderObject: $renderObject');
}
}
/// Instantiation of RenderObjectWidgets that have no children
class LeafRenderObjectElement extends RenderObjectElement {
LeafRenderObjectElement(LeafRenderObjectWidget widget): super(widget);
@override
void insertChildRenderObject(RenderObject child, dynamic slot) {
assert(false);
}
@override
void moveChildRenderObject(RenderObject child, dynamic slot) {
assert(false);
}
@override
void removeChildRenderObject(RenderObject child) {
assert(false);
}
}
/// Instantiation of RenderObjectWidgets that have up to one child
class SingleChildRenderObjectElement extends RenderObjectElement {
SingleChildRenderObjectElement(SingleChildRenderObjectWidget widget) : super(widget);
@override
SingleChildRenderObjectWidget get widget => super.widget;
Element _child;
@override
void visitChildren(ElementVisitor visitor) {
if (_child != null)
visitor(_child);
}
@override
bool detachChild(Element child) {
assert(child == _child);
_deactivateChild(_child);
_child = null;
return true;
}
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_child = updateChild(_child, widget.child, null);
}
@override
void update(SingleChildRenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_child = updateChild(_child, widget.child, null);
}
@override
void insertChildRenderObject(RenderObject child, dynamic slot) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(slot == null);
renderObject.child = child;
assert(renderObject == this.renderObject);
}
@override
void moveChildRenderObject(RenderObject child, dynamic slot) {
assert(false);
}
@override
void removeChildRenderObject(RenderObject child) {
final RenderObjectWithChildMixin<RenderObject> renderObject = this.renderObject;
assert(renderObject.child == child);
renderObject.child = null;
assert(renderObject == this.renderObject);
}
}
/// Instantiation of RenderObjectWidgets that can have a list of children
class MultiChildRenderObjectElement extends RenderObjectElement {
MultiChildRenderObjectElement(MultiChildRenderObjectWidget widget) : super(widget) {
assert(!_debugHasDuplicateIds());
}
@override
MultiChildRenderObjectWidget get widget => super.widget;
List<Element> _children;
// We keep a set of detached children to avoid O(n^2) work walking _children
// repeatedly to remove children.
final Set<Element> _detachedChildren = new HashSet<Element>();
@override
void insertChildRenderObject(RenderObject child, Element slot) {
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
renderObject.insert(child, after: slot?.renderObject);
assert(renderObject == this.renderObject);
}
@override
void moveChildRenderObject(RenderObject child, dynamic slot) {
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
renderObject.move(child, after: slot?.renderObject);
assert(renderObject == this.renderObject);
}
@override
void removeChildRenderObject(RenderObject child) {
final ContainerRenderObjectMixin<RenderObject, ContainerParentDataMixin<RenderObject>> renderObject = this.renderObject;
assert(child.parent == renderObject);
renderObject.remove(child);
assert(renderObject == this.renderObject);
}
bool _debugHasDuplicateIds() {
Set<Key> idSet = new HashSet<Key>();
for (Widget child in widget.children) {
assert(child != null);
if (child.key == null)
continue; // when these nodes are reordered, we just reassign the data
if (!idSet.add(child.key)) {
throw new WidgetError(
'Duplicate keys found.\n'
'If multiple keyed nodes exist as children of another node, they must have unique keys.\n'
'$widget has multiple children with key "${child.key}".'
);
}
}
return false;
}
@override
void visitChildren(ElementVisitor visitor) {
for (Element child in _children) {
if (!_detachedChildren.contains(child))
visitor(child);
}
}
@override
bool detachChild(Element child) {
_detachedChildren.add(child);
_deactivateChild(child);
return true;
}
@override
void mount(Element parent, dynamic newSlot) {
super.mount(parent, newSlot);
_children = new List<Element>(widget.children.length);
Element previousChild;
for (int i = 0; i < _children.length; ++i) {
Element newChild = _inflateWidget(widget.children[i], previousChild);
_children[i] = newChild;
previousChild = newChild;
}
}
@override
void update(MultiChildRenderObjectWidget newWidget) {
super.update(newWidget);
assert(widget == newWidget);
_children = updateChildren(_children, widget.children, detachedChildren: _detachedChildren);
_detachedChildren.clear();
}
}
class WidgetError extends AssertionError {
WidgetError(this.message);
final String message;
@override
String toString() => message;
}
typedef void WidgetsExceptionHandler(String context, dynamic exception, StackTrace stack);
/// This callback is invoked whenever an exception is caught by the widget
/// system. The 'context' argument is a description of what was happening when
/// the exception occurred, and may include additional details such as
/// descriptions of the objects involved. The 'exception' argument contains the
/// object that was thrown, and the 'stack' argument contains the stack trace.
/// If no callback is set, then a default behavior consisting of dumping the
/// context, exception, and stack trace to the console is used instead.
WidgetsExceptionHandler debugWidgetsExceptionHandler;
void _debugReportException(String context, dynamic exception, StackTrace stack) {
if (debugWidgetsExceptionHandler != null) {
debugWidgetsExceptionHandler(context, exception, stack);
} else {
debugPrint('-- EXCEPTION CAUGHT BY WIDGETS LIBRARY ---------------------------------');
debugPrint('Exception caught while $context');
debugPrint('$exception');
debugPrint('Stack trace:');
debugPrint('$stack');
debugPrint('------------------------------------------------------------------------');
}
}