Return value when pop (#3368)
Return value when pop
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index 87ec4b5..e8c655b 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 6.5.0
+
+- Supports returning values on pop.
+
## 6.4.1
- Adds `initialExtra` to **GoRouter** to pass extra data alongside `initialRoute`.
@@ -22,6 +26,7 @@
- Adds `GoRouter.maybeOf` to get the closest `GoRouter` from the context, if there is any.
+
## 6.0.10
- Adds helpers for go_router_builder for ShellRoute support
diff --git a/packages/go_router/doc/navigation.md b/packages/go_router/doc/navigation.md
index 93d51ed..f5fa16a 100644
--- a/packages/go_router/doc/navigation.md
+++ b/packages/go_router/doc/navigation.md
@@ -68,4 +68,21 @@
);
```
+## Returning values
+Waiting for a value to be returned:
+
+```dart
+onTap: () {
+ final bool? result = await context.push<bool>('/page2');
+ if(result ?? false)...
+}
+```
+
+Returning a value:
+
+```dart
+onTap: () => context.pop(true)
+```
+
+
[Named routes]: https://pub.dev/documentation/go_router/latest/topics/Named%20routes-topic.html
diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart
index 9440266..7c4c6e3 100644
--- a/packages/go_router/lib/src/delegate.dart
+++ b/packages/go_router/lib/src/delegate.dart
@@ -82,8 +82,9 @@
return ValueKey<String>('$path-p$count');
}
- void _push(RouteMatchList matches, ValueKey<String> pageKey) {
- final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch(
+ Future<T?> _push<T extends Object?>(
+ RouteMatchList matches, ValueKey<String> pageKey) async {
+ final ImperativeRouteMatch<T> newPageKeyMatch = ImperativeRouteMatch<T>(
route: matches.last.route,
subloc: matches.last.subloc,
extra: matches.last.extra,
@@ -93,6 +94,7 @@
);
_matchList.push(newPageKeyMatch);
+ return newPageKeyMatch._future;
}
/// Pushes the given location onto the page stack.
@@ -103,12 +105,13 @@
/// * [replace] which replaces the top-most page of the page stack but treats
/// it as the same page. The page key will be reused. This will preserve the
/// state and not run any page animation.
- void push(RouteMatchList matches) {
+ Future<T?> push<T extends Object?>(RouteMatchList matches) async {
assert(matches.last.route is! ShellRoute);
final ValueKey<String> pageKey = _getNewKeyForPath(matches.fullpath);
- _push(matches, pageKey);
+ final Future<T?> future = _push(matches, pageKey);
notifyListeners();
+ return future;
}
/// Returns `true` if the active Navigator can pop.
@@ -127,6 +130,7 @@
final _NavigatorStateIterator iterator = _createNavigatorStateIterator();
while (iterator.moveNext()) {
if (iterator.current.canPop()) {
+ iterator.matchList.last.complete(result);
iterator.current.pop<T>(result);
return;
}
@@ -308,7 +312,7 @@
/// The route match that represent route pushed through [GoRouter.push].
// TODO(chunhtai): Removes this once imperative API no longer insert route match.
-class ImperativeRouteMatch extends RouteMatch {
+class ImperativeRouteMatch<T> extends RouteMatch {
/// Constructor for [ImperativeRouteMatch].
ImperativeRouteMatch({
required super.route,
@@ -317,8 +321,20 @@
required super.error,
required super.pageKey,
required this.matches,
- });
+ }) : _completer = Completer<T?>();
/// The matches that produces this route match.
final RouteMatchList matches;
+
+ /// The completer for the future returned by [GoRouter.push].
+ final Completer<T?> _completer;
+
+ @override
+ void complete([dynamic value]) {
+ _completer.complete(value as T?);
+ }
+
+ /// The future of the [RouteMatch] completer.
+ /// When the future completes, this will return the value passed to [complete].
+ Future<T?> get _future => _completer.future;
}
diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart
index 62a4d55..f4840f2 100644
--- a/packages/go_router/lib/src/match.dart
+++ b/packages/go_router/lib/src/match.dart
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'matching.dart';
@@ -61,6 +60,9 @@
throw MatcherError('Unexpected route type: $route', restLoc);
}
+ /// Called when the corresponding [Route] associated with this route match is completed.
+ void complete([Object? value]) {}
+
/// The matched route.
final RouteBase route;
diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart
index 0900588..47f5b08 100644
--- a/packages/go_router/lib/src/misc/extensions.dart
+++ b/packages/go_router/lib/src/misc/extensions.dart
@@ -44,17 +44,17 @@
/// * [replace] which replaces the top-most page of the page stack but treats
/// it as the same page. The page key will be reused. This will preserve the
/// state and not run any page animation.
- void push(String location, {Object? extra}) =>
- GoRouter.of(this).push(location, extra: extra);
+ Future<T?> push<T extends Object?>(String location, {Object? extra}) =>
+ GoRouter.of(this).push<T>(location, extra: extra);
/// Navigate to a named route onto the page stack.
- void pushNamed(
+ Future<T?> pushNamed<T extends Object?>(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra,
}) =>
- GoRouter.of(this).pushNamed(
+ GoRouter.of(this).pushNamed<T>(
name,
params: params,
queryParams: queryParams,
diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart
index e91eaf3..37c3e98 100644
--- a/packages/go_router/lib/src/parser.dart
+++ b/packages/go_router/lib/src/parser.dart
@@ -104,7 +104,7 @@
}
if (configuration.matches.last is ImperativeRouteMatch) {
configuration =
- (configuration.matches.last as ImperativeRouteMatch).matches;
+ (configuration.matches.last as ImperativeRouteMatch<Object?>).matches;
}
return RouteInformation(
location: configuration.uri.toString(),
diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart
index 2960372..b63cb99 100644
--- a/packages/go_router/lib/src/router.dart
+++ b/packages/go_router/lib/src/router.dart
@@ -159,7 +159,7 @@
routerDelegate.currentConfiguration.matches.last
is ImperativeRouteMatch) {
newLocation = (routerDelegate.currentConfiguration.matches.last
- as ImperativeRouteMatch)
+ as ImperativeRouteMatch<Object?>)
.matches
.uri
.toString();
@@ -219,32 +219,31 @@
/// * [replace] which replaces the top-most page of the page stack but treats
/// it as the same page. The page key will be reused. This will preserve the
/// state and not run any page animation.
- void push(String location, {Object? extra}) {
+ Future<T?> push<T extends Object?>(String location, {Object? extra}) async {
assert(() {
log.info('pushing $location');
return true;
}());
- _routeInformationParser
- .parseRouteInformationWithDependencies(
+ final RouteMatchList matches =
+ await _routeInformationParser.parseRouteInformationWithDependencies(
RouteInformation(location: location, state: extra),
// TODO(chunhtai): avoid accessing the context directly through global key.
// https://github.com/flutter/flutter/issues/99112
_routerDelegate.navigatorKey.currentContext!,
- )
- .then<void>((RouteMatchList matches) {
- _routerDelegate.push(matches);
- });
+ );
+
+ return _routerDelegate.push<T>(matches);
}
/// Push a named route onto the page stack w/ optional parameters, e.g.
/// `name='person', params={'fid': 'f2', 'pid': 'p1'}`
- void pushNamed(
+ Future<T?> pushNamed<T extends Object?>(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra,
}) =>
- push(
+ push<T>(
namedLocation(name, params: params, queryParams: queryParams),
extra: extra,
);
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index d4d4b29..991a4f9 100644
--- a/packages/go_router/pubspec.yaml
+++ b/packages/go_router/pubspec.yaml
@@ -1,7 +1,7 @@
name: go_router
description: A declarative router for Flutter based on Navigation 2 supporting
deep linking, data-driven routes and more
-version: 6.4.1
+version: 6.5.0
repository: https://github.com/flutter/packages/tree/main/packages/go_router
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart
index b6cc6bc..e40084a 100644
--- a/packages/go_router/test/delegate_test.dart
+++ b/packages/go_router/test/delegate_test.dart
@@ -141,7 +141,7 @@
reason: 'The last match should have been removed',
);
expect(
- (goRouter.routerDelegate.matches.last as ImperativeRouteMatch)
+ (goRouter.routerDelegate.matches.last as ImperativeRouteMatch<Object?>)
.matches
.uri
.toString(),
@@ -270,7 +270,7 @@
reason: 'The last match should have been removed',
);
expect(
- (goRouter.routerDelegate.matches.last as ImperativeRouteMatch)
+ (goRouter.routerDelegate.matches.last as ImperativeRouteMatch<Object?>)
.matches
.uri
.toString(),
@@ -393,7 +393,7 @@
reason: 'The last match should have been removed',
);
expect(
- (goRouter.routerDelegate.matches.last as ImperativeRouteMatch)
+ (goRouter.routerDelegate.matches.last as ImperativeRouteMatch<Object?>)
.matches
.uri
.toString(),
diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart
index 1733513..833c16e 100644
--- a/packages/go_router/test/go_router_test.dart
+++ b/packages/go_router/test/go_router_test.dart
@@ -2439,8 +2439,8 @@
expect(router.location, loc);
expect(matches.matches, hasLength(2));
expect(find.byType(PersonScreen), findsOneWidget);
- final ImperativeRouteMatch imperativeRouteMatch =
- matches.matches.last as ImperativeRouteMatch;
+ final ImperativeRouteMatch<Object?> imperativeRouteMatch =
+ matches.matches.last as ImperativeRouteMatch<Object?>;
expect(imperativeRouteMatch.matches.pathParameters['fid'], fid);
expect(imperativeRouteMatch.matches.pathParameters['pid'], pid);
});
@@ -2698,6 +2698,26 @@
expect(router.extra, extra);
});
+ testWidgets('calls [push] on closest GoRouter and waits for result',
+ (WidgetTester tester) async {
+ final GoRouterPushSpy router = GoRouterPushSpy(routes: routes);
+ await tester.pumpWidget(
+ MaterialApp.router(
+ routeInformationProvider: router.routeInformationProvider,
+ routeInformationParser: router.routeInformationParser,
+ routerDelegate: router.routerDelegate,
+ title: 'GoRouter Example',
+ ),
+ );
+ final String? result = await router.push<String>(
+ location,
+ extra: extra,
+ );
+ expect(result, extra);
+ expect(router.myLocation, location);
+ expect(router.extra, extra);
+ });
+
testWidgets('calls [pushNamed] on closest GoRouter',
(WidgetTester tester) async {
final GoRouterPushNamedSpy router = GoRouterPushNamedSpy(routes: routes);
@@ -2719,6 +2739,30 @@
expect(router.extra, extra);
});
+ testWidgets('calls [pushNamed] on closest GoRouter and waits for result',
+ (WidgetTester tester) async {
+ final GoRouterPushNamedSpy router = GoRouterPushNamedSpy(routes: routes);
+ await tester.pumpWidget(
+ MaterialApp.router(
+ routeInformationProvider: router.routeInformationProvider,
+ routeInformationParser: router.routeInformationParser,
+ routerDelegate: router.routerDelegate,
+ title: 'GoRouter Example',
+ ),
+ );
+ final String? result = await router.pushNamed<String>(
+ name,
+ params: params,
+ queryParams: queryParams,
+ extra: extra,
+ );
+ expect(result, extra);
+ expect(router.extra, extra);
+ expect(router.name, name);
+ expect(router.params, params);
+ expect(router.queryParams, queryParams);
+ });
+
testWidgets('calls [pop] on closest GoRouter', (WidgetTester tester) async {
final GoRouterPopSpy router = GoRouterPopSpy(routes: routes);
await tester.pumpWidget(
diff --git a/packages/go_router/test/inherited_test.dart b/packages/go_router/test/inherited_test.dart
index 9fbb915..7afa9fe 100644
--- a/packages/go_router/test/inherited_test.dart
+++ b/packages/go_router/test/inherited_test.dart
@@ -129,11 +129,12 @@
late String latestPushedName;
@override
- void pushNamed(String name,
+ Future<T?> pushNamed<T extends Object?>(String name,
{Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
Object? extra}) {
latestPushedName = name;
+ return Future<T?>.value();
}
@override
diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart
index 04d12e7..b81f4c2 100644
--- a/packages/go_router/test/test_helpers.dart
+++ b/packages/go_router/test/test_helpers.dart
@@ -95,9 +95,10 @@
Object? extra;
@override
- void push(String location, {Object? extra}) {
+ Future<T?> push<T extends Object?>(String location, {Object? extra}) {
myLocation = location;
this.extra = extra;
+ return Future<T?>.value(extra as T?);
}
}
@@ -110,7 +111,7 @@
Object? extra;
@override
- void pushNamed(
+ Future<T?> pushNamed<T extends Object?>(
String name, {
Map<String, String> params = const <String, String>{},
Map<String, dynamic> queryParams = const <String, dynamic>{},
@@ -120,6 +121,7 @@
this.params = params;
this.queryParams = queryParams;
this.extra = extra;
+ return Future<T?>.value(extra as T?);
}
}