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);
......
...@@ -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