Fix menu anchor state handling (#157612)

This commit refactors the `_MenuAnchorState` class in `menu_anchor.dart` to include a check for the mounted state and the scheduler phase before calling `setState()`. This ensures that UI updates are only performed when the widget is still mounted and not during the persistent callbacks phase. Additionally, a new test case is added in `menu_anchor_test.dart` to verify that the `isOpen` state of the `MenuAnchor` widget is updated correctly when the button is pressed.

Fix: #157606

*Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.*

*List which issues are fixed by this PR. You must list at least one issue. An issue is not required if the PR fixes something trivial like a typo.*

*If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].*
This commit is contained in:
YeungKC 2024-10-30 18:42:00 +09:00 committed by GitHub
parent f9c130abf0
commit ec50578982
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 65 additions and 0 deletions

View File

@ -282,6 +282,9 @@ class MenuAnchor extends StatefulWidget {
///
/// If not supplied, then the [MenuAnchor] will be the size that its parent
/// allocates for it.
///
/// If provided, the builder will be called each time the menu is opened or
/// closed.
final MenuAnchorChildBuilder? builder;
/// The optional child to be passed to the [builder].
@ -576,6 +579,11 @@ class _MenuAnchorState extends State<MenuAnchor> {
}
widget.onOpen?.call();
if (mounted && SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks) {
setState(() {
// Mark dirty to ensure UI updates
});
}
}
/// Close the menu.

View File

@ -4737,6 +4737,63 @@ void main() {
final MenuController controller = MenuController();
expect(controller.isOpen, false);
});
// Regression test for https://github.com/flutter/flutter/issues/157606.
testWidgets('MenuAnchor updates isOpen state correctly', (WidgetTester tester) async {
bool isOpen = false;
int openCount = 0;
int closeCount = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: MenuAnchor(
menuChildren: const <Widget>[
MenuItemButton(child: Text('menu item')),
],
builder: (BuildContext context, MenuController controller, Widget? child) {
isOpen = controller.isOpen;
return FilledButton(
onPressed: () {
if (controller.isOpen) {
controller.close();
} else {
controller.open();
}
},
child: Text(isOpen ? 'close' : 'open'),
);
},
onOpen: () => openCount++,
onClose: () => closeCount++,
),
),
),
)
);
expect(find.text('open'), findsOneWidget);
expect(isOpen, false);
expect(openCount, 0);
expect(closeCount, 0);
await tester.tap(find.byType(FilledButton));
await tester.pump();
expect(find.text('close'), findsOneWidget);
expect(isOpen, true);
expect(openCount, 1);
expect(closeCount, 0);
await tester.tap(find.byType(FilledButton));
await tester.pump();
expect(find.text('open'), findsOneWidget);
expect(isOpen, false);
expect(openCount, 1);
expect(closeCount, 1);
});
}
List<Widget> createTestMenus({