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> {
// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
// updating the pressed state of an _ActionButtonParentData based on the
// incoming isPressed property.
class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoAlertActionsRenderWidget> {
class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParentData> {
const _ActionButtonParentDataWidget({
Key key,
this.isPressed,
......@@ -977,6 +977,9 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoAlertActi
targetParent.markNeedsPaint();
}
}
@override
Type get debugTypicalAncestorWidgetClass => _CupertinoAlertActionsRenderWidget;
}
// ParentData applied to individual action buttons that report whether or not
......
......@@ -1020,7 +1020,7 @@ class _PressableActionButtonState extends State<_PressableActionButton> {
// _ActionButtonParentData. _ActionButtonParentDataWidget is responsible for
// updating the pressed state of an _ActionButtonParentData based on the
// incoming [isPressed] property.
class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoDialogActionsRenderWidget> {
class _ActionButtonParentDataWidget extends ParentDataWidget<_ActionButtonParentData> {
const _ActionButtonParentDataWidget({
Key key,
this.isPressed,
......@@ -1042,6 +1042,9 @@ class _ActionButtonParentDataWidget extends ParentDataWidget<_CupertinoDialogAct
targetParent.markNeedsPaint();
}
}
@override
Type get debugTypicalAncestorWidgetClass => _CupertinoDialogActionsRenderWidget;
}
// ParentData applied to individual action buttons that report whether or not
......
......@@ -5,6 +5,7 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
import 'framework.dart';
......@@ -80,7 +81,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
handle.addListener(_handles[handle]);
if (!_keepingAlive) {
_keepingAlive = true;
final ParentDataElement<SliverWithKeepAliveWidget> childElement = _getChildElement();
final ParentDataElement<KeepAliveParentDataMixin> childElement = _getChildElement();
if (childElement != null) {
// If the child already exists, update it synchronously.
_updateParentDataOfChild(childElement);
......@@ -92,7 +93,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
if (!mounted) {
return;
}
final ParentDataElement<SliverWithKeepAliveWidget> childElement = _getChildElement();
final ParentDataElement<KeepAliveParentDataMixin> childElement = _getChildElement();
assert(childElement != null);
_updateParentDataOfChild(childElement);
});
......@@ -105,7 +106,7 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
///
/// While this widget is guaranteed to have a child, this may return null if
/// the first build of that child has not completed yet.
ParentDataElement<SliverWithKeepAliveWidget> _getChildElement() {
ParentDataElement<KeepAliveParentDataMixin> _getChildElement() {
assert(mounted);
final Element element = context as Element;
Element childElement;
......@@ -131,12 +132,12 @@ class _AutomaticKeepAliveState extends State<AutomaticKeepAlive> {
element.visitChildren((Element child) {
childElement = child;
});
assert(childElement == null || childElement is ParentDataElement<SliverWithKeepAliveWidget>);
return childElement as ParentDataElement<SliverWithKeepAliveWidget>;
assert(childElement == null || childElement is ParentDataElement<KeepAliveParentDataMixin>);
return childElement as ParentDataElement<KeepAliveParentDataMixin>;
}
void _updateParentDataOfChild(ParentDataElement<SliverWithKeepAliveWidget> childElement) {
childElement.applyWidgetOutOfTurn(build(context) as ParentDataWidget<SliverWithKeepAliveWidget>);
void _updateParentDataOfChild(ParentDataElement<KeepAliveParentDataMixin> childElement) {
childElement.applyWidgetOutOfTurn(build(context) as ParentDataWidget<KeepAliveParentDataMixin>);
}
VoidCallback _createCallback(Listenable handle) {
......
......@@ -1910,7 +1910,7 @@ class CustomSingleChildLayout extends SingleChildRenderObjectWidget {
/// The [MultiChildLayoutDelegate.hasChild],
/// [MultiChildLayoutDelegate.layoutChild], and
/// [MultiChildLayoutDelegate.positionChild] methods use these identifiers.
class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
class LayoutId extends ParentDataWidget<MultiChildLayoutParentData> {
/// Marks a child with a layout identifier.
///
/// Both the child and the id arguments must not be null.
......@@ -1940,6 +1940,9 @@ class LayoutId extends ParentDataWidget<CustomMultiChildLayout> {
}
}
@override
Type get debugTypicalAncestorWidgetClass => CustomMultiChildLayout;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......@@ -3353,7 +3356,7 @@ class IndexedStack extends Stack {
/// * [PositionedTransition], which takes a provided [Animation] to transition
/// changes in the child's position over a given duration.
/// * [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.
///
/// Only two out of the three horizontal values ([left], [right],
......@@ -3577,6 +3580,9 @@ class Positioned extends ParentDataWidget<Stack> {
}
}
@override
Type get debugTypicalAncestorWidgetClass => Stack;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......@@ -4363,7 +4369,7 @@ class Column extends Flex {
/// * [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.
/// * 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]
/// flexes.
const Flexible({
......@@ -4413,6 +4419,9 @@ class Flexible extends ParentDataWidget<Flex> {
}
}
@override
Type get debugTypicalAncestorWidgetClass => Flex;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......
......@@ -1554,7 +1554,7 @@ class _SliverOffstageElement extends SingleChildRenderObjectElement {
/// In practice, the simplest way to deal with these notifications is to mix
/// [AutomaticKeepAliveClientMixin] into one's [State]. See the documentation
/// for that mixin class for details.
class KeepAlive extends ParentDataWidget<SliverWithKeepAliveWidget> {
class KeepAlive extends ParentDataWidget<KeepAliveParentDataMixin> {
/// Marks a child as needing to remain alive.
///
/// The [child] and [keepAlive] arguments must not be null.
......@@ -1590,6 +1590,9 @@ class KeepAlive extends ParentDataWidget<SliverWithKeepAliveWidget> {
@override
bool debugCanApplyOutOfTurn() => keepAlive;
@override
Type get debugTypicalAncestorWidgetClass => SliverWithKeepAliveWidget;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......
......@@ -354,7 +354,7 @@ class _TableElement extends RenderObjectElement {
/// the [TableCell] widget to its enclosing [Table] must contain only
/// [TableRow]s, [StatelessWidget]s, or [StatefulWidget]s (not
/// 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.
const TableCell({
Key key,
......@@ -376,6 +376,9 @@ class TableCell extends ParentDataWidget<Table> {
}
}
@override
Type get debugTypicalAncestorWidgetClass => Table;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
......
......@@ -160,7 +160,7 @@ class InvalidConstraintsChildLayoutDelegate extends MultiChildLayoutDelegate {
bool shouldRelayout(MultiChildLayoutDelegate oldDelegate) => true;
}
class LayoutWithMissingId extends ParentDataWidget<CustomMultiChildLayout> {
class LayoutWithMissingId extends ParentDataWidget<MultiChildLayoutParentData> {
const LayoutWithMissingId({
Key key,
@required Widget child,
......@@ -169,6 +169,9 @@ class LayoutWithMissingId extends ParentDataWidget<CustomMultiChildLayout> {
@override
void applyParentData(RenderObject renderObject) {}
@override
Type get debugTypicalAncestorWidgetClass => CustomMultiChildLayout;
}
void main() {
......
......@@ -49,7 +49,6 @@ final TestParentData kNonPositioned = TestParentData();
void main() {
testWidgets('ParentDataWidget control test', (WidgetTester tester) async {
await tester.pumpWidget(
Stack(
textDirection: TextDirection.ltr,
......@@ -251,33 +250,39 @@ void main() {
testWidgets('ParentDataWidget conflicting data', (WidgetTester tester) async {
await tester.pumpWidget(
Stack(
Directionality(
textDirection: TextDirection.ltr,
children: const <Widget>[
Positioned(
top: 5.0,
bottom: 8.0,
child: Positioned(
top: 6.0,
left: 7.0,
child: DecoratedBox(decoration: kBoxDecorationB),
child: Stack(
textDirection: TextDirection.ltr,
children: const <Widget>[
Positioned(
top: 5.0,
bottom: 8.0,
child: Positioned(
top: 6.0,
left: 7.0,
child: DecoratedBox(decoration: kBoxDecorationB),
),
),
),
],
],
),
),
);
dynamic exception = tester.takeException();
expect(exception, isFlutterError);
expect(
exception.toString(),
equalsIgnoringHashCodes(
'Incorrect use of ParentDataWidget.\n'
'Positioned widgets must be placed directly inside Stack widgets.\n'
'Positioned(no depth, left: 7.0, top: 6.0, dirty) has a Stack ancestor, but there are other widgets between them:\n'
'- Positioned(top: 5.0, bottom: 8.0) (this is a different Positioned than the one with the problem)\n'
'These widgets cannot come between a Positioned and its Stack.\n'
'The ownership chain for the parent of the offending Positioned was:\n'
' Positioned ← Stack ← [root]'
'The following ParentDataWidgets are providing parent data to the same RenderObject:\n'
'- Positioned(left: 7.0, top: 6.0) (typically placed directly inside a Stack widget)\n'
'- Positioned(top: 5.0, bottom: 8.0) (typically placed directly inside a Stack widget)\n'
'However, a RenderObject can only receive parent data from at most one ParentDataWidget.\n'
'Usually, this indicates that at least one of the offending ParentDataWidgets listed '
'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,15 +291,18 @@ void main() {
checkTree(tester, <TestParentData>[]);
await tester.pumpWidget(
Container(
child: Row(
children: const <Widget>[
Positioned(
top: 6.0,
left: 7.0,
child: DecoratedBox(decoration: kBoxDecorationB),
),
],
Directionality(
textDirection: TextDirection.ltr,
child: Container(
child: Row(
children: const <Widget>[
Positioned(
top: 6.0,
left: 7.0,
child: DecoratedBox(decoration: kBoxDecorationB),
),
],
),
),
),
);
......@@ -304,10 +312,14 @@ void main() {
exception.toString(),
equalsIgnoringHashCodes(
'Incorrect use of ParentDataWidget.\n'
'Positioned widgets must be placed inside Stack widgets.\n'
'Positioned(no depth, left: 7.0, top: 6.0, dirty) has no Stack ancestor at all.\n'
'The ownership chain for the parent of the offending Positioned was:\n'
' Row ← Container ← [root]'
'The ParentDataWidget Positioned(left: 7.0, top: 6.0) wants to apply ParentData of type '
'StackParentData to a RenderObject, which has been set up to accept ParentData of '
'incompatible type FlexParentData.\n'
'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,17 +389,20 @@ void main() {
});
testWidgets('Parent data invalid ancestor', (WidgetTester tester) async {
await tester.pumpWidget(Row(
children: <Widget>[
Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
Expanded(
child: Container(),
),
],
),
],
await tester.pumpWidget(Directionality(
textDirection: TextDirection.ltr,
child: Row(
children: <Widget>[
Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
Expanded(
child: Container(),
),
],
),
],
),
));
final dynamic exception = tester.takeException();
......@@ -396,13 +411,107 @@ void main() {
exception.toString(),
equalsIgnoringHashCodes(
'Incorrect use of ParentDataWidget.\n'
'Expanded widgets must be placed directly inside Flex widgets.\n'
'Expanded(no depth, flex: 1, dirty) has a Flex ancestor, but there are other widgets between them:\n'
'- Stack(alignment: AlignmentDirectional.topStart, textDirection: ltr, fit: loose, overflow: clip)\n'
'These widgets cannot come between a Expanded and its Flex.\n'
'The ownership chain for the parent of the offending Expanded was:\n'
' Stack ← Row ← [root]'
'The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type '
'FlexParentData to a RenderObject, which has been set up to accept ParentData of '
'incompatible type StackParentData.\n'
'Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. '
'Typically, Expanded widgets are placed directly inside Flex widgets.\n'
'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