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.
This commit is contained in:
parent
f0e7c51816
commit
301577a34f
@ -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(() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user