From 983b2f6bc83285140dd0b7e29420079d739235bc Mon Sep 17 00:00:00 2001 From: Tirth Date: Fri, 10 Nov 2023 21:44:16 +0530 Subject: [PATCH] Adds `useRootNavigator` property to `PopupMenuButton` widget. (#137453) Adds `useRootNavigator` property to `PopupMenuButton` widget. Fixes #95425 --- .../flutter/lib/src/material/popup_menu.dart | 8 ++ .../test/material/popup_menu_test.dart | 87 +++++++++++++++++++ 2 files changed, 95 insertions(+) diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index 107e8734d7..364ef591fb 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -1128,6 +1128,7 @@ class PopupMenuButton extends StatefulWidget { this.constraints, this.position, this.clipBehavior = Clip.none, + this.useRootNavigator = false, }) : assert( !(child != null && icon != null), 'You can only pass [child] or [icon], not both.', @@ -1292,6 +1293,12 @@ class PopupMenuButton extends StatefulWidget { /// Defaults to [Clip.none]. final Clip clipBehavior; + /// Used to determine whether to push the menu to the [Navigator] furthest + /// from or nearest to the given `context`. + /// + /// Defaults to false. + final bool useRootNavigator; + @override PopupMenuButtonState createState() => PopupMenuButtonState(); } @@ -1348,6 +1355,7 @@ class PopupMenuButtonState extends State> { color: widget.color ?? popupMenuTheme.color, constraints: widget.constraints, clipBehavior: widget.clipBehavior, + useRootNavigator: widget.useRootNavigator, ) .then((T? newValue) { if (!mounted) { diff --git a/packages/flutter/test/material/popup_menu_test.dart b/packages/flutter/test/material/popup_menu_test.dart index 5dec66dd1d..fed0ae19cf 100644 --- a/packages/flutter/test/material/popup_menu_test.dart +++ b/packages/flutter/test/material/popup_menu_test.dart @@ -3785,6 +3785,93 @@ void main() { await tester.pumpAndSettle(); expect(count, 1); }); + + testWidgetsWithLeakTracking('PopupMenuButton uses root navigator if useRootNavigator is true', (WidgetTester tester) async { + final MenuObserver rootObserver = MenuObserver(); + final MenuObserver nestedObserver = MenuObserver(); + + await tester.pumpWidget( + MaterialApp( + navigatorObservers: [rootObserver], + home: Navigator( + observers: [nestedObserver], + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + builder: (BuildContext context) { + return Material( + child: PopupMenuButton( + useRootNavigator: true, + child: const Text('button'), + itemBuilder: (BuildContext context) { + return >[ + const CheckedPopupMenuItem( + value: 'item1', + child: Text('item 1'), + ), + const CheckedPopupMenuItem( + value: 'item2', + child: Text('item 2'), + ), + ]; + }, + ), + ); + }, + ); + }, + ), + ), + ); + + // Open the dialog. + await tester.tap(find.text('button')); + + expect(rootObserver.menuCount, 1); + expect(nestedObserver.menuCount, 0); + }); + + testWidgetsWithLeakTracking('PopupMenuButton does not use root navigator if useRootNavigator is false', (WidgetTester tester) async { + final MenuObserver rootObserver = MenuObserver(); + final MenuObserver nestedObserver = MenuObserver(); + + await tester.pumpWidget( + MaterialApp( + navigatorObservers: [rootObserver], + home: Navigator( + observers: [nestedObserver], + onGenerateRoute: (RouteSettings settings) { + return MaterialPageRoute( + builder: (BuildContext context) { + return Material( + child: PopupMenuButton( + child: const Text('button'), + itemBuilder: (BuildContext context) { + return >[ + const CheckedPopupMenuItem( + value: 'item1', + child: Text('item 1'), + ), + const CheckedPopupMenuItem( + value: 'item2', + child: Text('item 2'), + ), + ]; + }, + ), + ); + }, + ); + }, + ), + ), + ); + + // Open the dialog. + await tester.tap(find.text('button')); + + expect(rootObserver.menuCount, 0); + expect(nestedObserver.menuCount, 1); + }); } class TestApp extends StatelessWidget {