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:
parent
f9c130abf0
commit
ec50578982
@ -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.
|
||||
|
@ -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({
|
||||
|
Loading…
x
Reference in New Issue
Block a user