Commit 6a5964d8 authored by Michael Goderbauer's avatar Michael Goderbauer Committed by Flutter GitHub Bot

Make ParentDataWidget usable with different ancestor RenderObjectWidget types (#48541)

parent 8d438b03
...@@ -955,7 +955,7 @@ class _PressableActionButtonState extends State<_PressableActionButton> { ...@@ -955,7 +955,7 @@ class _PressableActionButtonState extends State<_PressableActionButton> {
// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for // _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
// updating the pressed state of an _ActionButtonParentData based on the // updating the pressed state of an _ActionButtonParentData based on the
// incoming isPressed property. // incoming isPressed property.
class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoAlertActionsRenderWidget> { class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParentData> {
const _ActionButtonParentDataWidget({ const _ActionButtonParentDataWidget({
Key key, Key key,
this.isPressed, this.isPressed,
...@@ -977,6 +977,9 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoAlertActi ...@@ -977,6 +977,9 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoAlertActi
targetParent.markNeedsPaint(); targetParent.markNeedsPaint();
} }
} }
@override
Type get debugTypicalAncestorWidgetClass => _CupertinoAlertActionsRenderWidget;
} }
// ParentData applied to individual action buttons that report whether or not // ParentData applied to individual action buttons that report whether or not
......
...@@ -1020,7 +1020,7 @@ class _PressableActionButtonState extends State<_PressableActionButton> { ...@@ -1020,7 +1020,7 @@ class _PressableActionButtonState extends State<_PressableActionButton> {
// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for // _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
// updating the pressed state of an _ActionButtonParentData based on the // updating the pressed state of an _ActionButtonParentData based on the
// incoming [isPressed] property. // incoming [isPressed] property.
class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoDialogActionsRenderWidget> { class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParentData> {
const _ActionButtonParentDataWidget({ const _ActionButtonParentDataWidget({
Key key, Key key,
this.isPressed, this.isPressed,
...@@ -1042,6 +1042,9 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoDialogAct ...@@ -1042,6 +1042,9 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoDialogAct
targetParent.markNeedsPaint(); targetParent.markNeedsPaint();
} }
} }
@override
Type get debugTypicalAncestorWidgetClass => _CupertinoDialogActionsRenderWidget;
} }
// ParentData applied to individual action buttons that report whether or not // ParentData applied to individual action buttons that report whether or not
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'framework.dart'; import 'framework.dart';
...@@ -80,7 +81,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -80,7 +81,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
handle.addListener(_handles[handle]); handle.addListener(_handles[handle]);
if (!_keepingAlive) { if (!_keepingAlive) {
_keepingAlive = true; _keepingAlive = true;
final ParentDataElement<SliverWithKeepAliveWidget> childElement = _getChildElement(); final ParentDataElement<KeepAliveParentDataMixin> childElement = _getChildElement();
if (childElement != null) { if (childElement != null) {
// If the child already exists, update it synchronously. // If the child already exists, update it synchronously.
_updateParentDataOfChild(childElement); _updateParentDataOfChild(childElement);
...@@ -92,7 +93,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -92,7 +93,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
if (!mounted) { if (!mounted) {
return; return;
} }
final ParentDataElement<SliverWithKeepAliveWidget> childElement = _getChildElement(); final ParentDataElement<KeepAliveParentDataMixin> childElement = _getChildElement();
assert(childElement != null); assert(childElement != null);
_updateParentDataOfChild(childElement); _updateParentDataOfChild(childElement);
}); });
...@@ -105,7 +106,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -105,7 +106,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
/// ///
/// While this widget is guaranteed to have a child, this may return null if /// While this widget is guaranteed to have a child, this may return null if
/// the first build of that child has not completed yet. /// the first build of that child has not completed yet.
ParentDataElement<SliverWithKeepAliveWidget> _getChildElement() { ParentDataElement<KeepAliveParentDataMixin> _getChildElement() {
assert(mounted); assert(mounted);
final Element element = context as Element; final Element element = context as Element;
Element childElement; Element childElement;
...@@ -131,12 +132,12 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> { ...@@ -131,12 +132,12 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
element.visitChildren((Element child) { element.visitChildren((Element child) {
childElement = child; childElement = child;
}); });
assert(childElement == null || childElement is ParentDataElement<SliverWithKeepAliveWidget>); assert(childElement == null || childElement is ParentDataElement<KeepAliveParentDataMixin>);
return childElement as ParentDataElement<SliverWithKeepAliveWidget>; return childElement as ParentDataElement<KeepAliveParentDataMixin>;
} }
void _updateParentDataOfChild(ParentDataElement<SliverWithKeepAliveWidget> childElement) { void _updateParentDataOfChild(ParentDataElement<KeepAliveParentDataMixin> childElement) {
childElement.applyWidgetOutOfTurn(build(context) as ParentDataWidget<SliverWithKeepAliveWidget>); childElement.applyWidgetOutOfTurn(build(context) as ParentDataWidget<KeepAliveParentDataMixin>);
} }
VoidCallback _createCallback(Listenable handle) { VoidCallback _createCallback(Listenable handle) {
......
...@@ -1910,7 +1910,7 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget { ...@@ -1910,7 +1910,7 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
/// The [MultiChildLayoutDelegate.hasChild], /// The [MultiChildLayoutDelegate.hasChild],
/// [MultiChildLayoutDelegate.layoutChild], and /// [MultiChildLayoutDelegate.layoutChild], and
/// [MultiChildLayoutDelegate.positionChild] methods use these identifiers. /// [MultiChildLayoutDelegate.positionChild] methods use these identifiers.
class LayoutId extends ParentDataWidget<CustomMultiChildLayout> { class LayoutId extends ParentDataWidget<MultiChildLayoutParentData> {
/// Marks a child with a layout identifier. /// Marks a child with a layout identifier.
/// ///
/// Both the child and the id arguments must not be null. /// Both the child and the id arguments must not be null.
...@@ -1940,6 +1940,9 @@ class LayoutId extends ParentDataWidget<CustomMultiChildLayout> { ...@@ -1940,6 +1940,9 @@ class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
} }
} }
@override
Type get debugTypicalAncestorWidgetClass => CustomMultiChildLayout;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
...@@ -3353,7 +3356,7 @@ class IndexedStack extends Stack { ...@@ -3353,7 +3356,7 @@ class IndexedStack extends Stack {
/// * [PositionedTransition], which takes a provided [Animation] to transition /// * [PositionedTransition], which takes a provided [Animation] to transition
/// changes in the child's position over a given duration. /// changes in the child's position over a given duration.
/// * [PositionedDirectional], which adapts to the ambient [Directionality]. /// * [PositionedDirectional], which adapts to the ambient [Directionality].
class Positioned extends ParentDataWidget<Stack> { class Positioned extends ParentDataWidget<StackParentData> {
/// Creates a widget that controls where a child of a [Stack] is positioned. /// Creates a widget that controls where a child of a [Stack] is positioned.
/// ///
/// Only two out of the three horizontal values ([left], [right], /// Only two out of the three horizontal values ([left], [right],
...@@ -3577,6 +3580,9 @@ class Positioned extends ParentDataWidget<Stack> { ...@@ -3577,6 +3580,9 @@ class Positioned extends ParentDataWidget<Stack> {
} }
} }
@override
Type get debugTypicalAncestorWidgetClass => Stack;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
...@@ -4363,7 +4369,7 @@ class Column extends Flex { ...@@ -4363,7 +4369,7 @@ class Column extends Flex {
/// * [Expanded], which forces the child to expand to fill the available space. /// * [Expanded], which forces the child to expand to fill the available space.
/// * [Spacer], a widget that takes up space proportional to it's flex value. /// * [Spacer], a widget that takes up space proportional to it's flex value.
/// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/). /// * The [catalog of layout widgets](https://flutter.dev/widgets/layout/).
class Flexible extends ParentDataWidget<Flex> { class Flexible extends ParentDataWidget<FlexParentData> {
/// Creates a widget that controls how a child of a [Row], [Column], or [Flex] /// Creates a widget that controls how a child of a [Row], [Column], or [Flex]
/// flexes. /// flexes.
const Flexible({ const Flexible({
...@@ -4413,6 +4419,9 @@ class Flexible extends ParentDataWidget<Flex> { ...@@ -4413,6 +4419,9 @@ class Flexible extends ParentDataWidget<Flex> {
} }
} }
@override
Type get debugTypicalAncestorWidgetClass => Flex;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
......
...@@ -1419,9 +1419,8 @@ abstract class ProxyWidget extends Widget { ...@@ -1419,9 +1419,8 @@ abstract class ProxyWidget extends Widget {
/// [RenderObjectWidget]s with more than one child. For example, [Stack] uses /// [RenderObjectWidget]s with more than one child. For example, [Stack] uses
/// the [Positioned] parent data widget to position each child. /// the [Positioned] parent data widget to position each child.
/// ///
/// A [ParentDataWidget] is specific to a particular kind of [RenderObject], and /// A [ParentDataWidget] is specific to a particular kind of [ParentData]. That
/// thus also to a particular [RenderObjectWidget] class. That class is `T`, the /// class is `T`, the [ParentData] type argument.
/// [ParentDataWidget] type argument.
/// ///
/// {@tool snippet} /// {@tool snippet}
/// ///
...@@ -1429,7 +1428,7 @@ abstract class ProxyWidget extends Widget { ...@@ -1429,7 +1428,7 @@ abstract class ProxyWidget extends Widget {
/// `FrogJar` widget's children by specifying a [Size] for each one. /// `FrogJar` widget's children by specifying a [Size] for each one.
/// ///
/// ```dart /// ```dart
/// class FrogSize extends ParentDataWidget<FrogJar> { /// class FrogSize extends ParentDataWidget<FrogJarParentData> {
/// FrogSize({ /// FrogSize({
/// Key key, /// Key key,
/// @required this.size, /// @required this.size,
...@@ -1449,6 +1448,9 @@ abstract class ProxyWidget extends Widget { ...@@ -1449,6 +1448,9 @@ abstract class ProxyWidget extends Widget {
/// targetParent.markNeedsLayout(); /// targetParent.markNeedsLayout();
/// } /// }
/// } /// }
///
/// @override
/// Type get debugTypicalAncestorWidgetClass => FrogJar;
/// } /// }
/// ``` /// ```
/// {@end-tool} /// {@end-tool}
...@@ -1458,12 +1460,12 @@ abstract class ProxyWidget extends Widget { ...@@ -1458,12 +1460,12 @@ abstract class ProxyWidget extends Widget {
/// * [RenderObject], the superclass for layout algorithms. /// * [RenderObject], the superclass for layout algorithms.
/// * [RenderObject.parentData], the slot that this class configures. /// * [RenderObject.parentData], the slot that this class configures.
/// * [ParentData], the superclass of the data that will be placed in /// * [ParentData], the superclass of the data that will be placed in
/// [RenderObject.parentData] slots. /// [RenderObject.parentData] slots. The `T` type parameter for
/// [ParentDataWidget] is a [ParentData].
/// * [RenderObjectWidget], the class for widgets that wrap [RenderObject]s. /// * [RenderObjectWidget], the class for widgets that wrap [RenderObject]s.
/// The `T` type parameter for [ParentDataWidget] is a [RenderObjectWidget].
/// * [StatefulWidget] and [State], for widgets that can build differently /// * [StatefulWidget] and [State], for widgets that can build differently
/// several times over their lifetime. /// several times over their lifetime.
abstract class ParentDataWidget<T extends RenderObjectWidget> extends ProxyWidget { abstract class ParentDataWidget<T extends ParentData> extends ProxyWidget {
/// Abstract const constructor. This constructor enables subclasses to provide /// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions. /// const constructors so that they can be used in const expressions.
const ParentDataWidget({ Key key, Widget child }) const ParentDataWidget({ Key key, Widget child })
...@@ -1472,45 +1474,61 @@ abstract class ParentDataWidget<T extends RenderObjectWidget> extends ProxyWidge ...@@ -1472,45 +1474,61 @@ abstract class ParentDataWidget<T extends RenderObjectWidget> extends ProxyWidge
@override @override
ParentDataElement<T> createElement() => ParentDataElement<T>(this); ParentDataElement<T> createElement() => ParentDataElement<T>(this);
/// Subclasses should override this method to return true if the given /// Checks if this widget can apply its parent data to the provided
/// ancestor is a RenderObjectWidget that wraps a RenderObject that can handle /// `renderObject`.
/// the kind of ParentData widget that the ParentDataWidget subclass handles.
/// ///
/// The default implementation uses the type argument. /// The [RenderObject.parentData] of the provided `renderObject` is
bool debugIsValidAncestor(RenderObjectWidget ancestor) { /// 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 != dynamic);
assert(T != RenderObjectWidget); assert(T != ParentData);
return ancestor is T; return renderObject.parentData is T;
} }
/// Subclasses should override this to describe the requirements for using the /// The [RenderObjectWidget] that is typically used to set up the [ParentData]
/// ParentDataWidget subclass. It is called when debugIsValidAncestor() /// that [applyParentData] will write to.
/// returned false for an ancestor, or when there are extraneous ///
/// [ParentDataWidget]s in the ancestor chain. /// This is only used in error messages to tell users what widget typically
Iterable<DiagnosticsNode> debugDescribeInvalidAncestorChain({ String description, DiagnosticsNode ownershipChain, bool foundValidAncestor, Iterable<Widget> badAncestors }) sync* { /// wraps this ParentDataWidget.
Type get debugTypicalAncestorWidgetClass;
Iterable<DiagnosticsNode> _debugDescribeIncorrectParentDataType({
@required ParentData parentData,
RenderObjectWidget parentDataCreator,
DiagnosticsNode ownershipChain,
}) sync* {
assert(T != dynamic); assert(T != dynamic);
assert(T != RenderObjectWidget); assert(T != ParentData);
if (!foundValidAncestor) { assert(debugTypicalAncestorWidgetClass != null);
final String description = 'The ParentDataWidget $this wants to apply ParentData of type $T to a RenderObject';
if (parentData == null) {
yield ErrorDescription( yield ErrorDescription(
'$runtimeType widgets must be placed inside $T widgets.\n' '$description, which has not been set up to receive any ParentData.'
'$description has no $T ancestor at all.'
); );
} else { } else {
assert(badAncestors.isNotEmpty);
yield ErrorDescription( yield ErrorDescription(
'$runtimeType widgets must be placed directly inside $T widgets.\n' '$description, which has been set up to accept ParentData of incompatible type ${parentData.runtimeType}.'
'$description has a $T ancestor, but there are other widgets between them:'
); );
for (final Widget ancestor in badAncestors) {
if (ancestor.runtimeType == runtimeType) {
yield ErrorDescription('- $ancestor (this is a different $runtimeType than the one with the problem)');
} else {
yield ErrorDescription('- $ancestor');
} }
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.'
);
} }
yield ErrorDescription('These widgets cannot come between a $runtimeType and its $T.'); if (ownershipChain != null) {
yield ErrorDescription(
'The ownership chain for the RenderObject that received the incompatible parent data was:\n $ownershipChain'
);
} }
yield ErrorDescription('The ownership chain for the parent of the offending $runtimeType was:\n $ownershipChain');
} }
/// Write the data from this widget into the given render object's parent data. /// Write the data from this widget into the given render object's parent data.
...@@ -2684,6 +2702,9 @@ class BuildOwner { ...@@ -2684,6 +2702,9 @@ class BuildOwner {
return true; return true;
}()); }());
} catch (e, stack) { } 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); _debugReportException(ErrorSummary('while finalizing the widget tree'), e, stack);
} finally { } finally {
Timeline.finishSync(); Timeline.finishSync();
...@@ -4657,52 +4678,19 @@ abstract class ProxyElement extends ComponentElement { ...@@ -4657,52 +4678,19 @@ abstract class ProxyElement extends ComponentElement {
} }
/// An [Element] that uses a [ParentDataWidget] as its configuration. /// An [Element] that uses a [ParentDataWidget] as its configuration.
class ParentDataElement<T extends RenderObjectWidget> extends ProxyElement { class ParentDataElement<T extends ParentData> extends ProxyElement {
/// Creates an element that uses the given widget as its configuration. /// Creates an element that uses the given widget as its configuration.
ParentDataElement(ParentDataWidget<T> widget) : super(widget); ParentDataElement(ParentDataWidget<T> widget) : super(widget);
@override @override
ParentDataWidget<T> get widget => super.widget as ParentDataWidget<T>; ParentDataWidget<T> get widget => super.widget as ParentDataWidget<T>;
@override
void mount(Element parent, dynamic newSlot) {
assert(() {
final 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;
// TODO(jacobr): switch to describing the invalid parent chain in terms
// of DiagnosticsNode objects when possible.
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Incorrect use of ParentDataWidget.'),
// TODO(jacobr): fix this constructor call to use FlutterErrorBuilder.
...widget.debugDescribeInvalidAncestorChain(
description: '$this',
ownershipChain: ErrorDescription(parent.debugGetCreatorChain(10)),
foundValidAncestor: ancestor != null,
badAncestors: badAncestors,
),
]);
}());
super.mount(parent, newSlot);
}
void _applyParentData(ParentDataWidget<T> widget) { void _applyParentData(ParentDataWidget<T> widget) {
void applyParentDataToChild(Element child) { void applyParentDataToChild(Element child) {
if (child is RenderObjectElement) { if (child is RenderObjectElement) {
child._updateParentData(widget); child._updateParentData(widget);
} else { } else {
assert(child is! ParentDataElement<RenderObjectWidget>); assert(child is! ParentDataElement<ParentData>);
child.visitChildren(applyParentDataToChild); child.visitChildren(applyParentDataToChild);
} }
} }
...@@ -5116,14 +5104,48 @@ abstract class RenderObjectElement extends Element { ...@@ -5116,14 +5104,48 @@ abstract class RenderObjectElement extends Element {
return ancestor as RenderObjectElement; return ancestor as RenderObjectElement;
} }
ParentDataElement<RenderObjectWidget> _findAncestorParentDataElement() { ParentDataElement<ParentData> _findAncestorParentDataElement() {
Element ancestor = _parent; Element ancestor = _parent;
ParentDataElement<ParentData> result;
while (ancestor != null && ancestor is! RenderObjectElement) { while (ancestor != null && ancestor is! RenderObjectElement) {
if (ancestor is ParentDataElement<RenderObjectWidget>) if (ancestor is ParentDataElement<ParentData>) {
return ancestor; result = ancestor as ParentDataElement<ParentData>;
break;
}
ancestor = ancestor._parent; ancestor = ancestor._parent;
} }
return null; assert(() {
if (result == null || ancestor == null) {
return true;
}
// Check that no other ParentDataWidgets want to provide parent data.
final List<ParentDataElement<ParentData>> badAncestors = <ParentDataElement<ParentData>>[];
ancestor = ancestor._parent;
while (ancestor != null && ancestor is! RenderObjectElement) {
if (ancestor is ParentDataElement<ParentData>) {
badAncestors.add(ancestor as ParentDataElement<ParentData>);
}
ancestor = ancestor._parent;
}
if (badAncestors.isNotEmpty) {
badAncestors.insert(0, result);
try {
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Incorrect use of ParentDataWidget.'),
ErrorDescription('The following ParentDataWidgets are providing parent data to the same RenderObject:'),
for (final ParentDataElement<ParentData> ancestor in badAncestors)
ErrorDescription('- ${ancestor.widget} (typically placed directly inside a ${ancestor.widget.debugTypicalAncestorWidgetClass} widget)'),
ErrorDescription('However, a RenderObject can only receive parent data from at most one ParentDataWidget.'),
ErrorHint('Usually, this indicates that at least one of the offending ParentDataWidgets listed above is not placed directly inside a compatible ancestor widget.'),
ErrorDescription('The ownership chain for the RenderObject that received the parent data was:\n ${debugGetCreatorChain(10)}'),
]);
} on FlutterError catch (e) {
_debugReportException(ErrorSummary('while looking for parent data.'), e, e.stackTrace);
}
}
return true;
}());
return result;
} }
@override @override
...@@ -5355,8 +5377,31 @@ abstract class RenderObjectElement extends Element { ...@@ -5355,8 +5377,31 @@ abstract class RenderObjectElement extends Element {
widget.didUnmountRenderObject(renderObject); widget.didUnmountRenderObject(renderObject);
} }
void _updateParentData(ParentDataWidget<RenderObjectWidget> parentData) { void _updateParentData(ParentDataWidget<ParentData> parentDataWidget) {
parentData.applyParentData(renderObject); bool applyParentData = true;
assert(() {
try {
if (!parentDataWidget.debugIsValidRenderObject(renderObject)) {
applyParentData = false;
throw FlutterError.fromParts(<DiagnosticsNode>[
ErrorSummary('Incorrect use of ParentDataWidget.'),
...parentDataWidget._debugDescribeIncorrectParentDataType(
parentData: renderObject.parentData,
parentDataCreator: _ancestorRenderObjectElement.widget,
ownershipChain: ErrorDescription(debugGetCreatorChain(10)),
),
]);
}
} on FlutterError catch (e) {
// 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 applying parent data.'), e, e.stackTrace);
}
return true;
}());
if (applyParentData)
parentDataWidget.applyParentData(renderObject);
} }
@override @override
...@@ -5373,7 +5418,7 @@ abstract class RenderObjectElement extends Element { ...@@ -5373,7 +5418,7 @@ abstract class RenderObjectElement extends Element {
_slot = newSlot; _slot = newSlot;
_ancestorRenderObjectElement = _findAncestorRenderObjectElement(); _ancestorRenderObjectElement = _findAncestorRenderObjectElement();
_ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot); _ancestorRenderObjectElement?.insertChildRenderObject(renderObject, newSlot);
final ParentDataElement<RenderObjectWidget> parentDataElement = _findAncestorParentDataElement(); final ParentDataElement<ParentData> parentDataElement = _findAncestorParentDataElement();
if (parentDataElement != null) if (parentDataElement != null)
_updateParentData(parentDataElement.widget); _updateParentData(parentDataElement.widget);
} }
......
...@@ -1554,7 +1554,7 @@ class _SliverOffstageElement extends SingleChildRenderObjectElement { ...@@ -1554,7 +1554,7 @@ class _SliverOffstageElement extends SingleChildRenderObjectElement {
/// In practice, the simplest way to deal with these notifications is to mix /// In practice, the simplest way to deal with these notifications is to mix
/// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation /// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation
/// for that mixin class for details. /// for that mixin class for details.
class KeepAlive extends ParentDataWidget<SliverWithKeepAliveWidget> { class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> {
/// Marks a child as needing to remain alive. /// Marks a child as needing to remain alive.
/// ///
/// The [child] and [keepAlive] arguments must not be null. /// The [child] and [keepAlive] arguments must not be null.
...@@ -1590,6 +1590,9 @@ class KeepAlive extends ParentDataWidget<SliverWithKeepAliveWidget> { ...@@ -1590,6 +1590,9 @@ class KeepAlive extends ParentDataWidget<SliverWithKeepAliveWidget> {
@override @override
bool debugCanApplyOutOfTurn() => keepAlive; bool debugCanApplyOutOfTurn() => keepAlive;
@override
Type get debugTypicalAncestorWidgetClass => SliverWithKeepAliveWidget;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
......
...@@ -354,7 +354,7 @@ class _TableElement extends RenderObjectElement { ...@@ -354,7 +354,7 @@ class _TableElement extends RenderObjectElement {
/// the [TableCell] widget to its enclosing [Table] must contain only /// the [TableCell] widget to its enclosing [Table] must contain only
/// [TableRow]s, [StatelessWidget]s, or [StatefulWidget]s (not /// [TableRow]s, [StatelessWidget]s, or [StatefulWidget]s (not
/// other kinds of widgets, like [RenderObjectWidget]s). /// other kinds of widgets, like [RenderObjectWidget]s).
class TableCell extends ParentDataWidget<Table> { class TableCell extends ParentDataWidget<TableCellParentData> {
/// Creates a widget that controls how a child of a [Table] is aligned. /// Creates a widget that controls how a child of a [Table] is aligned.
const TableCell({ const TableCell({
Key key, Key key,
...@@ -376,6 +376,9 @@ class TableCell extends ParentDataWidget<Table> { ...@@ -376,6 +376,9 @@ class TableCell extends ParentDataWidget<Table> {
} }
} }
@override
Type get debugTypicalAncestorWidgetClass => Table;
@override @override
void debugFillProperties(DiagnosticPropertiesBuilder properties) { void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties); super.debugFillProperties(properties);
......
...@@ -160,7 +160,7 @@ class InvalidConstraintsChildLayoutDelegate extends MultiChildLayoutDelegate { ...@@ -160,7 +160,7 @@ class InvalidConstraintsChildLayoutDelegate extends MultiChildLayoutDelegate {
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => true; bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => true;
} }
class LayoutWithMissingId extends ParentDataWidget<CustomMultiChildLayout> { class LayoutWithMissingId extends ParentDataWidget<MultiChildLayoutParentData> {
const LayoutWithMissingId({ const LayoutWithMissingId({
Key key, Key key,
@required Widget child, @required Widget child,
...@@ -169,6 +169,9 @@ class LayoutWithMissingId extends ParentDataWidget<CustomMultiChildLayout> { ...@@ -169,6 +169,9 @@ class LayoutWithMissingId extends ParentDataWidget<CustomMultiChildLayout> {
@override @override
void applyParentData(RenderObject renderObject) {} void applyParentData(RenderObject renderObject) {}
@override
Type get debugTypicalAncestorWidgetClass => CustomMultiChildLayout;
} }
void main() { void main() {
......
...@@ -49,7 +49,6 @@ final TestParentData kNonPositioned = TestParentData(); ...@@ -49,7 +49,6 @@ final TestParentData kNonPositioned = TestParentData();
void main() { void main() {
testWidgets('ParentDataWidget control test', (WidgetTester tester) async { testWidgets('ParentDataWidget control test', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
Stack( Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -251,7 +250,9 @@ void main() { ...@@ -251,7 +250,9 @@ void main() {
testWidgets('ParentDataWidget conflicting data', (WidgetTester tester) async { testWidgets('ParentDataWidget conflicting data', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
Stack( Directionality(
textDirection: TextDirection.ltr,
child: Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
children: const <Widget>[ children: const <Widget>[
Positioned( Positioned(
...@@ -265,19 +266,23 @@ void main() { ...@@ -265,19 +266,23 @@ void main() {
), ),
], ],
), ),
),
); );
dynamic exception = tester.takeException(); dynamic exception = tester.takeException();
expect(exception, isFlutterError); expect(exception, isFlutterError);
expect( expect(
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'Incorrect use of ParentDataWidget.\n' 'Incorrect use of ParentDataWidget.\n'
'Positioned widgets must be placed directly inside Stack widgets.\n' 'The following ParentDataWidgets are providing parent data to the same RenderObject:\n'
'Positioned(no depth, left: 7.0, top: 6.0, dirty) has a Stack ancestor, but there are other widgets between them:\n' '- Positioned(left: 7.0, top: 6.0) (typically placed directly inside a Stack widget)\n'
'- Positioned(top: 5.0, bottom: 8.0) (this is a different Positioned than the one with the problem)\n' '- Positioned(top: 5.0, bottom: 8.0) (typically placed directly inside a Stack widget)\n'
'These widgets cannot come between a Positioned and its Stack.\n' 'However, a RenderObject can only receive parent data from at most one ParentDataWidget.\n'
'The ownership chain for the parent of the offending Positioned was:\n' 'Usually, this indicates that at least one of the offending ParentDataWidgets listed '
' Positioned ← Stack ← [root]' 'above is not placed directly inside a compatible ancestor widget.\n'
'The ownership chain for the RenderObject that received the parent data was:\n'
' DecoratedBox ← Positioned ← Positioned ← Stack ← Directionality ← [root]'
), ),
); );
...@@ -286,7 +291,9 @@ void main() { ...@@ -286,7 +291,9 @@ void main() {
checkTree(tester, <TestParentData>[]); checkTree(tester, <TestParentData>[]);
await tester.pumpWidget( await tester.pumpWidget(
Container( Directionality(
textDirection: TextDirection.ltr,
child: Container(
child: Row( child: Row(
children: const <Widget>[ children: const <Widget>[
Positioned( Positioned(
...@@ -297,6 +304,7 @@ void main() { ...@@ -297,6 +304,7 @@ void main() {
], ],
), ),
), ),
),
); );
exception = tester.takeException(); exception = tester.takeException();
expect(exception, isFlutterError); expect(exception, isFlutterError);
...@@ -304,10 +312,14 @@ void main() { ...@@ -304,10 +312,14 @@ void main() {
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'Incorrect use of ParentDataWidget.\n' 'Incorrect use of ParentDataWidget.\n'
'Positioned widgets must be placed inside Stack widgets.\n' 'The ParentDataWidget Positioned(left: 7.0, top: 6.0) wants to apply ParentData of type '
'Positioned(no depth, left: 7.0, top: 6.0, dirty) has no Stack ancestor at all.\n' 'StackParentData to a RenderObject, which has been set up to accept ParentData of '
'The ownership chain for the parent of the offending Positioned was:\n' 'incompatible type FlexParentData.\n'
' Row ← Container ← [root]' 'Usually, this means that the Positioned widget has the wrong ancestor RenderObjectWidget. '
'Typically, Positioned widgets are placed directly inside Stack widgets.\n'
'The offending Positioned is currently placed inside a Row widget.\n'
'The ownership chain for the RenderObject that received the incompatible parent data was:\n'
' DecoratedBox ← Positioned ← Row ← Container ← Directionality ← [root]'
), ),
); );
...@@ -377,7 +389,9 @@ void main() { ...@@ -377,7 +389,9 @@ void main() {
}); });
testWidgets('Parent data invalid ancestor', (WidgetTester tester) async { testWidgets('Parent data invalid ancestor', (WidgetTester tester) async {
await tester.pumpWidget(Row( await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Row(
children: <Widget>[ children: <Widget>[
Stack( Stack(
textDirection: TextDirection.ltr, textDirection: TextDirection.ltr,
...@@ -388,6 +402,7 @@ void main() { ...@@ -388,6 +402,7 @@ void main() {
], ],
), ),
], ],
),
)); ));
final dynamic exception = tester.takeException(); final dynamic exception = tester.takeException();
...@@ -396,13 +411,107 @@ void main() { ...@@ -396,13 +411,107 @@ void main() {
exception.toString(), exception.toString(),
equalsIgnoringHashCodes( equalsIgnoringHashCodes(
'Incorrect use of ParentDataWidget.\n' 'Incorrect use of ParentDataWidget.\n'
'Expanded widgets must be placed directly inside Flex widgets.\n' 'The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type '
'Expanded(no depth, flex: 1, dirty) has a Flex ancestor, but there are other widgets between them:\n' 'FlexParentData to a RenderObject, which has been set up to accept ParentData of '
'- Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, overflow: clip)\n' 'incompatible type StackParentData.\n'
'These widgets cannot come between a Expanded and its Flex.\n' 'Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. '
'The ownership chain for the parent of the offending Expanded was:\n' 'Typically, Expanded widgets are placed directly inside Flex widgets.\n'
' Stack ← Row ← [root]' 'The offending Expanded is currently placed inside a Stack widget.\n'
'The ownership chain for the RenderObject that received the incompatible parent data was:\n'
' LimitedBox ← Container ← Expanded ← Stack ← Row ← Directionality ← [root]'
), ),
); );
}); });
testWidgets('ParentDataWidget can be used with different ancestor RenderObjectWidgets', (WidgetTester tester) async {
await tester.pumpWidget(
OneAncestorWidget(
child: Container(),
),
);
DummyParentData parentData = tester.renderObject(find.byType(Container)).parentData as DummyParentData;
expect(parentData.string, isNull);
await tester.pumpWidget(
OneAncestorWidget(
child: TestParentDataWidget(
string: 'Foo',
child: Container(),
),
),
);
parentData = tester.renderObject(find.byType(Container)).parentData as DummyParentData;
expect(parentData.string, 'Foo');
await tester.pumpWidget(
AnotherAncestorWidget(
child: TestParentDataWidget(
string: 'Bar',
child: Container(),
),
),
);
parentData = tester.renderObject(find.byType(Container)).parentData as DummyParentData;
expect(parentData.string, 'Bar');
});
}
class TestParentDataWidget extends ParentDataWidget<DummyParentData> {
const TestParentDataWidget({
Key key,
this.string,
Widget child,
}) : super(key: key, child: child);
final String string;
@override
void applyParentData(RenderObject renderObject) {
assert(renderObject.parentData is DummyParentData);
final DummyParentData parentData = renderObject.parentData as DummyParentData;
parentData.string = string;
}
@override
Type get debugTypicalAncestorWidgetClass => OneAncestorWidget;
}
class DummyParentData extends ParentData {
String string;
}
class OneAncestorWidget extends SingleChildRenderObjectWidget {
const OneAncestorWidget({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderOne createRenderObject(BuildContext context) => RenderOne();
}
class AnotherAncestorWidget extends SingleChildRenderObjectWidget {
const AnotherAncestorWidget({
Key key,
Widget child,
}) : super(key: key, child: child);
@override
RenderAnother createRenderObject(BuildContext context) => RenderAnother();
}
class RenderOne extends RenderProxyBox {
@override
void setupParentData(RenderBox child) {
if (child.parentData is! DummyParentData)
child.parentData = DummyParentData();
}
}
class RenderAnother extends RenderProxyBox {
@override
void setupParentData(RenderBox child) {
if (child.parentData is! DummyParentData)
child.parentData = DummyParentData();
}
} }
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