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 {
}
/// Metadata for identifying children in a [CustomMultiChildLayout].
class LayoutId extends ParentDataWidget {
class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
LayoutId({
Key key,
Widget child,
......@@ -445,13 +445,6 @@ class LayoutId extends ParentDataWidget {
/// An object representing the identity of this child.
final Object id;
void debugValidateAncestor(Widget ancestor) {
assert(() {
'LayoutId must placed inside a CustomMultiChildLayout';
return ancestor is CustomMultiChildLayout;
});
}
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is MultiChildLayoutParentData);
final MultiChildLayoutParentData parentData = renderObject.parentData;
......@@ -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.
///
/// For details about the stack layout algorithm, see [RenderStack]. To control
/// the position of child widgets, see the [Positioned] widget.
class Stack extends MultiChildRenderObjectWidget {
class Stack extends StackRenderObjectWidgetBase {
Stack(List<Widget> children, {
Key key,
this.alignment: const FractionalOffset(0.0, 0.0)
......@@ -988,7 +988,7 @@ class Stack extends MultiChildRenderObjectWidget {
}
/// A [Stack] that shows a single child at once.
class IndexedStack extends MultiChildRenderObjectWidget {
class IndexedStack extends StackRenderObjectWidgetBase {
IndexedStack(List<Widget> children, {
Key key,
this.alignment: const FractionalOffset(0.0, 0.0),
......@@ -1017,7 +1017,7 @@ class IndexedStack extends MultiChildRenderObjectWidget {
/// 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
/// kinds of widgets, like [RenderObjectWidget]s).
class Positioned extends ParentDataWidget {
class Positioned extends ParentDataWidget<StackRenderObjectWidgetBase> {
Positioned({
Key key,
Widget child,
......@@ -1066,13 +1066,6 @@ class Positioned extends ParentDataWidget {
/// Ignored if both top and bottom are non-null.
final double height;
void debugValidateAncestor(Widget ancestor) {
assert(() {
'Positioned must placed inside a Stack';
return ancestor is Stack;
});
}
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is StackParentData);
final StackParentData parentData = renderObject.parentData;
......@@ -1214,7 +1207,7 @@ class Column extends Flex {
/// path from this widget to its enclosing [Flex], [Row], or [Column] must
/// contain only components (e.g., not other kinds of widgets, like
/// [RenderObjectWidget]s).
class Flexible extends ParentDataWidget {
class Flexible extends ParentDataWidget<Flex> {
Flexible({ Key key, this.flex: 1, Widget child })
: super(key: key, child: child);
......@@ -1226,13 +1219,6 @@ class Flexible extends ParentDataWidget {
/// according to the flex factors of the flexible children.
final int flex;
void debugValidateAncestor(Widget ancestor) {
assert(() {
'Flexible must placed inside a Flex';
return ancestor is Flex;
});
}
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is FlexParentData);
final FlexParentData parentData = renderObject.parentData;
......
......@@ -446,17 +446,50 @@ abstract class _ProxyComponent extends Widget {
final Widget child;
}
abstract class ParentDataWidget extends _ProxyComponent {
abstract class ParentDataWidget<T extends RenderObjectWidget> extends _ProxyComponent {
const ParentDataWidget({ Key key, Widget child })
: super(key: key, child: child);
ParentDataElement createElement() => new ParentDataElement(this);
/// Subclasses should override this function to ensure that they are placed
/// inside widgets that expect them.
/// 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 given ancestor is the first RenderObjectWidget ancestor of this widget.
void debugValidateAncestor(RenderObjectWidget ancestor);
/// 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);
}
......@@ -1286,16 +1319,27 @@ class ParentDataElement extends _ProxyElement<ParentDataWidget> {
void mount(Element parent, dynamic slot) {
assert(() {
List<Widget> badAncestors = <Widget>[];
Element ancestor = parent;
while (ancestor is! RenderObjectElement) {
assert(ancestor != null);
assert(() {
'You cannot nest parent data widgets inside one another.';
return ancestor is! ParentDataElement;
});
while (ancestor != null) {
if (ancestor is ParentDataElement) {
badAncestors.add(ancestor.widget);
} else if (ancestor is RenderObjectElement) {
if (widget.debugIsValidAncestor(ancestor.widget))
break;
badAncestors.add(ancestor.widget);
}
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;
});
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