NavigatorPopHandler.onPopWithResult (#155618)

NavigatorPopHandler now includes the return value from Route. Recently some navigation infrastructure was updated to support passing through these return values, but NavigatorPopHandler was missed until now.
This commit is contained in:
Justin McCandless 2024-10-08 08:19:29 -07:00 committed by GitHub
parent fc865ed9e7
commit 22635e19c1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 114 additions and 9 deletions

View File

@ -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 `<T>` 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<T> 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<T>? onPopWithResult;
@override
State<NavigatorPopHandler> createState() => _NavigatorPopHandlerState();
State<NavigatorPopHandler<T>> createState() => _NavigatorPopHandlerState<T>();
}
class _NavigatorPopHandlerState extends State<NavigatorPopHandler> {
class _NavigatorPopHandlerState<T> extends State<NavigatorPopHandler<T>> {
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<Object?>(
return PopScope<T>(
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<NavigationNotification>(
@ -108,3 +134,6 @@ class _NavigatorPopHandlerState extends State<NavigatorPopHandler> {
);
}
}
/// A signature for a function that is passed the result of a [Route].
typedef PopResultCallback<T> = void Function(T? result);

View File

@ -5404,6 +5404,78 @@ void main() {
});
});
});
testWidgets('NavigatorPopHandler.onPopWithResult', (WidgetTester tester) async {
final GlobalKey<NavigatorState> nav = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> nestedNav = GlobalKey<NavigatorState>();
const String result = 'i am a result';
final List<String?> results = <String>[];
await tester.pumpWidget(
MaterialApp(
navigatorKey: nav,
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) => _LinksPage(
title: 'Home page',
buttons: <Widget>[
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: <Widget>[
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<dynamic>?);
@ -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<NavigatorState>? navigatorKey;
final PopResultCallback<String>? 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<String>(
onPopWithResult: (String? result) {
widget.onPopWithResult?.call(result);
if (widget.popScopePageEnabled == false) {
return;
}
_navigatorKey.currentState!.pop();
_navigatorKey.currentState!.pop(result);
},
child: Navigator(
key: _navigatorKey,