diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 87ec4b591ce..e8c655bf8df 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 93d51ed97dd..f5fa16ac4ba 100644 --- a/packages/go_router/doc/navigation.md +++ b/packages/go_router/doc/navigation.md @@ -68,4 +68,21 @@ Navigator.of(context).push( ); ``` +## Returning values +Waiting for a value to be returned: + +```dart +onTap: () { + final bool? result = await context.push('/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 94402661afc..7c4c6e32401 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -82,8 +82,9 @@ class GoRouterDelegate extends RouterDelegate return ValueKey('$path-p$count'); } - void _push(RouteMatchList matches, ValueKey pageKey) { - final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch( + Future _push( + RouteMatchList matches, ValueKey pageKey) async { + final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch( route: matches.last.route, subloc: matches.last.subloc, extra: matches.last.extra, @@ -93,6 +94,7 @@ class GoRouterDelegate extends RouterDelegate ); _matchList.push(newPageKeyMatch); + return newPageKeyMatch._future; } /// Pushes the given location onto the page stack. @@ -103,12 +105,13 @@ class GoRouterDelegate extends RouterDelegate /// * [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 push(RouteMatchList matches) async { assert(matches.last.route is! ShellRoute); final ValueKey pageKey = _getNewKeyForPath(matches.fullpath); - _push(matches, pageKey); + final Future future = _push(matches, pageKey); notifyListeners(); + return future; } /// Returns `true` if the active Navigator can pop. @@ -127,6 +130,7 @@ class GoRouterDelegate extends RouterDelegate final _NavigatorStateIterator iterator = _createNavigatorStateIterator(); while (iterator.moveNext()) { if (iterator.current.canPop()) { + iterator.matchList.last.complete(result); iterator.current.pop(result); return; } @@ -308,7 +312,7 @@ class _NavigatorStateIterator extends Iterator { /// 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 extends RouteMatch { /// Constructor for [ImperativeRouteMatch]. ImperativeRouteMatch({ required super.route, @@ -317,8 +321,20 @@ class ImperativeRouteMatch extends RouteMatch { required super.error, required super.pageKey, required this.matches, - }); + }) : _completer = Completer(); /// The matches that produces this route match. final RouteMatchList matches; + + /// The completer for the future returned by [GoRouter.push]. + final Completer _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 get _future => _completer.future; } diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index 62a4d550054..f4840f2ec86 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 @@ class RouteMatch { 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 0900588a4ff..47f5b08cd8e 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -44,17 +44,17 @@ extension GoRouterHelper on BuildContext { /// * [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 push(String location, {Object? extra}) => + GoRouter.of(this).push(location, extra: extra); /// Navigate to a named route onto the page stack. - void pushNamed( + Future pushNamed( String name, { Map params = const {}, Map queryParams = const {}, Object? extra, }) => - GoRouter.of(this).pushNamed( + GoRouter.of(this).pushNamed( name, params: params, queryParams: queryParams, diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index e91eaf352a0..37c3e98bd02 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -104,7 +104,7 @@ class GoRouteInformationParser extends RouteInformationParser { } if (configuration.matches.last is ImperativeRouteMatch) { configuration = - (configuration.matches.last as ImperativeRouteMatch).matches; + (configuration.matches.last as ImperativeRouteMatch).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 29603720112..b63cb997320 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -159,7 +159,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig { routerDelegate.currentConfiguration.matches.last is ImperativeRouteMatch) { newLocation = (routerDelegate.currentConfiguration.matches.last - as ImperativeRouteMatch) + as ImperativeRouteMatch) .matches .uri .toString(); @@ -219,32 +219,31 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// * [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 push(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((RouteMatchList matches) { - _routerDelegate.push(matches); - }); + ); + + return _routerDelegate.push(matches); } /// Push a named route onto the page stack w/ optional parameters, e.g. /// `name='person', params={'fid': 'f2', 'pid': 'p1'}` - void pushNamed( + Future pushNamed( String name, { Map params = const {}, Map queryParams = const {}, Object? extra, }) => - push( + push( namedLocation(name, params: params, queryParams: queryParams), extra: extra, ); diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index d4d4b29c454..991a4f9ad28 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 b6cc6bc6b65..e40084a77fd 100644 --- a/packages/go_router/test/delegate_test.dart +++ b/packages/go_router/test/delegate_test.dart @@ -141,7 +141,7 @@ void main() { reason: 'The last match should have been removed', ); expect( - (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) + (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) .matches .uri .toString(), @@ -270,7 +270,7 @@ void main() { reason: 'The last match should have been removed', ); expect( - (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) + (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) .matches .uri .toString(), @@ -393,7 +393,7 @@ void main() { reason: 'The last match should have been removed', ); expect( - (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) + (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) .matches .uri .toString(), diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index 17335136bd7..833c16e0fb7 100644 --- a/packages/go_router/test/go_router_test.dart +++ b/packages/go_router/test/go_router_test.dart @@ -2439,8 +2439,8 @@ void main() { expect(router.location, loc); expect(matches.matches, hasLength(2)); expect(find.byType(PersonScreen), findsOneWidget); - final ImperativeRouteMatch imperativeRouteMatch = - matches.matches.last as ImperativeRouteMatch; + final ImperativeRouteMatch imperativeRouteMatch = + matches.matches.last as ImperativeRouteMatch; expect(imperativeRouteMatch.matches.pathParameters['fid'], fid); expect(imperativeRouteMatch.matches.pathParameters['pid'], pid); }); @@ -2698,6 +2698,26 @@ void main() { 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( + 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 @@ void main() { 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( + 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 9fbb915a4a2..7afa9fe5752 100644 --- a/packages/go_router/test/inherited_test.dart +++ b/packages/go_router/test/inherited_test.dart @@ -129,11 +129,12 @@ class MockGoRouter extends GoRouter { late String latestPushedName; @override - void pushNamed(String name, + Future pushNamed(String name, {Map params = const {}, Map queryParams = const {}, Object? extra}) { latestPushedName = name; + return Future.value(); } @override diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart index 04d12e7383e..b81f4c2e894 100644 --- a/packages/go_router/test/test_helpers.dart +++ b/packages/go_router/test/test_helpers.dart @@ -95,9 +95,10 @@ class GoRouterPushSpy extends GoRouter { Object? extra; @override - void push(String location, {Object? extra}) { + Future push(String location, {Object? extra}) { myLocation = location; this.extra = extra; + return Future.value(extra as T?); } } @@ -110,7 +111,7 @@ class GoRouterPushNamedSpy extends GoRouter { Object? extra; @override - void pushNamed( + Future pushNamed( String name, { Map params = const {}, Map queryParams = const {}, @@ -120,6 +121,7 @@ class GoRouterPushNamedSpy extends GoRouter { this.params = params; this.queryParams = queryParams; this.extra = extra; + return Future.value(extra as T?); } }