[material/menu_anchor.dart] Remove _MenuAnchorState from parent when disposed. (#149586)

`_MenuAnchorState` now removes itself from its parent regardless of whether `_MenuAnchorState._isOpen` is true.

Before, _MenuAnchorState would only detach itself when `_MenuAnchorState._isOpen == true`. This led to _MenuAnchorState being retained until an anchor's parent was disposed.

Before:

https://github.com/flutter/flutter/assets/59215665/48f1617a-a3d0-49ab-a767-f41e3a30ea41

After:

https://github.com/flutter/flutter/assets/59215665/31312bb6-49ec-4083-b8de-e9ae2a42a8b3

Fixes https://github.com/flutter/flutter/issues/149584

I was a bit unsure what the best way of testing this change would be, and wanted feedback from the Flutter team. Is there a way for us to view at the instance count of `_MenuAnchorState`, or expose `_MenuAnchorState._anchorChildren.length` for testing?

- [] I added new tests to check the change I am making, or this PR is [test-exempt].
This commit is contained in:
davidhicks980 2024-06-20 14:10:05 -04:00 committed by GitHub
parent b3dc24ccc6
commit c2c183387e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 49 additions and 1 deletions

View File

@ -328,8 +328,10 @@ class _MenuAnchorState extends State<MenuAnchor> {
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;

View File

@ -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 <Widget>[
SubmenuButton(
menuChildren: <Widget>[],
child: Text(''),
)
],
),
),
);
controller.open();
await tester.pump();
final WeakReference<State> state =
WeakReference<State>(
tester.firstState<State<SubmenuButton>>(
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<void>(() 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<Widget> createTestMenus({