Commit 7df2dfca authored by Ian Hickson's avatar Ian Hickson

Improve diagnostics around misuse of ParentDataWidgets.

Fixes https://github.com/flutter/flutter/issues/572
parent 24cdc2cc
...@@ -432,7 +432,7 @@ class CustomOneChildLayout extends OneChildRenderObjectWidget { ...@@ -432,7 +432,7 @@ class CustomOneChildLayout extends OneChildRenderObjectWidget {
} }
/// Metadata for identifying children in a [CustomMultiChildLayout]. /// Metadata for identifying children in a [CustomMultiChildLayout].
class LayoutId extends ParentDataWidget { class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
LayoutId({ LayoutId({
Key key, Key key,
Widget child, Widget child,
...@@ -445,13 +445,6 @@ class LayoutId extends ParentDataWidget { ...@@ -445,13 +445,6 @@ class LayoutId extends ParentDataWidget {
/// An object representing the identity of this child. /// An object representing the identity of this child.
final Object id; final Object id;
void debugValidateAncestor(Widget ancestor) {
assert(() {
'LayoutId must placed inside a CustomMultiChildLayout';
return ancestor is CustomMultiChildLayout;
});
}
void applyParentData(RenderObject renderObject) { void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is MultiChildLayoutParentData); assert(renderObject.parentData is MultiChildLayoutParentData);
final MultiChildLayoutParentData parentData = renderObject.parentData; final MultiChildLayoutParentData parentData = renderObject.parentData;
...@@ -967,11 +960,18 @@ class BlockBody extends MultiChildRenderObjectWidget { ...@@ -967,11 +960,18 @@ class BlockBody extends MultiChildRenderObjectWidget {
} }
} }
abstract class StackRenderObjectWidgetBase extends MultiChildRenderObjectWidget {
StackRenderObjectWidgetBase({
List<Widget> children,
Key key
}) : super(key: key, children: children);
}
/// Uses the stack layout algorithm for its children. /// Uses the stack layout algorithm for its children.
/// ///
/// For details about the stack layout algorithm, see [RenderStack]. To control /// For details about the stack layout algorithm, see [RenderStack]. To control
/// the position of child widgets, see the [Positioned] widget. /// the position of child widgets, see the [Positioned] widget.
class Stack extends MultiChildRenderObjectWidget { class Stack extends StackRenderObjectWidgetBase {
Stack(List<Widget> children, { Stack(List<Widget> children, {
Key key, Key key,
this.alignment: const FractionalOffset(0.0, 0.0) this.alignment: const FractionalOffset(0.0, 0.0)
...@@ -988,7 +988,7 @@ class Stack extends MultiChildRenderObjectWidget { ...@@ -988,7 +988,7 @@ class Stack extends MultiChildRenderObjectWidget {
} }
/// A [Stack] that shows a single child at once. /// A [Stack] that shows a single child at once.
class IndexedStack extends MultiChildRenderObjectWidget { class IndexedStack extends StackRenderObjectWidgetBase {
IndexedStack(List<Widget> children, { IndexedStack(List<Widget> children, {
Key key, Key key,
this.alignment: const FractionalOffset(0.0, 0.0), this.alignment: const FractionalOffset(0.0, 0.0),
...@@ -1017,7 +1017,7 @@ class IndexedStack extends MultiChildRenderObjectWidget { ...@@ -1017,7 +1017,7 @@ class IndexedStack extends MultiChildRenderObjectWidget {
/// This widget must be a descendant of a [Stack], and the path from this widget /// This widget must be a descendant of a [Stack], and the path from this widget
/// to its enclosing [Stack] must contain only components (e.g., not other /// to its enclosing [Stack] must contain only components (e.g., not other
/// kinds of widgets, like [RenderObjectWidget]s). /// kinds of widgets, like [RenderObjectWidget]s).
class Positioned extends ParentDataWidget { class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
Positioned({ Positioned({
Key key, Key key,
Widget child, Widget child,
...@@ -1066,13 +1066,6 @@ class Positioned extends ParentDataWidget { ...@@ -1066,13 +1066,6 @@ class Positioned extends ParentDataWidget {
/// Ignored if both top and bottom are non-null. /// Ignored if both top and bottom are non-null.
final double height; final double height;
void debugValidateAncestor(Widget ancestor) {
assert(() {
'Positioned must placed inside a Stack';
return ancestor is Stack;
});
}
void applyParentData(RenderObject renderObject) { void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is StackParentData); assert(renderObject.parentData is StackParentData);
final StackParentData parentData = renderObject.parentData; final StackParentData parentData = renderObject.parentData;
...@@ -1214,7 +1207,7 @@ class Column extends Flex { ...@@ -1214,7 +1207,7 @@ class Column extends Flex {
/// path from this widget to its enclosing [Flex], [Row], or [Column] must /// path from this widget to its enclosing [Flex], [Row], or [Column] must
/// contain only components (e.g., not other kinds of widgets, like /// contain only components (e.g., not other kinds of widgets, like
/// [RenderObjectWidget]s). /// [RenderObjectWidget]s).
class Flexible extends ParentDataWidget { class Flexible extends ParentDataWidget<Flex> {
Flexible({ Key key, this.flex: 1, Widget child }) Flexible({ Key key, this.flex: 1, Widget child })
: super(key: key, child: child); : super(key: key, child: child);
...@@ -1226,13 +1219,6 @@ class Flexible extends ParentDataWidget { ...@@ -1226,13 +1219,6 @@ class Flexible extends ParentDataWidget {
/// according to the flex factors of the flexible children. /// according to the flex factors of the flexible children.
final int flex; final int flex;
void debugValidateAncestor(Widget ancestor) {
assert(() {
'Flexible must placed inside a Flex';
return ancestor is Flex;
});
}
void applyParentData(RenderObject renderObject) { void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is FlexParentData); assert(renderObject.parentData is FlexParentData);
final FlexParentData parentData = renderObject.parentData; final FlexParentData parentData = renderObject.parentData;
......
...@@ -446,17 +446,50 @@ abstract class _ProxyComponent extends Widget { ...@@ -446,17 +446,50 @@ abstract class _ProxyComponent extends Widget {
final Widget child; final Widget child;
} }
abstract class ParentDataWidget extends _ProxyComponent { abstract class ParentDataWidget<T extends RenderObjectWidget> extends _ProxyComponent {
const ParentDataWidget({ Key key, Widget child }) const ParentDataWidget({ Key key, Widget child })
: super(key: key, child: child); : super(key: key, child: child);
ParentDataElement createElement() => new ParentDataElement(this); ParentDataElement createElement() => new ParentDataElement(this);
/// Subclasses should override this function to ensure that they are placed /// Subclasses should override this function to return true if the given
/// inside widgets that expect them. /// ancestor is a RenderObjectWidget that wraps a RenderObject that can handle
/// the kind of ParentData widget that the ParentDataWidget subclass handles.
/// ///
/// The given ancestor is the first RenderObjectWidget ancestor of this widget. /// The default implementation uses the type argument.
void debugValidateAncestor(RenderObjectWidget ancestor); 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); void applyParentData(RenderObject renderObject);
} }
...@@ -1286,16 +1319,27 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> { ...@@ -1286,16 +1319,27 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> {
void mount(Element parent, dynamic slot) { void mount(Element parent, dynamic slot) {
assert(() { assert(() {
List<Widget> badAncestors = <Widget>[];
Element ancestor = parent; Element ancestor = parent;
while (ancestor is! RenderObjectElement) { while (ancestor != null) {
assert(ancestor != null); if (ancestor is ParentDataElement) {
assert(() { badAncestors.add(ancestor.widget);
'You cannot nest parent data widgets inside one another.'; } else if (ancestor is RenderObjectElement) {
return ancestor is! ParentDataElement; if (widget.debugIsValidAncestor(ancestor.widget))
}); break;
badAncestors.add(ancestor.widget);
}
ancestor = ancestor._parent; ancestor = ancestor._parent;
} }
_widget.debugValidateAncestor(ancestor._widget); if (ancestor != null && badAncestors.isEmpty)
return true;
debugPrint(widget.debugDescribeInvalidAncestorChain(
description: "$this",
ownershipChain: parent.debugGetOwnershipChain(10),
foundValidAncestor: ancestor != null,
badAncestors: badAncestors
));
assert('Incorrect use of ParentDataWidget. See console log for details.' == true);
return true; return true;
}); });
super.mount(parent, slot); super.mount(parent, slot);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment