Skip to content

Commit ec50578

Browse files
authored
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].*
1 parent f9c130a commit ec50578

2 files changed

Lines changed: 65 additions & 0 deletions

File tree

packages/flutter/lib/src/material/menu_anchor.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,9 @@ class MenuAnchor extends StatefulWidget {
282282
///
283283
/// If not supplied, then the [MenuAnchor] will be the size that its parent
284284
/// allocates for it.
285+
///
286+
/// If provided, the builder will be called each time the menu is opened or
287+
/// closed.
285288
final MenuAnchorChildBuilder? builder;
286289

287290
/// The optional child to be passed to the [builder].
@@ -576,6 +579,11 @@ class _MenuAnchorState extends State<MenuAnchor> {
576579
}
577580

578581
widget.onOpen?.call();
582+
if (mounted && SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks) {
583+
setState(() {
584+
// Mark dirty to ensure UI updates
585+
});
586+
}
579587
}
580588

581589
/// Close the menu.

packages/flutter/test/material/menu_anchor_test.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4737,6 +4737,63 @@ void main() {
47374737
final MenuController controller = MenuController();
47384738
expect(controller.isOpen, false);
47394739
});
4740+
4741+
// Regression test for https://github.com/flutter/flutter/issues/157606.
4742+
testWidgets('MenuAnchor updates isOpen state correctly', (WidgetTester tester) async {
4743+
bool isOpen = false;
4744+
int openCount = 0;
4745+
int closeCount = 0;
4746+
4747+
await tester.pumpWidget(
4748+
MaterialApp(
4749+
home: Scaffold(
4750+
body: Center(
4751+
child: MenuAnchor(
4752+
menuChildren: const <Widget>[
4753+
MenuItemButton(child: Text('menu item')),
4754+
],
4755+
builder: (BuildContext context, MenuController controller, Widget? child) {
4756+
isOpen = controller.isOpen;
4757+
return FilledButton(
4758+
onPressed: () {
4759+
if (controller.isOpen) {
4760+
controller.close();
4761+
} else {
4762+
controller.open();
4763+
}
4764+
},
4765+
child: Text(isOpen ? 'close' : 'open'),
4766+
);
4767+
},
4768+
onOpen: () => openCount++,
4769+
onClose: () => closeCount++,
4770+
),
4771+
),
4772+
),
4773+
)
4774+
);
4775+
4776+
expect(find.text('open'), findsOneWidget);
4777+
expect(isOpen, false);
4778+
expect(openCount, 0);
4779+
expect(closeCount, 0);
4780+
4781+
await tester.tap(find.byType(FilledButton));
4782+
await tester.pump();
4783+
4784+
expect(find.text('close'), findsOneWidget);
4785+
expect(isOpen, true);
4786+
expect(openCount, 1);
4787+
expect(closeCount, 0);
4788+
4789+
await tester.tap(find.byType(FilledButton));
4790+
await tester.pump();
4791+
4792+
expect(find.text('open'), findsOneWidget);
4793+
expect(isOpen, false);
4794+
expect(openCount, 1);
4795+
expect(closeCount, 1);
4796+
});
47404797
}
47414798

47424799
List<Widget> createTestMenus({

0 commit comments

Comments
 (0)