Unverified Commit 761747bf authored by Łukasz Gawron's avatar Łukasz Gawron Committed by GitHub

[CP] Fix TwoDimensionalViewport's keep alive child not always removed (when no...

[CP] Fix TwoDimensionalViewport's keep alive child not always removed (when no longer should be kept alive) (#149639)

Cherry pick request of https://github.com/flutter/flutter/pull/148298 to stable.

Fixes problems in 2D viewport when using keep alive widgets (affects users using Material Ink components).
parent e3f15a5b
...@@ -1647,6 +1647,10 @@ abstract class RenderTwoDimensionalViewport extends RenderBox implements RenderA ...@@ -1647,6 +1647,10 @@ abstract class RenderTwoDimensionalViewport extends RenderBox implements RenderA
_children.remove(slot); _children.remove(slot);
} }
assert(_debugTrackOrphans(noLongerOrphan: child)); assert(_debugTrackOrphans(noLongerOrphan: child));
if (_keepAliveBucket[childParentData.vicinity] == child) {
_keepAliveBucket.remove(childParentData.vicinity);
}
assert(_keepAliveBucket[childParentData.vicinity] != child);
dropChild(child); dropChild(child);
return; return;
} }
......
...@@ -510,3 +510,39 @@ class TestParentDataWidget extends ParentDataWidget<TestExtendedParentData> { ...@@ -510,3 +510,39 @@ class TestParentDataWidget extends ParentDataWidget<TestExtendedParentData> {
@override @override
Type get debugTypicalAncestorWidgetClass => SimpleBuilderTableViewport; Type get debugTypicalAncestorWidgetClass => SimpleBuilderTableViewport;
} }
class KeepAliveOnlyWhenHovered extends StatefulWidget {
const KeepAliveOnlyWhenHovered({ required this.child, super.key });
final Widget child;
@override
KeepAliveOnlyWhenHoveredState createState() => KeepAliveOnlyWhenHoveredState();
}
class KeepAliveOnlyWhenHoveredState extends State<KeepAliveOnlyWhenHovered> with AutomaticKeepAliveClientMixin {
bool _hovered = false;
@override
bool get wantKeepAlive => _hovered;
@override
Widget build(BuildContext context) {
super.build(context);
return MouseRegion(
onEnter: (_) {
setState(() {
_hovered = true;
updateKeepAlive();
});
},
onExit: (_) {
setState(() {
_hovered = false;
updateKeepAlive();
});
},
child: widget.child,
);
}
}
...@@ -731,6 +731,66 @@ void main() { ...@@ -731,6 +731,66 @@ void main() {
); );
}); });
testWidgets('Ensure KeepAlive widget is not held onto when it no longer should be kept alive offscreen', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/138977
final UniqueKey checkBoxKey = UniqueKey();
final Widget originCell = KeepAliveOnlyWhenHovered(
key: checkBoxKey,
child: const SizedBox.square(dimension: 200),
);
const Widget otherCell = SizedBox.square(dimension: 200, child: Placeholder());
final ScrollController verticalController = ScrollController();
addTearDown(verticalController.dispose);
final TwoDimensionalChildListDelegate listDelegate = TwoDimensionalChildListDelegate(
children: <List<Widget>>[
<Widget>[originCell, otherCell, otherCell, otherCell, otherCell],
<Widget>[otherCell, otherCell, otherCell, otherCell, otherCell],
<Widget>[otherCell, otherCell, otherCell, otherCell, otherCell],
<Widget>[otherCell, otherCell, otherCell, otherCell, otherCell],
<Widget>[otherCell, otherCell, otherCell, otherCell, otherCell],
],
);
addTearDown(listDelegate.dispose);
await tester.pumpWidget(simpleListTest(
delegate: listDelegate,
verticalDetails: ScrollableDetails.vertical(controller: verticalController),
));
await tester.pumpAndSettle();
expect(find.byKey(checkBoxKey), findsOneWidget);
// Scroll away, should not be kept alive (disposed).
verticalController.jumpTo(verticalController.position.maxScrollExtent);
await tester.pump();
expect(find.byKey(checkBoxKey), findsNothing);
// Bring back into view
verticalController.jumpTo(0.0);
await tester.pump();
expect(find.byKey(checkBoxKey), findsOneWidget);
// Hover over widget to make it keep alive.
final TestGesture gesture = await tester.createGesture(
kind: PointerDeviceKind.mouse,
);
await gesture.addPointer(location: Offset.zero);
addTearDown(gesture.removePointer);
await tester.pump();
await gesture.moveTo(tester.getCenter(find.byKey(checkBoxKey)));
await tester.pump();
// Scroll away, should be kept alive still.
verticalController.jumpTo(verticalController.position.maxScrollExtent);
await tester.pump();
expect(find.byKey(checkBoxKey), findsOneWidget);
// Move the pointer outside the widget bounds to trigger exit event
// and remove it from keep alive bucket.
await gesture.moveTo(const Offset(300, 300));
await tester.pump();
expect(find.byKey(checkBoxKey), findsNothing);
});
testWidgets('list delegate will not add automatic keep alives', (WidgetTester tester) async { testWidgets('list delegate will not add automatic keep alives', (WidgetTester tester) async {
final UniqueKey checkBoxKey = UniqueKey(); final UniqueKey checkBoxKey = UniqueKey();
final Widget originCell = SizedBox.square( final Widget originCell = SizedBox.square(
......
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