diff --git a/packages/flutter/lib/src/widgets/navigator_pop_handler.dart b/packages/flutter/lib/src/widgets/navigator_pop_handler.dart index ea2be66955..f6d3eeb9c5 100644 --- a/packages/flutter/lib/src/widgets/navigator_pop_handler.dart +++ b/packages/flutter/lib/src/widgets/navigator_pop_handler.dart @@ -12,6 +12,9 @@ import 'pop_scope.dart'; /// Typically wraps a nested [Navigator] widget and allows it to handle system /// back gestures in the [onPop] callback. /// +/// The type parameter `` indicates the type of the [Route]'s return value, +/// which is passed to [onPopWithResult. +/// /// {@tool dartpad} /// This sample demonstrates how to use this widget to properly handle system /// back gestures when using nested [Navigator]s. @@ -33,14 +36,20 @@ import 'pop_scope.dart'; /// handle pops. /// * [NavigationNotification], which indicates whether a [Navigator] in a /// subtree can handle pops. -class NavigatorPopHandler extends StatefulWidget { +@optionalTypeArgs +class NavigatorPopHandler extends StatefulWidget { /// Creates an instance of [NavigatorPopHandler]. const NavigatorPopHandler({ super.key, + @Deprecated( + 'Use onPopWithResult instead. ' + 'This feature was deprecated after v3.26.0-0.1.pre.', + ) this.onPop, + this.onPopWithResult, this.enabled = true, required this.child, - }); + }) : assert(onPop == null || onPopWithResult == null); /// The widget to place below this in the widget tree. /// @@ -68,26 +77,43 @@ class NavigatorPopHandler extends StatefulWidget { /// /// Typically this is used to pop the [Navigator] in [child]. See the sample /// code on [NavigatorPopHandler] for a full example of this. + @Deprecated( + 'Use onPopWithResult instead. ' + 'This feature was deprecated after v3.26.0-0.1.pre.', + ) final VoidCallback? onPop; + /// Called when a handleable pop event happens. + /// + /// For example, a pop is handleable when a [Navigator] in [child] has + /// multiple routes on its stack. It's not handleable when it has only a + /// single route, and so [onPop] will not be called. + /// + /// Typically this is used to pop the [Navigator] in [child]. See the sample + /// code on [NavigatorPopHandler] for a full example of this. + /// + /// The passed `result` is the result of the popped [Route]. + final PopResultCallback? onPopWithResult; + @override - State createState() => _NavigatorPopHandlerState(); + State> createState() => _NavigatorPopHandlerState(); } -class _NavigatorPopHandlerState extends State { +class _NavigatorPopHandlerState extends State> { bool _canPop = true; @override Widget build(BuildContext context) { // When the widget subtree indicates it can handle a pop, disable popping // here, so that it can be manually handled in canPop. - return PopScope( + return PopScope( canPop: !widget.enabled || _canPop, - onPopInvokedWithResult: (bool didPop, Object? result) { + onPopInvokedWithResult: (bool didPop, T? result) { if (didPop) { return; } widget.onPop?.call(); + widget.onPopWithResult?.call(result); }, // Listen to changes in the navigation stack in the widget subtree. child: NotificationListener( @@ -108,3 +134,6 @@ class _NavigatorPopHandlerState extends State { ); } } + +/// A signature for a function that is passed the result of a [Route]. +typedef PopResultCallback = void Function(T? result); diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index 3b2560d2d6..2ca9e154f6 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -5404,6 +5404,78 @@ void main() { }); }); }); + + testWidgets('NavigatorPopHandler.onPopWithResult', (WidgetTester tester) async { + final GlobalKey nav = GlobalKey(); + final GlobalKey nestedNav = GlobalKey(); + const String result = 'i am a result'; + final List results = []; + await tester.pumpWidget( + MaterialApp( + navigatorKey: nav, + initialRoute: '/', + routes: { + '/': (BuildContext context) => _LinksPage( + title: 'Home page', + buttons: [ + TextButton( + onPressed: () { + Navigator.of(context).pushNamed('/one'); + }, + child: const Text('Go to one'), + ), + TextButton( + onPressed: () { + Navigator.of(context).pushNamed('/nested'); + }, + child: const Text('Go to nested'), + ), + ], + ), + '/one': (BuildContext context) => _LinksPage( + title: 'Page one', + buttons: [ + TextButton( + onPressed: () { + Navigator.of(context).pushNamed('/one/one'); + }, + child: const Text('Go to one/one'), + ), + ], + ), + '/nested': (BuildContext context) => _NestedNavigatorsPage( + navigatorKey: nestedNav, + onPopWithResult: (String? result) { + results.add(result); + }, + ), + }, + ), + ); + + expect(find.text('Home page'), findsOneWidget); + + await tester.tap(find.text('Go to nested')); + await tester.pumpAndSettle(); + + expect(find.text('Nested - home'), findsOneWidget); + + await tester.tap(find.text('Go to nested/one')); + await tester.pumpAndSettle(); + + expect(find.text('Nested - page one'), findsOneWidget); + expect(results, isEmpty); + + // Pop the root Navigator, despite being on a route in the nested + // Navigator. This is to trigger NavigatorPopHandler.onPopWithResult with + // a the given result. + await nav.currentState?.maybePop(result); + await tester.pumpAndSettle(); + + expect(find.text('Nested - home'), findsOneWidget); + expect(results, hasLength(1)); + expect(results.first, result); + }); } typedef AnnouncementCallBack = void Function(Route?); @@ -5783,6 +5855,7 @@ class _NestedNavigatorsPage extends StatefulWidget { const _NestedNavigatorsPage({ this.popScopePageEnabled, this.navigatorKey, + this.onPopWithResult, }); /// Whether the PopScope on the /popscope page is enabled. @@ -5792,6 +5865,8 @@ class _NestedNavigatorsPage extends StatefulWidget { final GlobalKey? navigatorKey; + final PopResultCallback? onPopWithResult; + @override State<_NestedNavigatorsPage> createState() => _NestedNavigatorsPageState(); } @@ -5808,12 +5883,13 @@ class _NestedNavigatorsPageState extends State<_NestedNavigatorsPage> { @override Widget build(BuildContext context) { final BuildContext rootContext = context; - return NavigatorPopHandler( - onPop: () { + return NavigatorPopHandler( + onPopWithResult: (String? result) { + widget.onPopWithResult?.call(result); if (widget.popScopePageEnabled == false) { return; } - _navigatorKey.currentState!.pop(); + _navigatorKey.currentState!.pop(result); }, child: Navigator( key: _navigatorKey,