mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Fix TwoDimensionalViewport's keep alive child not always removed (when no longer should be kept alive) (#148298)
- Fixes a child not removed from `_keepAliveBucket` when widget is no longer kept alive offscreen. Bug was triggering assert in performLayout. - Adds test to cover the case from bug report Fixes #138977
This commit is contained in:
parent
6861b77c2b
commit
a53b78ddfb
@ -1649,6 +1649,10 @@ abstract class RenderTwoDimensionalViewport extends RenderBox implements RenderA
|
||||
_children.remove(slot);
|
||||
}
|
||||
assert(_debugTrackOrphans(noLongerOrphan: child));
|
||||
if (_keepAliveBucket[childParentData.vicinity] == child) {
|
||||
_keepAliveBucket.remove(childParentData.vicinity);
|
||||
}
|
||||
assert(_keepAliveBucket[childParentData.vicinity] != child);
|
||||
dropChild(child);
|
||||
return;
|
||||
}
|
||||
|
@ -511,3 +511,39 @@ class TestParentDataWidget extends ParentDataWidget<TestExtendedParentData> {
|
||||
@override
|
||||
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() {
|
||||
);
|
||||
});
|
||||
|
||||
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 {
|
||||
final UniqueKey checkBoxKey = UniqueKey();
|
||||
final Widget originCell = SizedBox.square(
|
||||
|
Loading…
Reference in New Issue
Block a user