diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 04b7865106d..45f4f240b47 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -328,8 +328,10 @@ class _MenuAnchorState extends State { assert(_debugMenuInfo('Disposing of $this')); if (_isOpen) { _close(inDispose: true); - _parent?._removeChild(this); } + + _parent?._removeChild(this); + _parent = null; _anchorChildren.clear(); _menuController._detach(this); _internalMenuController = null; diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index 85676da1834..5063d286160 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -8,6 +8,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:leak_tracker/leak_tracker.dart'; import '../widgets/semantics_tester.dart'; @@ -3803,6 +3804,51 @@ void main() { ..rect(color: overlayColor.withOpacity(0.1)), ); }); + + testWidgets('Garbage collector destroys child _MenuAnchorState after parent is closed', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/149584 + await tester.pumpWidget( + MaterialApp( + home: MenuAnchor( + controller: controller, + menuChildren: const [ + SubmenuButton( + menuChildren: [], + child: Text(''), + ) + ], + ), + ), + ); + + controller.open(); + await tester.pump(); + + final WeakReference state = + WeakReference( + tester.firstState>( + find.byType(SubmenuButton), + ), + ); + expect(state.target, isNotNull); + + controller.close(); + await tester.pump(); + + controller.open(); + await tester.pump(); + + controller.close(); + await tester.pump(); + + // Garbage collect. 1 should be enough, but 3 prevents flaky tests. + await tester.runAsync(() async { + await forceGC(fullGcCycles: 3); + }); + + expect(state.target, isNull); + }, skip: kIsWeb // [intended] ForceGC does not work in web and in release mode. See https://api.flutter.dev/flutter/package-leak_tracker_leak_tracker/forceGC.html + ); } List createTestMenus({