From 9098a288ce6dd31edd4ddfe362b8dbddc106ebdb Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Sat, 24 Dec 2022 04:57:00 +0800 Subject: [PATCH 1/9] :recycle: Use PageKey --- packages/go_router/lib/go_router.dart | 1 + packages/go_router/lib/src/builder.dart | 7 +-- packages/go_router/lib/src/match.dart | 12 +++--- packages/go_router/lib/src/matching.dart | 3 +- packages/go_router/lib/src/page_key.dart | 47 +++++++++++++++++++++ packages/go_router/lib/src/redirection.dart | 3 +- packages/go_router/lib/src/state.dart | 10 +++-- 7 files changed, 68 insertions(+), 15 deletions(-) create mode 100644 packages/go_router/lib/src/page_key.dart diff --git a/packages/go_router/lib/go_router.dart b/packages/go_router/lib/go_router.dart index ad2a74f222c..cb7ed9b25df 100644 --- a/packages/go_router/lib/go_router.dart +++ b/packages/go_router/lib/go_router.dart @@ -10,6 +10,7 @@ export 'src/configuration.dart' show GoRoute, GoRouterState, RouteBase, ShellRoute; export 'src/misc/extensions.dart'; export 'src/misc/inherited_router.dart'; +export 'src/page_key.dart'; export 'src/pages/custom_transition_page.dart'; export 'src/route_data.dart' show GoRouteData, TypedGoRoute; export 'src/router.dart'; diff --git a/packages/go_router/lib/src/builder.dart b/packages/go_router/lib/src/builder.dart index 04c6656fb82..22f991f7ec4 100644 --- a/packages/go_router/lib/src/builder.dart +++ b/packages/go_router/lib/src/builder.dart @@ -10,6 +10,7 @@ import 'logging.dart'; import 'match.dart'; import 'matching.dart'; import 'misc/error_screen.dart'; +import 'page_key.dart'; import 'pages/cupertino.dart'; import 'pages/custom_transition_page.dart'; import 'pages/material.dart'; @@ -365,7 +366,7 @@ class RouteBuilder { key: state.pageKey, name: state.name ?? state.fullpath, arguments: {...state.params, ...state.queryParams}, - restorationId: state.pageKey.value, + restorationId: state.pageKey.fullPath, child: child, ); } @@ -416,7 +417,7 @@ class RouteBuilder { queryParams: uri.queryParameters, queryParametersAll: uri.queryParametersAll, error: Exception(error), - pageKey: const ValueKey('error'), + pageKey: const PageKey(path: 'error'), ); // If the error page builder is provided, use that, otherwise, if the error @@ -438,7 +439,7 @@ class RouteBuilder { } typedef _PageBuilderForAppType = Page Function({ - required LocalKey key, + required PageKey key, required String? name, required Object? arguments, required String restorationId, diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index 9d01fa56fbc..da42cc996d0 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -2,12 +2,10 @@ // 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 'configuration.dart'; import 'matching.dart'; +import 'page_key.dart'; import 'path_utils.dart'; -import 'route.dart'; /// An instance of a GoRoute plus information about the current location. class RouteMatch { @@ -34,7 +32,7 @@ class RouteMatch { subloc: restLoc, extra: extra, error: null, - pageKey: ValueKey(route.hashCode.toString()), + pageKey: PageKey(path: route.hashCode.toString()), ); } else if (route is GoRoute) { assert(!route.path.contains('//')); @@ -55,7 +53,7 @@ class RouteMatch { subloc: subloc, extra: extra, error: null, - pageKey: ValueKey(route.hashCode.toString()), + pageKey: PageKey(path: route.hashCode.toString()), ); } throw MatcherError('Unexpected route type: $route', restLoc); @@ -74,5 +72,5 @@ class RouteMatch { final Exception? error; /// Optional value key of type string, to hold a unique reference to a page. - final ValueKey pageKey; + final PageKey pageKey; } diff --git a/packages/go_router/lib/src/matching.dart b/packages/go_router/lib/src/matching.dart index 4297249f8b1..6119b9ab0e2 100644 --- a/packages/go_router/lib/src/matching.dart +++ b/packages/go_router/lib/src/matching.dart @@ -6,6 +6,7 @@ import 'package:flutter/widgets.dart'; import 'configuration.dart'; import 'match.dart'; +import 'page_key.dart'; import 'path_utils.dart'; /// Converts a location into a list of [RouteMatch] objects. @@ -232,7 +233,7 @@ RouteMatchList errorScreen(Uri uri, String errorMessage) { throw UnimplementedError(); }, ), - pageKey: const ValueKey('error'), + pageKey: const PageKey(path :'error'), ), ], uri, diff --git a/packages/go_router/lib/src/page_key.dart b/packages/go_router/lib/src/page_key.dart new file mode 100644 index 00000000000..75b0da4e3cf --- /dev/null +++ b/packages/go_router/lib/src/page_key.dart @@ -0,0 +1,47 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// 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'; + +/// A key go router uses for its pages. +class PageKey extends LocalKey { + /// A key go router uses for its pages. + const PageKey({ + required this.path, + this.count = 0, + }); + + /// The path of the page. For example: + /// ```dart + /// '/family/:fid' + /// ``` + final String path; + + /// An integer that is incremented each time a new page is created. This is + /// used to generate an unique/different key for 2 pages with the same path. + final int count; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is PageKey && other.path == path && other.count == count; + } + + @override + int get hashCode => Object.hash(runtimeType, path, count); + + @override + String toString() { + return 'PageKey($path, $count)'; + } + + /// The full path of the page. For example: + /// + /// ```dart + /// '/family/:fid-p2' + /// ``` + String get fullPath => '$path-p$count'; +} diff --git a/packages/go_router/lib/src/redirection.dart b/packages/go_router/lib/src/redirection.dart index 3ebef5cf294..71a5612e0ec 100644 --- a/packages/go_router/lib/src/redirection.dart +++ b/packages/go_router/lib/src/redirection.dart @@ -10,6 +10,7 @@ import 'configuration.dart'; import 'logging.dart'; import 'match.dart'; import 'matching.dart'; +import 'page_key.dart'; /// A GoRouter redirector function. typedef RouteRedirector = FutureOr Function( @@ -100,7 +101,7 @@ FutureOr redirect( queryParams: prevMatchList.uri.queryParameters, queryParametersAll: prevMatchList.uri.queryParametersAll, extra: extra, - pageKey: const ValueKey('topLevel'), + pageKey: const PageKey(path: 'topLevel'), ), ); diff --git a/packages/go_router/lib/src/state.dart b/packages/go_router/lib/src/state.dart index 721565a5e62..c9c5693bfa6 100644 --- a/packages/go_router/lib/src/state.dart +++ b/packages/go_router/lib/src/state.dart @@ -4,9 +4,9 @@ import 'package:flutter/widgets.dart'; -import '../go_router.dart'; import 'configuration.dart'; import 'misc/errors.dart'; +import 'page_key.dart'; /// The route state during routing. /// @@ -64,8 +64,12 @@ class GoRouterState { /// The error associated with this sub-route. final Exception? error; - /// A unique string key for this sub-route, e.g. ValueKey('/family/:fid') - final ValueKey pageKey; + /// A unique string key for this sub-route. + /// E.g. + /// ```dart + /// PageKey(path: '/family/:fid', count: 0) + /// ``` + final PageKey pageKey; /// Gets the [GoRouterState] from context. /// From b9f2edc2351e12f958e3d8ffd3f633910a27fc26 Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Sat, 24 Dec 2022 04:58:01 +0800 Subject: [PATCH 2/9] :sparkles: Add replace method --- packages/go_router/lib/src/delegate.dart | 57 +++++++++++++++---- packages/go_router/lib/src/matching.dart | 5 +- .../go_router/lib/src/misc/extensions.dart | 19 ++++++- packages/go_router/lib/src/router.dart | 31 +++++++++- 4 files changed, 97 insertions(+), 15 deletions(-) diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index b34aac04f89..614e09e4895 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -12,6 +12,7 @@ import 'configuration.dart'; import 'match.dart'; import 'matching.dart'; import 'misc/errors.dart'; +import 'page_key.dart'; import 'typedefs.dart'; /// GoRouter implementation of [RouterDelegate]. @@ -50,7 +51,7 @@ class GoRouterDelegate extends RouterDelegate /// /// This is used to generate a unique key for each route. /// - /// For example, it would could be equal to: + /// For example, it could be equal to: /// ```dart /// { /// 'family': 1, @@ -75,15 +76,14 @@ class GoRouterDelegate extends RouterDelegate return false; } - /// Pushes the given location onto the page stack - void push(RouteMatchList matches) { - assert(matches.last.route is! ShellRoute); - + PageKey _getNewKeyForPath(String path) { // Remap the pageKey to allow any number of the same page on the stack - final int count = (_pushCounts[matches.fullpath] ?? 0) + 1; - _pushCounts[matches.fullpath] = count; - final ValueKey pageKey = - ValueKey('${matches.fullpath}-p$count'); + final int count = (_pushCounts[path] ?? -1) + 1; + _pushCounts[path] = count; + return PageKey(path: path, count: count); + } + + void _push(RouteMatchList matches, PageKey pageKey) { final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch( route: matches.last.route, subloc: matches.last.subloc, @@ -94,6 +94,20 @@ class GoRouterDelegate extends RouterDelegate ); _matchList.push(newPageKeyMatch); + } + + /// Pushes the given location onto the page stack. + /// + /// See also: + /// * [pushReplacement] which replaces the top-most page of the page stack and + /// always use a new page key. + /// * [replace] which replaces the top-most page of the page stack and keeps + /// the page key when possible. + void push(RouteMatchList matches) { + assert(matches.last.route is! ShellRoute); + + final PageKey pageKey = _getNewKeyForPath(matches.fullpath); + _push(matches, pageKey); notifyListeners(); } @@ -141,15 +155,38 @@ class GoRouterDelegate extends RouterDelegate return true; } - /// Replaces the top-most page of the page stack with the given one. + /// Replaces the top-most page of the page stack with the given one. The page + /// key of the new page will always be different from the old one. /// /// See also: /// * [push] which pushes the given location onto the page stack. + /// * [replace] which replaces the top-most page of the page stack but keeps + /// the page key when possible. void pushReplacement(RouteMatchList matches) { + assert(matches.last.route is! ShellRoute); _matchList.pop(); push(matches); // [push] will notify the listeners. } + /// Replaces the top-most page of the page stack with the given one and reuse + /// the page key when the path didn't change. + /// + /// See also: + /// * [push] which pushes the given location onto the page stack. + /// * [pushReplacement] which replaces the top-most page of the page stack but + /// always use a new page key. + void replace(RouteMatchList matches) { + assert(matches.last.route is! ShellRoute); + PageKey pageKey = _matchList.pop().pageKey; + if (pageKey.path != matches.fullpath) { + // If the same page is not being replace, (ex: 'family' and + // 'family/:fid'), We don't try to reuse the same key. + pageKey = _getNewKeyForPath(matches.fullpath); + } + _push(matches, pageKey); + notifyListeners(); + } + /// For internal use; visible for testing only. @visibleForTesting RouteMatchList get matches => _matchList; diff --git a/packages/go_router/lib/src/matching.dart b/packages/go_router/lib/src/matching.dart index 6119b9ab0e2..76eba674fa4 100644 --- a/packages/go_router/lib/src/matching.dart +++ b/packages/go_router/lib/src/matching.dart @@ -98,16 +98,17 @@ class RouteMatchList { } /// Removes the last match. - void pop() { + RouteMatch pop() { if (_matches.last.route is GoRoute) { final GoRoute route = _matches.last.route as GoRoute; _uri = _uri.replace(path: removePatternFromPath(route.path, _uri.path)); } - _matches.removeLast(); + final RouteMatch routeMatch = _matches.removeLast(); // Also pop ShellRoutes when there are no subsequent route matches while (_matches.isNotEmpty && _matches.last.route is ShellRoute) { _matches.removeLast(); } + return routeMatch; } /// An optional object provided by the app during navigation. diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index 7030ad3c25c..649d03ae4eb 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -37,6 +37,12 @@ extension GoRouterHelper on BuildContext { ); /// Push a location onto the page stack. + /// + /// See also: + /// * [pushReplacement] which replaces the top-most page of the page stack and + /// always use a new page key. + /// * [replace] which replaces the top-most page of the page stack and keeps + /// the page key when possible. void push(String location, {Object? extra}) => GoRouter.of(this).push(location, extra: extra); @@ -66,7 +72,9 @@ extension GoRouterHelper on BuildContext { /// /// See also: /// * [go] which navigates to the location. - /// * [push] which pushes the location onto the page stack. + /// * [push] which pushes the given location onto the page stack. + /// * [replace] which replaces the top-most page of the page stack but keeps + /// the page key when possible. void pushReplacement(String location, {Object? extra}) => GoRouter.of(this).pushReplacement(location, extra: extra); @@ -89,4 +97,13 @@ extension GoRouterHelper on BuildContext { queryParams: queryParams, extra: extra, ); + + /// Replaces the top-most page of the page stack with the given one. + /// + /// See also: + /// * [push] which pushes the given location onto the page stack. + /// * [pushReplacement] which replaces the top-most page of the page stack but + /// always use a new page key. + void replace(String location, {Object? extra}) => + GoRouter.of(this).pushReplacement(location, extra: extra); } diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index 73a86c645de..e3c09da17d9 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -203,7 +203,13 @@ class GoRouter extends ChangeNotifier implements RouterConfig { ); /// Push a URI location onto the page stack w/ optional query parameters, e.g. - /// `/family/f2/person/p1?color=blue` + /// `/family/f2/person/p1?color=blue`. + /// + /// See also: + /// * [pushReplacement] which replaces the top-most page of the page stack and + /// always use a new page key. + /// * [replace] which replaces the top-most page of the page stack and keeps + /// the page key when possible. void push(String location, {Object? extra}) { assert(() { log.info('pushing $location'); @@ -239,7 +245,9 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// /// See also: /// * [go] which navigates to the location. - /// * [push] which pushes the location onto the page stack. + /// * [push] which pushes the given location onto the page stack. + /// * [replace] which replaces the top-most page of the page stack but keeps + /// the page key when possible. void pushReplacement(String location, {Object? extra}) { routeInformationParser .parseRouteInformationWithDependencies( @@ -272,6 +280,25 @@ class GoRouter extends ChangeNotifier implements RouterConfig { ); } + /// Replaces the top-most page of the page stack with the given one. + /// + /// See also: + /// * [push] which pushes the given location onto the page stack. + /// * [pushReplacement] which replaces the top-most page of the page stack but + /// always use a new page key. + void replace(String location, {Object? extra}) { + 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 matchList) { + routerDelegate.replace(matchList); + }); + } + /// Pop the top-most route off the current screen. /// /// If the top-most route is a pop up or dialog, this method pops it instead From 2b5bcbac534872a25ddd8f48364bf5fa6e84cd4b Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Sat, 24 Dec 2022 04:58:16 +0800 Subject: [PATCH 3/9] :whie_check_mark: Update the tests --- packages/go_router/test/builder_test.dart | 13 +-- packages/go_router/test/delegate_test.dart | 115 ++++++++++++++++++++- 2 files changed, 117 insertions(+), 11 deletions(-) diff --git a/packages/go_router/test/builder_test.dart b/packages/go_router/test/builder_test.dart index 04e75432df9..9a096f662c2 100644 --- a/packages/go_router/test/builder_test.dart +++ b/packages/go_router/test/builder_test.dart @@ -8,6 +8,7 @@ import 'package:go_router/src/builder.dart'; import 'package:go_router/src/configuration.dart'; import 'package:go_router/src/match.dart'; import 'package:go_router/src/matching.dart'; +import 'package:go_router/src/page_key.dart'; void main() { group('RouteBuilder', () { @@ -35,7 +36,7 @@ void main() { subloc: '/', extra: null, error: null, - pageKey: const ValueKey('/'), + pageKey: const PageKey(path: '/'), ), ], Uri.parse('/'), @@ -82,7 +83,7 @@ void main() { subloc: '/', extra: null, error: null, - pageKey: const ValueKey('/'), + pageKey: const PageKey(path: '/'), ), ], Uri.parse('/'), @@ -124,7 +125,7 @@ void main() { subloc: '/', extra: null, error: null, - pageKey: const ValueKey('/'), + pageKey: const PageKey(path: '/'), ), ], Uri.parse('/'), @@ -179,14 +180,14 @@ void main() { subloc: '', extra: null, error: null, - pageKey: const ValueKey(''), + pageKey: const PageKey(path: ''), ), RouteMatch( route: config.routes.first.routes.first, subloc: '/details', extra: null, error: null, - pageKey: const ValueKey('/details'), + pageKey: const PageKey(path: '/details'), ), ], Uri.parse('/details'), @@ -254,7 +255,7 @@ void main() { subloc: '/a/details', extra: null, error: null, - pageKey: const ValueKey('/a/details'), + pageKey: const PageKey(path: '/a/details'), ), ], Uri.parse('/a/details'), diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart index 62f4b691d98..e7d4d7b74c8 100644 --- a/packages/go_router/test/delegate_test.dart +++ b/packages/go_router/test/delegate_test.dart @@ -67,7 +67,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches[1].pageKey, - const Key('/a-p1'), + const PageKey(path: '/a'), ); goRouter.push('/a'); @@ -76,7 +76,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 3); expect( goRouter.routerDelegate.matches.matches[2].pageKey, - const Key('/a-p2'), + const PageKey(path: '/a', count: 1), ); }, ); @@ -151,7 +151,7 @@ void main() { }); testWidgets( - 'It should return different pageKey when replace is called', + 'It should return different pageKey when pushReplacement is called', (WidgetTester tester) async { final GoRouter goRouter = await createGoRouter(tester); expect(goRouter.routerDelegate.matches.matches.length, 1); @@ -166,7 +166,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const Key('/a-p1'), + const PageKey(path: '/a'), ); goRouter.pushReplacement('/a'); @@ -175,7 +175,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const Key('/a-p2'), + const PageKey(path: '/a', count: 1), ); }, ); @@ -235,6 +235,111 @@ void main() { ); }); + group('replace', () { + testWidgets('It should replace the last match with the given one', + (WidgetTester tester) async { + final GoRouter goRouter = GoRouter( + initialLocation: '/', + routes: [ + GoRoute(path: '/', builder: (_, __) => const SizedBox()), + GoRoute(path: '/page-0', builder: (_, __) => const SizedBox()), + GoRoute(path: '/page-1', builder: (_, __) => const SizedBox()), + ], + ); + await tester.pumpWidget( + MaterialApp.router( + routerConfig: goRouter, + ), + ); + + goRouter.push('/page-0'); + + goRouter.routerDelegate.addListener(expectAsync0(() {})); + final RouteMatch first = goRouter.routerDelegate.matches.matches.first; + final RouteMatch last = goRouter.routerDelegate.matches.last; + goRouter.replace('/page-1'); + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.first, + first, + reason: 'The first match should still be in the list of matches', + ); + expect( + goRouter.routerDelegate.matches.last, + isNot(last), + reason: 'The last match should have been removed', + ); + expect( + (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) + .matches + .uri + .toString(), + '/page-1', + reason: 'The new location should have been pushed', + ); + }); + + testWidgets( + 'It should use the same pageKey when replace is called with the same path', + (WidgetTester tester) async { + final GoRouter goRouter = await createGoRouter(tester); + expect(goRouter.routerDelegate.matches.matches.length, 1); + expect( + goRouter.routerDelegate.matches.matches[0].pageKey, + isNotNull, + ); + + goRouter.push('/a'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/a'), + ); + + goRouter.replace('/a'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/a'), + ); + }, + ); + + testWidgets( + 'It should use a new pageKey when replace is called with a different path', + (WidgetTester tester) async { + final GoRouter goRouter = await createGoRouter(tester); + expect(goRouter.routerDelegate.matches.matches.length, 1); + expect( + goRouter.routerDelegate.matches.matches[0].pageKey, + isNotNull, + ); + + goRouter.push('/a'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/a'), + ); + + goRouter.replace('/'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/'), + ); + }, + ); + }); + testWidgets('dispose unsubscribes from refreshListenable', (WidgetTester tester) async { final FakeRefreshListenable refreshListenable = FakeRefreshListenable(); From f85f785894e5cd98d940d6240f83baa428c4ed96 Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Sat, 24 Dec 2022 04:58:28 +0800 Subject: [PATCH 4/9] :arrow_up: Update the version number --- packages/go_router/CHANGELOG.md | 4 ++++ packages/go_router/pubspec.yaml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index 075e950d611..69e8166067d 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,7 @@ +## 6.1.0 + +- Adds `replace` method to that replaces the current route with a new one and keeps the same page key if the path is the same. This is useful for when you want to update the query params without changing the page key ([#115902]https://github.com/flutter/flutter/issues/115902). + ## 6.0.0 - **BREAKING CHANGE** diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index edcfd0a077a..fcf13313f83 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.0.0 +version: 6.1.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 From 7bff0736941520ada6b7fe7dc3b53b318e6e2de3 Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Mon, 9 Jan 2023 23:00:25 +0800 Subject: [PATCH 5/9] :sparkles: Add replaceNamed --- .../go_router/lib/src/misc/extensions.dart | 18 ++- packages/go_router/lib/src/router.dart | 20 +++ packages/go_router/test/delegate_test.dart | 123 ++++++++++++++++++ 3 files changed, 160 insertions(+), 1 deletion(-) diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index 649d03ae4eb..8fe4bf1c294 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -105,5 +105,21 @@ extension GoRouterHelper on BuildContext { /// * [pushReplacement] which replaces the top-most page of the page stack but /// always use a new page key. void replace(String location, {Object? extra}) => - GoRouter.of(this).pushReplacement(location, extra: extra); + GoRouter.of(this).replace(location, extra: extra); + + /// Replaces the top-most page of the page stack with the named route w/ + /// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': + /// 'p1'}`. + /// + /// See also: + /// * [pushNamed] which pushes the given location onto the page stack. + /// * [pushReplacementNamed] which replaces the top-most page of the page + /// stack but always use a new page key. + void replaceNamed( + String name, { + Map params = const {}, + Map queryParams = const {}, + Object? extra, + }) => + GoRouter.of(this).replaceNamed(name, extra: extra); } diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index e3c09da17d9..ad3ce1158c4 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -299,6 +299,26 @@ class GoRouter extends ChangeNotifier implements RouterConfig { }); } + /// Replaces the top-most page of the page stack with the named route w/ + /// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': + /// 'p1'}`. + /// + /// See also: + /// * [pushNamed] which pushes the given location onto the page stack. + /// * [pushReplacementNamed] which replaces the top-most page of the page + /// stack but always use a new page key. + void replaceNamed( + String name, { + Map params = const {}, + Map queryParams = const {}, + Object? extra, + }) { + replace( + namedLocation(name, params: params, queryParams: queryParams), + extra: extra, + ); + } + /// Pop the top-most route off the current screen. /// /// If the top-most route is a pop up or dialog, this method pops it instead diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart index e7d4d7b74c8..b70eae71aea 100644 --- a/packages/go_router/test/delegate_test.dart +++ b/packages/go_router/test/delegate_test.dart @@ -340,6 +340,129 @@ void main() { ); }); + group('replaceNamed', () { + Future createGoRouter( + WidgetTester tester, { + Listenable? refreshListenable, + }) async { + final GoRouter router = GoRouter( + initialLocation: '/', + routes: [ + GoRoute( + path: '/', + name: 'home', + builder: (_, __) => const SizedBox(), + ), + GoRoute( + path: '/page-0', + name: 'page0', + builder: (_, __) => const SizedBox(), + ), + GoRoute( + path: '/page-1', + name: 'page1', + builder: (_, __) => const SizedBox(), + ), + ], + ); + await tester.pumpWidget(MaterialApp.router( + routerConfig: router, + )); + return router; + } + + testWidgets('It should replace the last match with the given one', + (WidgetTester tester) async { + final GoRouter goRouter = await createGoRouter(tester); + + goRouter.pushNamed('page0'); + + goRouter.routerDelegate.addListener(expectAsync0(() {})); + final RouteMatch first = goRouter.routerDelegate.matches.matches.first; + final RouteMatch last = goRouter.routerDelegate.matches.last; + goRouter.replaceNamed('page1'); + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.first, + first, + reason: 'The first match should still be in the list of matches', + ); + expect( + goRouter.routerDelegate.matches.last, + isNot(last), + reason: 'The last match should have been removed', + ); + expect( + (goRouter.routerDelegate.matches.last as ImperativeRouteMatch) + .matches + .uri + .toString(), + '/page-1', + reason: 'The new location should have been pushed', + ); + }); + + testWidgets( + 'It should use the same pageKey when replace is called with the same path', + (WidgetTester tester) async { + final GoRouter goRouter = await createGoRouter(tester); + expect(goRouter.routerDelegate.matches.matches.length, 1); + expect( + goRouter.routerDelegate.matches.matches.first.pageKey, + isNotNull, + ); + + goRouter.pushNamed('page0'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/page-0'), + ); + + goRouter.replaceNamed('page0'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/page-0'), + ); + }, + ); + + testWidgets( + 'It should use a new pageKey when replace is called with a different path', + (WidgetTester tester) async { + final GoRouter goRouter = await createGoRouter(tester); + expect(goRouter.routerDelegate.matches.matches.length, 1); + expect( + goRouter.routerDelegate.matches.matches.first.pageKey, + isNotNull, + ); + + goRouter.pushNamed('page0'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/page-0'), + ); + + goRouter.replaceNamed('home'); + await tester.pumpAndSettle(); + + expect(goRouter.routerDelegate.matches.matches.length, 2); + expect( + goRouter.routerDelegate.matches.matches.last.pageKey, + const PageKey(path: '/'), + ); + }, + ); + }); + testWidgets('dispose unsubscribes from refreshListenable', (WidgetTester tester) async { final FakeRefreshListenable refreshListenable = FakeRefreshListenable(); From 6bb466d4afa787fa64a0107d596bdfff4adcc58c Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Fri, 17 Feb 2023 10:15:46 +0800 Subject: [PATCH 6/9] :necktie: Always reuse the same key when using replace --- packages/go_router/lib/go_router.dart | 1 - packages/go_router/lib/src/builder.dart | 7 ++- packages/go_router/lib/src/delegate.dart | 26 ++++------ packages/go_router/lib/src/match.dart | 14 +++--- packages/go_router/lib/src/matching.dart | 3 +- .../go_router/lib/src/misc/extensions.dart | 14 +++--- packages/go_router/lib/src/page_key.dart | 47 ------------------- packages/go_router/lib/src/redirection.dart | 3 +- packages/go_router/lib/src/router.dart | 12 ++--- packages/go_router/lib/src/state.dart | 5 +- packages/go_router/test/builder_test.dart | 13 +++-- packages/go_router/test/delegate_test.dart | 28 +++++------ 12 files changed, 58 insertions(+), 115 deletions(-) delete mode 100644 packages/go_router/lib/src/page_key.dart diff --git a/packages/go_router/lib/go_router.dart b/packages/go_router/lib/go_router.dart index cb7ed9b25df..ad2a74f222c 100644 --- a/packages/go_router/lib/go_router.dart +++ b/packages/go_router/lib/go_router.dart @@ -10,7 +10,6 @@ export 'src/configuration.dart' show GoRoute, GoRouterState, RouteBase, ShellRoute; export 'src/misc/extensions.dart'; export 'src/misc/inherited_router.dart'; -export 'src/page_key.dart'; export 'src/pages/custom_transition_page.dart'; export 'src/route_data.dart' show GoRouteData, TypedGoRoute; export 'src/router.dart'; diff --git a/packages/go_router/lib/src/builder.dart b/packages/go_router/lib/src/builder.dart index b3e7b7adce3..ebb382dd390 100644 --- a/packages/go_router/lib/src/builder.dart +++ b/packages/go_router/lib/src/builder.dart @@ -11,7 +11,6 @@ import 'logging.dart'; import 'match.dart'; import 'matching.dart'; import 'misc/error_screen.dart'; -import 'page_key.dart'; import 'pages/cupertino.dart'; import 'pages/custom_transition_page.dart'; import 'pages/material.dart'; @@ -400,7 +399,7 @@ class RouteBuilder { key: state.pageKey, name: state.name ?? state.path, arguments: {...state.params, ...state.queryParams}, - restorationId: state.pageKey.fullPath, + restorationId: state.pageKey.value, child: child, ); } @@ -451,7 +450,7 @@ class RouteBuilder { queryParams: uri.queryParameters, queryParametersAll: uri.queryParametersAll, error: Exception(error), - pageKey: const PageKey(path: 'error'), + pageKey: const ValueKey('error'), ); // If the error page builder is provided, use that, otherwise, if the error @@ -485,7 +484,7 @@ class RouteBuilder { } typedef _PageBuilderForAppType = Page Function({ - required PageKey key, + required LocalKey key, required String? name, required Object? arguments, required String restorationId, diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index d4a1fa56ba0..e4a4119940c 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -12,7 +12,6 @@ import 'configuration.dart'; import 'match.dart'; import 'matching.dart'; import 'misc/errors.dart'; -import 'page_key.dart'; import 'typedefs.dart'; /// GoRouter implementation of [RouterDelegate]. @@ -76,14 +75,14 @@ class GoRouterDelegate extends RouterDelegate return false; } - PageKey _getNewKeyForPath(String path) { + ValueKey _getNewKeyForPath(String path) { // Remap the pageKey to allow any number of the same page on the stack final int count = (_pushCounts[path] ?? -1) + 1; _pushCounts[path] = count; - return PageKey(path: path, count: count); + return ValueKey('$path-p$count'); } - void _push(RouteMatchList matches, PageKey pageKey) { + void _push(RouteMatchList matches, ValueKey pageKey) { final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch( route: matches.last.route, subloc: matches.last.subloc, @@ -101,12 +100,12 @@ class GoRouterDelegate extends RouterDelegate /// See also: /// * [pushReplacement] which replaces the top-most page of the page stack and /// always use a new page key. - /// * [replace] which replaces the top-most page of the page stack and keeps - /// the page key when possible. + /// * [replace] which replaces the top-most page of the page stack and reuses + /// the page key. void push(RouteMatchList matches) { assert(matches.last.route is! ShellRoute); - final PageKey pageKey = _getNewKeyForPath(matches.fullpath); + final ValueKey pageKey = _getNewKeyForPath(matches.fullpath); _push(matches, pageKey); notifyListeners(); } @@ -165,8 +164,8 @@ class GoRouterDelegate extends RouterDelegate /// /// See also: /// * [push] which pushes the given location onto the page stack. - /// * [replace] which replaces the top-most page of the page stack but keeps - /// the page key when possible. + /// * [replace] which replaces the top-most page of the page stack but reuses + /// the page key. void pushReplacement(RouteMatchList matches) { assert(matches.last.route is! ShellRoute); _matchList.remove(_matchList.last); @@ -179,17 +178,12 @@ class GoRouterDelegate extends RouterDelegate /// See also: /// * [push] which pushes the given location onto the page stack. /// * [pushReplacement] which replaces the top-most page of the page stack but - /// always use a new page key. + /// always uses a new page key. void replace(RouteMatchList matches) { assert(matches.last.route is! ShellRoute); final RouteMatch routeMatch = _matchList.last; - PageKey pageKey = routeMatch.pageKey; + final ValueKey pageKey = routeMatch.pageKey; _matchList.remove(routeMatch); - if (pageKey.path != matches.fullpath) { - // If the same page is not being replace, (ex: 'family' and - // 'family/:fid'), We don't try to reuse the same key. - pageKey = _getNewKeyForPath(matches.fullpath); - } _push(matches, pageKey); notifyListeners(); } diff --git a/packages/go_router/lib/src/match.dart b/packages/go_router/lib/src/match.dart index cdb1e3852af..62a4d550054 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -2,10 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'configuration.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + import 'matching.dart'; -import 'page_key.dart'; import 'path_utils.dart'; +import 'route.dart'; /// An instance of a GoRoute plus information about the current location. class RouteMatch { @@ -32,7 +34,7 @@ class RouteMatch { subloc: restLoc, extra: extra, error: null, - pageKey: PageKey(path: route.hashCode.toString()), + pageKey: ValueKey(route.hashCode.toString()), ); } else if (route is GoRoute) { assert(!route.path.contains('//')); @@ -53,7 +55,7 @@ class RouteMatch { subloc: subloc, extra: extra, error: null, - pageKey: PageKey(path: route.hashCode.toString()), + pageKey: ValueKey(route.hashCode.toString()), ); } throw MatcherError('Unexpected route type: $route', restLoc); @@ -71,6 +73,6 @@ class RouteMatch { /// An exception if there was an error during matching. final Exception? error; - /// Value key, to hold a unique reference to a page. - final PageKey pageKey; + /// Value key of type string, to hold a unique reference to a page. + final ValueKey pageKey; } diff --git a/packages/go_router/lib/src/matching.dart b/packages/go_router/lib/src/matching.dart index 76a773bd84a..86e570210f6 100644 --- a/packages/go_router/lib/src/matching.dart +++ b/packages/go_router/lib/src/matching.dart @@ -8,7 +8,6 @@ import 'package:flutter/widgets.dart'; import 'configuration.dart'; import 'delegate.dart'; import 'match.dart'; -import 'page_key.dart'; import 'path_utils.dart'; /// Converts a location into a list of [RouteMatch] objects. @@ -250,7 +249,7 @@ RouteMatchList errorScreen(Uri uri, String errorMessage) { throw UnimplementedError(); }, ), - pageKey: const PageKey(path: 'error'), + pageKey: const ValueKey('error'), ), ], uri, diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index 4976e6633ec..8798c0e1ef7 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -40,9 +40,9 @@ extension GoRouterHelper on BuildContext { /// /// See also: /// * [pushReplacement] which replaces the top-most page of the page stack and - /// always use a new page key. - /// * [replace] which replaces the top-most page of the page stack and keeps - /// the page key when possible. + /// always uses a new page key. + /// * [replace] which replaces the top-most page of the page stack and reuses + /// the page key. void push(String location, {Object? extra}) => GoRouter.of(this).push(location, extra: extra); @@ -73,8 +73,8 @@ extension GoRouterHelper on BuildContext { /// See also: /// * [go] which navigates to the location. /// * [push] which pushes the given location onto the page stack. - /// * [replace] which replaces the top-most page of the page stack but keeps - /// the page key when possible. + /// * [replace] which replaces the top-most page of the page stack but reuses + /// the page key. void pushReplacement(String location, {Object? extra}) => GoRouter.of(this).pushReplacement(location, extra: extra); @@ -103,7 +103,7 @@ extension GoRouterHelper on BuildContext { /// See also: /// * [push] which pushes the given location onto the page stack. /// * [pushReplacement] which replaces the top-most page of the page stack but - /// always use a new page key. + /// always uses a new page key. void replace(String location, {Object? extra}) => GoRouter.of(this).replace(location, extra: extra); @@ -114,7 +114,7 @@ extension GoRouterHelper on BuildContext { /// See also: /// * [pushNamed] which pushes the given location onto the page stack. /// * [pushReplacementNamed] which replaces the top-most page of the page - /// stack but always use a new page key. + /// stack but always uses a new page key. void replaceNamed( String name, { Map params = const {}, diff --git a/packages/go_router/lib/src/page_key.dart b/packages/go_router/lib/src/page_key.dart deleted file mode 100644 index 75b0da4e3cf..00000000000 --- a/packages/go_router/lib/src/page_key.dart +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2013 The Flutter Authors. All rights reserved. -// 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'; - -/// A key go router uses for its pages. -class PageKey extends LocalKey { - /// A key go router uses for its pages. - const PageKey({ - required this.path, - this.count = 0, - }); - - /// The path of the page. For example: - /// ```dart - /// '/family/:fid' - /// ``` - final String path; - - /// An integer that is incremented each time a new page is created. This is - /// used to generate an unique/different key for 2 pages with the same path. - final int count; - - @override - bool operator ==(Object other) { - if (other.runtimeType != runtimeType) { - return false; - } - return other is PageKey && other.path == path && other.count == count; - } - - @override - int get hashCode => Object.hash(runtimeType, path, count); - - @override - String toString() { - return 'PageKey($path, $count)'; - } - - /// The full path of the page. For example: - /// - /// ```dart - /// '/family/:fid-p2' - /// ``` - String get fullPath => '$path-p$count'; -} diff --git a/packages/go_router/lib/src/redirection.dart b/packages/go_router/lib/src/redirection.dart index 71a5612e0ec..3ebef5cf294 100644 --- a/packages/go_router/lib/src/redirection.dart +++ b/packages/go_router/lib/src/redirection.dart @@ -10,7 +10,6 @@ import 'configuration.dart'; import 'logging.dart'; import 'match.dart'; import 'matching.dart'; -import 'page_key.dart'; /// A GoRouter redirector function. typedef RouteRedirector = FutureOr Function( @@ -101,7 +100,7 @@ FutureOr redirect( queryParams: prevMatchList.uri.queryParameters, queryParametersAll: prevMatchList.uri.queryParametersAll, extra: extra, - pageKey: const PageKey(path: 'topLevel'), + pageKey: const ValueKey('topLevel'), ), ); diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index ad3ce1158c4..ed8765d92b1 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -208,8 +208,8 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// See also: /// * [pushReplacement] which replaces the top-most page of the page stack and /// always use a new page key. - /// * [replace] which replaces the top-most page of the page stack and keeps - /// the page key when possible. + /// * [replace] which replaces the top-most page of the page stack and reuses + /// the page key. void push(String location, {Object? extra}) { assert(() { log.info('pushing $location'); @@ -246,8 +246,8 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// See also: /// * [go] which navigates to the location. /// * [push] which pushes the given location onto the page stack. - /// * [replace] which replaces the top-most page of the page stack but keeps - /// the page key when possible. + /// * [replace] which replaces the top-most page of the page stack but reuses + /// the page key. void pushReplacement(String location, {Object? extra}) { routeInformationParser .parseRouteInformationWithDependencies( @@ -285,7 +285,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// See also: /// * [push] which pushes the given location onto the page stack. /// * [pushReplacement] which replaces the top-most page of the page stack but - /// always use a new page key. + /// always uses a new page key. void replace(String location, {Object? extra}) { routeInformationParser .parseRouteInformationWithDependencies( @@ -306,7 +306,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// See also: /// * [pushNamed] which pushes the given location onto the page stack. /// * [pushReplacementNamed] which replaces the top-most page of the page - /// stack but always use a new page key. + /// stack but always uses a new page key. void replaceNamed( String name, { Map params = const {}, diff --git a/packages/go_router/lib/src/state.dart b/packages/go_router/lib/src/state.dart index c9c5693bfa6..9fc830d2ea4 100644 --- a/packages/go_router/lib/src/state.dart +++ b/packages/go_router/lib/src/state.dart @@ -6,7 +6,6 @@ import 'package:flutter/widgets.dart'; import 'configuration.dart'; import 'misc/errors.dart'; -import 'page_key.dart'; /// The route state during routing. /// @@ -67,9 +66,9 @@ class GoRouterState { /// A unique string key for this sub-route. /// E.g. /// ```dart - /// PageKey(path: '/family/:fid', count: 0) + /// ValueKey('/family/:fid') /// ``` - final PageKey pageKey; + final ValueKey pageKey; /// Gets the [GoRouterState] from context. /// diff --git a/packages/go_router/test/builder_test.dart b/packages/go_router/test/builder_test.dart index 9a096f662c2..04e75432df9 100644 --- a/packages/go_router/test/builder_test.dart +++ b/packages/go_router/test/builder_test.dart @@ -8,7 +8,6 @@ import 'package:go_router/src/builder.dart'; import 'package:go_router/src/configuration.dart'; import 'package:go_router/src/match.dart'; import 'package:go_router/src/matching.dart'; -import 'package:go_router/src/page_key.dart'; void main() { group('RouteBuilder', () { @@ -36,7 +35,7 @@ void main() { subloc: '/', extra: null, error: null, - pageKey: const PageKey(path: '/'), + pageKey: const ValueKey('/'), ), ], Uri.parse('/'), @@ -83,7 +82,7 @@ void main() { subloc: '/', extra: null, error: null, - pageKey: const PageKey(path: '/'), + pageKey: const ValueKey('/'), ), ], Uri.parse('/'), @@ -125,7 +124,7 @@ void main() { subloc: '/', extra: null, error: null, - pageKey: const PageKey(path: '/'), + pageKey: const ValueKey('/'), ), ], Uri.parse('/'), @@ -180,14 +179,14 @@ void main() { subloc: '', extra: null, error: null, - pageKey: const PageKey(path: ''), + pageKey: const ValueKey(''), ), RouteMatch( route: config.routes.first.routes.first, subloc: '/details', extra: null, error: null, - pageKey: const PageKey(path: '/details'), + pageKey: const ValueKey('/details'), ), ], Uri.parse('/details'), @@ -255,7 +254,7 @@ void main() { subloc: '/a/details', extra: null, error: null, - pageKey: const PageKey(path: '/a/details'), + pageKey: const ValueKey('/a/details'), ), ], Uri.parse('/a/details'), diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart index b70eae71aea..b6cc6bc6b65 100644 --- a/packages/go_router/test/delegate_test.dart +++ b/packages/go_router/test/delegate_test.dart @@ -67,7 +67,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches[1].pageKey, - const PageKey(path: '/a'), + const ValueKey('/a-p0'), ); goRouter.push('/a'); @@ -76,7 +76,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 3); expect( goRouter.routerDelegate.matches.matches[2].pageKey, - const PageKey(path: '/a', count: 1), + const ValueKey('/a-p1'), ); }, ); @@ -166,7 +166,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/a'), + const ValueKey('/a-p0'), ); goRouter.pushReplacement('/a'); @@ -175,7 +175,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/a', count: 1), + const ValueKey('/a-p1'), ); }, ); @@ -280,7 +280,7 @@ void main() { }); testWidgets( - 'It should use the same pageKey when replace is called with the same path', + 'It should use the same pageKey when replace is called (with the same path)', (WidgetTester tester) async { final GoRouter goRouter = await createGoRouter(tester); expect(goRouter.routerDelegate.matches.matches.length, 1); @@ -295,7 +295,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/a'), + const ValueKey('/a-p0'), ); goRouter.replace('/a'); @@ -304,13 +304,13 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/a'), + const ValueKey('/a-p0'), ); }, ); testWidgets( - 'It should use a new pageKey when replace is called with a different path', + 'It should use the same pageKey when replace is called (with a different path)', (WidgetTester tester) async { final GoRouter goRouter = await createGoRouter(tester); expect(goRouter.routerDelegate.matches.matches.length, 1); @@ -325,7 +325,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/a'), + const ValueKey('/a-p0'), ); goRouter.replace('/'); @@ -334,7 +334,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/'), + const ValueKey('/a-p0'), ); }, ); @@ -418,7 +418,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/page-0'), + const ValueKey('/page-0-p0'), ); goRouter.replaceNamed('page0'); @@ -427,7 +427,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/page-0'), + const ValueKey('/page-0-p0'), ); }, ); @@ -448,7 +448,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/page-0'), + const ValueKey('/page-0-p0'), ); goRouter.replaceNamed('home'); @@ -457,7 +457,7 @@ void main() { expect(goRouter.routerDelegate.matches.matches.length, 2); expect( goRouter.routerDelegate.matches.matches.last.pageKey, - const PageKey(path: '/'), + const ValueKey('/page-0-p0'), ); }, ); From 72301c9f39c4fbc8c85982b31edd76172ce9625f Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Sun, 19 Feb 2023 19:21:27 +0800 Subject: [PATCH 7/9] docs: Update the doc of replace to mention animation and state --- packages/go_router/lib/src/delegate.dart | 10 ++++++---- packages/go_router/lib/src/misc/extensions.dart | 10 ++++++---- packages/go_router/lib/src/router.dart | 10 ++++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index e4a4119940c..6498a1d174e 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -100,8 +100,9 @@ class GoRouterDelegate extends RouterDelegate /// See also: /// * [pushReplacement] which replaces the top-most page of the page stack and /// always use a new page key. - /// * [replace] which replaces the top-most page of the page stack and reuses - /// the page key. + /// * [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) { assert(matches.last.route is! ShellRoute); @@ -164,8 +165,9 @@ class GoRouterDelegate extends RouterDelegate /// /// See also: /// * [push] which pushes the given location onto the page stack. - /// * [replace] which replaces the top-most page of the page stack but reuses - /// the page key. + /// * [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 pushReplacement(RouteMatchList matches) { assert(matches.last.route is! ShellRoute); _matchList.remove(_matchList.last); diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index 8798c0e1ef7..f1c23ce24fc 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -41,8 +41,9 @@ extension GoRouterHelper on BuildContext { /// See also: /// * [pushReplacement] which replaces the top-most page of the page stack and /// always uses a new page key. - /// * [replace] which replaces the top-most page of the page stack and reuses - /// the page key. + /// * [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); @@ -73,8 +74,9 @@ extension GoRouterHelper on BuildContext { /// See also: /// * [go] which navigates to the location. /// * [push] which pushes the given location onto the page stack. - /// * [replace] which replaces the top-most page of the page stack but reuses - /// the page key. + /// * [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 pushReplacement(String location, {Object? extra}) => GoRouter.of(this).pushReplacement(location, extra: extra); diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index ed8765d92b1..cfccab86de0 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -208,8 +208,9 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// See also: /// * [pushReplacement] which replaces the top-most page of the page stack and /// always use a new page key. - /// * [replace] which replaces the top-most page of the page stack and reuses - /// the page key. + /// * [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}) { assert(() { log.info('pushing $location'); @@ -246,8 +247,9 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// See also: /// * [go] which navigates to the location. /// * [push] which pushes the given location onto the page stack. - /// * [replace] which replaces the top-most page of the page stack but reuses - /// the page key. + /// * [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 pushReplacement(String location, {Object? extra}) { routeInformationParser .parseRouteInformationWithDependencies( From 7be19634db14e8cde7aa48fdc476adbdd6e74a43 Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Wed, 22 Feb 2023 09:20:04 +0800 Subject: [PATCH 8/9] docs: Add more doc to replace and replaceNamed --- packages/go_router/lib/src/misc/extensions.dart | 7 +++++-- packages/go_router/lib/src/router.dart | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index f1c23ce24fc..0c60f98b038 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -100,7 +100,9 @@ extension GoRouterHelper on BuildContext { extra: extra, ); - /// Replaces the top-most page of the page stack with the given one. + /// Replaces the top-most page of the page stack with the given one but treats + /// it as the same page. The page key will be reused. This will preserve the + /// state and not run any page animation. /// /// See also: /// * [push] which pushes the given location onto the page stack. @@ -111,7 +113,8 @@ extension GoRouterHelper on BuildContext { /// Replaces the top-most page of the page stack with the named route w/ /// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': - /// 'p1'}`. + /// 'p1'}`. But it treats it as the same page. The page key will be reused. + /// This will preserve the state and not run any page animation. /// /// See also: /// * [pushNamed] which pushes the given location onto the page stack. diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index cfccab86de0..3eab6104a20 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -282,7 +282,9 @@ class GoRouter extends ChangeNotifier implements RouterConfig { ); } - /// Replaces the top-most page of the page stack with the given one. + /// Replaces the top-most page of the page stack with the given one but treats + /// it as the same page. The page key will be reused. This will preserve the + /// state and not run any page animation. /// /// See also: /// * [push] which pushes the given location onto the page stack. @@ -303,7 +305,8 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// Replaces the top-most page of the page stack with the named route w/ /// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': - /// 'p1'}`. + /// 'p1'}`. But it treats it as the same page. The page key will be reused. + /// This will preserve the state and not run any page animation. /// /// See also: /// * [pushNamed] which pushes the given location onto the page stack. From 58853df8c5b0a416f28250b42378932fcfc7721a Mon Sep 17 00:00:00 2001 From: ValentinVignal Date: Sun, 5 Mar 2023 12:40:22 +0800 Subject: [PATCH 9/9] docs: Improve the documentation --- packages/go_router/lib/src/delegate.dart | 12 ++++++++---- packages/go_router/lib/src/misc/extensions.dart | 16 ++++++++++------ packages/go_router/lib/src/router.dart | 16 ++++++++++------ 3 files changed, 28 insertions(+), 16 deletions(-) diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index 6498a1d174e..94402661afc 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -160,8 +160,9 @@ class GoRouterDelegate extends RouterDelegate return true; } - /// Replaces the top-most page of the page stack with the given one. The page - /// key of the new page will always be different from the old one. + /// Replaces the top-most page of the page stack with the given one. + /// + /// The page key of the new page will always be different from the old one. /// /// See also: /// * [push] which pushes the given location onto the page stack. @@ -174,8 +175,11 @@ class GoRouterDelegate extends RouterDelegate push(matches); // [push] will notify the listeners. } - /// Replaces the top-most page of the page stack with the given one and reuse - /// the page key when the path didn't change. + /// Replaces the top-most page of the page stack with the given one but treats + /// it as the same page. + /// + /// The page key will be reused. This will preserve the state and not run any + /// page animation. /// /// See also: /// * [push] which pushes the given location onto the page stack. diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index 0c60f98b038..0900588a4ff 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -101,8 +101,10 @@ extension GoRouterHelper on BuildContext { ); /// Replaces the top-most page of the page stack with the given one but treats - /// it as the same page. The page key will be reused. This will preserve the - /// state and not run any page animation. + /// it as the same page. + /// + /// The page key will be reused. This will preserve the state and not run any + /// page animation. /// /// See also: /// * [push] which pushes the given location onto the page stack. @@ -111,10 +113,12 @@ extension GoRouterHelper on BuildContext { void replace(String location, {Object? extra}) => GoRouter.of(this).replace(location, extra: extra); - /// Replaces the top-most page of the page stack with the named route w/ - /// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': - /// 'p1'}`. But it treats it as the same page. The page key will be reused. - /// This will preserve the state and not run any page animation. + /// Replaces the top-most page with the named route and optional parameters, + /// preserving the page key. + /// + /// This will preserve the state and not run any page animation. Optional + /// parameters can be providded to the named route, e.g. `name='person', + /// params={'fid': 'f2', 'pid': 'p1'}`. /// /// See also: /// * [pushNamed] which pushes the given location onto the page stack. diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index eb169831a73..148ab6ee457 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -283,8 +283,10 @@ class GoRouter extends ChangeNotifier implements RouterConfig { } /// Replaces the top-most page of the page stack with the given one but treats - /// it as the same page. The page key will be reused. This will preserve the - /// state and not run any page animation. + /// it as the same page. + /// + /// The page key will be reused. This will preserve the state and not run any + /// page animation. /// /// See also: /// * [push] which pushes the given location onto the page stack. @@ -303,10 +305,12 @@ class GoRouter extends ChangeNotifier implements RouterConfig { }); } - /// Replaces the top-most page of the page stack with the named route w/ - /// optional parameters, e.g. `name='person', params={'fid': 'f2', 'pid': - /// 'p1'}`. But it treats it as the same page. The page key will be reused. - /// This will preserve the state and not run any page animation. + /// Replaces the top-most page with the named route and optional parameters, + /// preserving the page key. + /// + /// This will preserve the state and not run any page animation. Optional + /// parameters can be providded to the named route, e.g. `name='person', + /// params={'fid': 'f2', 'pid': 'p1'}`. /// /// See also: /// * [pushNamed] which pushes the given location onto the page stack.