Unverified Commit 301577a3 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Fixing a memory leak in About box/dialog overlays (#130842)

## Description

Fix three memory leaks detected by `about_test.dart`, but were really in the `Route` and `OverlayEntry` classes.

## Related Issues
 - Fixes https://github.com/flutter/flutter/issues/130354

## Tests
 - Updates about_test.dart to not ignore the leaks anymore.
parent f0e7c518
......@@ -2861,11 +2861,11 @@ class _RouteEntry extends RouteTransitionRecord {
final _RestorationInformation? restorationInformation;
final bool pageBased;
static Route<dynamic> notAnnounced = _NotAnnounced();
static final Route<dynamic> notAnnounced = _NotAnnounced();
_RouteLifecycle currentState;
Route<dynamic>? lastAnnouncedPreviousRoute = notAnnounced; // last argument to Route.didChangePrevious
Route<dynamic> lastAnnouncedPoppedNextRoute = notAnnounced; // last argument to Route.didPopNext
WeakReference<Route<dynamic>> lastAnnouncedPoppedNextRoute = WeakReference<Route<dynamic>>(notAnnounced); // last argument to Route.didPopNext
Route<dynamic>? lastAnnouncedNextRoute = notAnnounced; // last argument to Route.didChangeNext
/// Restoration ID to be used for the encapsulating route when restoration is
......@@ -2954,7 +2954,7 @@ class _RouteEntry extends RouteTransitionRecord {
void handleDidPopNext(Route<dynamic> poppedRoute) {
route.didPopNext(poppedRoute);
lastAnnouncedPoppedNextRoute = poppedRoute;
lastAnnouncedPoppedNextRoute = WeakReference<Route<dynamic>>(poppedRoute);
}
/// Process the to-be-popped route.
......@@ -3153,7 +3153,7 @@ class _RouteEntry extends RouteTransitionRecord {
// already announced this change by calling didPopNext.
return !(
nextRoute == null &&
lastAnnouncedPoppedNextRoute == lastAnnouncedNextRoute
lastAnnouncedPoppedNextRoute.target == lastAnnouncedNextRoute
);
}
......
......@@ -135,20 +135,20 @@ class OverlayEntry implements Listenable {
/// Whether the [OverlayEntry] is currently mounted in the widget tree.
///
/// The [OverlayEntry] notifies its listeners when this value changes.
bool get mounted => _overlayEntryStateNotifier.value != null;
bool get mounted => _overlayEntryStateNotifier?.value != null;
/// The currently mounted `_OverlayEntryWidgetState` built using this [OverlayEntry].
final ValueNotifier<_OverlayEntryWidgetState?> _overlayEntryStateNotifier = ValueNotifier<_OverlayEntryWidgetState?>(null);
ValueNotifier<_OverlayEntryWidgetState?>? _overlayEntryStateNotifier = ValueNotifier<_OverlayEntryWidgetState?>(null);
@override
void addListener(VoidCallback listener) {
assert(!_disposedByOwner);
_overlayEntryStateNotifier.addListener(listener);
_overlayEntryStateNotifier?.addListener(listener);
}
@override
void removeListener(VoidCallback listener) {
_overlayEntryStateNotifier.removeListener(listener);
_overlayEntryStateNotifier?.removeListener(listener);
}
OverlayState? _overlay;
......@@ -194,7 +194,8 @@ class OverlayEntry implements Listenable {
void _didUnmount() {
assert(!mounted);
if (_disposedByOwner) {
_overlayEntryStateNotifier.dispose();
_overlayEntryStateNotifier?.dispose();
_overlayEntryStateNotifier = null;
}
}
......@@ -217,7 +218,11 @@ class OverlayEntry implements Listenable {
assert(_overlay == null, 'An OverlayEntry must first be removed from the Overlay before dispose is called.');
_disposedByOwner = true;
if (!mounted) {
_overlayEntryStateNotifier.dispose();
// If we're still mounted when disposed, then this will be disposed in
// _didUnmount, to allow notifications to occur until the entry is
// unmounted.
_overlayEntryStateNotifier?.dispose();
_overlayEntryStateNotifier = null;
}
}
......@@ -315,7 +320,7 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
@override
void initState() {
super.initState();
widget.entry._overlayEntryStateNotifier.value = this;
widget.entry._overlayEntryStateNotifier!.value = this;
_theater = context.findAncestorRenderObjectOfType<_RenderTheater>()!;
assert(_sortedTheaterSiblings == null);
}
......@@ -335,7 +340,7 @@ class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> {
@override
void dispose() {
widget.entry._overlayEntryStateNotifier.value = null;
widget.entry._overlayEntryStateNotifier?.value = null;
widget.entry._didUnmount();
_sortedTheaterSiblings = null;
super.dispose();
......@@ -917,9 +922,9 @@ class _TheaterParentData extends StackParentData {
// _overlayStateMounted is set to null in _OverlayEntryWidgetState's dispose
// method. This property is only accessed during layout, paint and hit-test so
// the `value!` should be safe.
Iterator<RenderBox>? get paintOrderIterator => overlayEntry?._overlayEntryStateNotifier.value!._paintOrderIterable.iterator;
Iterator<RenderBox>? get hitTestOrderIterator => overlayEntry?._overlayEntryStateNotifier.value!._hitTestOrderIterable.iterator;
void visitChildrenOfOverlayEntry(RenderObjectVisitor visitor) => overlayEntry?._overlayEntryStateNotifier.value!._paintOrderIterable.forEach(visitor);
Iterator<RenderBox>? get paintOrderIterator => overlayEntry?._overlayEntryStateNotifier?.value!._paintOrderIterable.iterator;
Iterator<RenderBox>? get hitTestOrderIterator => overlayEntry?._overlayEntryStateNotifier?.value!._hitTestOrderIterable.iterator;
void visitChildrenOfOverlayEntry(RenderObjectVisitor visitor) => overlayEntry?._overlayEntryStateNotifier?.value!._paintOrderIterable.forEach(visitor);
}
class _RenderTheater extends RenderBox with ContainerRenderObjectMixin<RenderBox, StackParentData>, _RenderTheaterMixin {
......
......@@ -11,6 +11,7 @@ import 'package:leak_tracker_flutter_testing/leak_tracker_flutter_testing.dart';
class TestResult {
bool dragStarted = false;
bool dragUpdate = false;
bool dragEnd = false;
}
class NestedScrollableCase extends StatelessWidget {
......@@ -77,7 +78,9 @@ class NestedDraggableCase extends StatelessWidget {
onDragUpdate: (DragUpdateDetails details){
testResult.dragUpdate = true;
},
onDragEnd: (_) {},
onDragEnd: (_) {
testResult.dragEnd = true;
},
),
);
},
......@@ -134,5 +137,6 @@ void main() {
expect(result.dragStarted, true);
expect(result.dragUpdate, true);
expect(result.dragEnd, true);
});
}
......@@ -282,15 +282,7 @@ void main() {
await tester.tap(find.text('Another package'));
await tester.pumpAndSettle();
expect(find.text('Another license'), findsOneWidget);
},
leakTrackingTestConfig: const LeakTrackingTestConfig(
// TODO(polina-c): remove after fixing
// https://github.com/flutter/flutter/issues/130354
notGCedAllowList: <String, int?>{
'ValueNotifier<_OverlayEntryWidgetState?>': 2,
'ValueNotifier<String?>': 1,
},
));
});
testWidgetsWithLeakTracking('LicensePage control test with all properties', (WidgetTester tester) async {
const FlutterLogo logo = FlutterLogo();
......@@ -366,15 +358,7 @@ void main() {
await tester.tap(find.text('Another package'));
await tester.pumpAndSettle();
expect(find.text('Another license'), findsOneWidget);
},
leakTrackingTestConfig: const LeakTrackingTestConfig(
// TODO(polina-c): remove after fixing
// https://github.com/flutter/flutter/issues/130354
notGCedAllowList: <String, int?>{
'ValueNotifier<_OverlayEntryWidgetState?>':2,
'ValueNotifier<String?>': 1,
},
));
});
testWidgetsWithLeakTracking('Material2 - _PackageLicensePage title style without AppBarTheme', (WidgetTester tester) async {
LicenseRegistry.addLicense(() {
......
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