diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md index a0d78041b85..d46e9de8c32 100644 --- a/packages/go_router/CHANGELOG.md +++ b/packages/go_router/CHANGELOG.md @@ -1,3 +1,19 @@ +## 7.0.0 + +- **BREAKING CHANGE**: + - For the below changes, run `dart fix --apply` to automatically migrate your code. + - `GoRouteState.subloc` has been renamed to `GoRouteState.matchedLocation`. + - `GoRouteState.params` has been renamed to `GoRouteState.pathParameters`. + - `GoRouteState.fullpath` has been renamed to `GoRouteState.fullPath`. + - `GoRouteState.queryParams` has been renamed to `GoRouteState.queryParameters`. + - `params` and `queryParams` in `GoRouteState.namedLocation` have been renamed to `pathParameters` and `queryParameters`. + - `params` and `queryParams` in `GoRouter`'s `namedLocation`, `pushNamed`, `pushReplacementNamed` + `replaceNamed` have been renamed to `pathParameters` and `queryParameters`. + - For the below changes, please follow the [migration guide](https://docs.google.com/document/d/10Xbpifbs4E-zh6YE5akIO8raJq_m3FIXs6nUGdOspOg). + - `params` and `queryParams` in `BuildContext`'s `namedLocation`, `pushNamed`, `pushReplacementNamed` + `replaceNamed` have been renamed to `pathParameters` and `queryParameters`. +- Cleans up API and makes RouteMatchList immutable. + ## 6.5.9 - Removes navigator keys from `GoRouteData` and `ShellRouteData`. diff --git a/packages/go_router/README.md b/packages/go_router/README.md index daed2191536..eae5297abd6 100644 --- a/packages/go_router/README.md +++ b/packages/go_router/README.md @@ -37,6 +37,7 @@ See the API documentation for details on the following topics: - [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html) ## Migration guides +- [Migrating to 7.0.0](https://docs.google.com/document/d/10Xbpifbs4E-zh6YE5akIO8raJq_m3FIXs6nUGdOspOg). - [Migrating to 6.0.0](https://flutter.dev/go/go-router-v6-breaking-changes) - [Migrating to 5.1.2](https://flutter.dev/go/go-router-v5-1-2-breaking-changes) - [Migrating to 5.0](https://flutter.dev/go/go-router-v5-breaking-changes) diff --git a/packages/go_router/analysis_options.yaml b/packages/go_router/analysis_options.yaml new file mode 100644 index 00000000000..cfb845a14d7 --- /dev/null +++ b/packages/go_router/analysis_options.yaml @@ -0,0 +1,5 @@ +include: ../../analysis_options.yaml + +analyzer: + exclude: + - "test_fixes/**" diff --git a/packages/go_router/doc/configuration.md b/packages/go_router/doc/configuration.md index ef4658a8fea..09093d07793 100644 --- a/packages/go_router/doc/configuration.md +++ b/packages/go_router/doc/configuration.md @@ -43,7 +43,7 @@ the builder callback: ```dart GoRoute( path: '/users/:userId', - builder: (context, state) => const UserScreen(id: state.params['userId']), + builder: (context, state) => const UserScreen(id: state.pathParameters['userId']), ), ``` @@ -55,7 +55,7 @@ after the `?`), use [GoRouterState][]. For example, a URL path such as ```dart GoRoute( path: '/users', - builder: (context, state) => const UsersScreen(filter: state.queryParams['filter']), + builder: (context, state) => const UsersScreen(filter: state.queryParameters['filter']), ), ``` diff --git a/packages/go_router/doc/named-routes.md b/packages/go_router/doc/named-routes.md index 54705d12cc1..861277cc36d 100644 --- a/packages/go_router/doc/named-routes.md +++ b/packages/go_router/doc/named-routes.md @@ -14,7 +14,7 @@ To navigate to a route using its name, call [`goNamed`](https://pub.dev/document ```dart TextButton( onPressed: () { - context.goNamed('song', params: {'songId': 123}); + context.goNamed('song', pathParameters: {'songId': 123}); }, child: const Text('Go to song 2'), ), @@ -25,7 +25,7 @@ Alternatively, you can look up the location for a name using `namedLocation`: ```dart TextButton( onPressed: () { - final String location = context.namedLocation('song', params: {'songId': 123}); + final String location = context.namedLocation('song', pathParameters: {'songId': 123}); context.go(location); }, child: const Text('Go to song 2'), diff --git a/packages/go_router/example/lib/async_redirection.dart b/packages/go_router/example/lib/async_redirection.dart index d91db6ed2dd..f84c14e8afc 100644 --- a/packages/go_router/example/lib/async_redirection.dart +++ b/packages/go_router/example/lib/async_redirection.dart @@ -55,7 +55,7 @@ class App extends StatelessWidget { // cause go_router to reparse current route if StreamAuth has new sign-in // information. final bool loggedIn = await StreamAuthScope.of(context).isSignedIn(); - final bool loggingIn = state.subloc == '/login'; + final bool loggingIn = state.matchedLocation == '/login'; if (!loggedIn) { return '/login'; } diff --git a/packages/go_router/example/lib/books/main.dart b/packages/go_router/example/lib/books/main.dart index 73cb6f35cbb..eb757e3de4f 100644 --- a/packages/go_router/example/lib/books/main.dart +++ b/packages/go_router/example/lib/books/main.dart @@ -63,7 +63,7 @@ class Bookstore extends StatelessWidget { GoRoute( path: '/book/:bookId', redirect: (BuildContext context, GoRouterState state) => - '/books/all/${state.params['bookId']}', + '/books/all/${state.pathParameters['bookId']}', ), GoRoute( path: '/books/:kind(new|all|popular)', @@ -72,14 +72,14 @@ class Bookstore extends StatelessWidget { key: _scaffoldKey, child: BookstoreScaffold( selectedTab: ScaffoldTab.books, - child: BooksScreen(state.params['kind']!), + child: BooksScreen(state.pathParameters['kind']!), ), ), routes: [ GoRoute( path: ':bookId', builder: (BuildContext context, GoRouterState state) { - final String bookId = state.params['bookId']!; + final String bookId = state.pathParameters['bookId']!; final Book? selectedBook = libraryInstance.allBooks .firstWhereOrNull((Book b) => b.id.toString() == bookId); @@ -91,7 +91,7 @@ class Bookstore extends StatelessWidget { GoRoute( path: '/author/:authorId', redirect: (BuildContext context, GoRouterState state) => - '/authors/${state.params['authorId']}', + '/authors/${state.pathParameters['authorId']}', ), GoRoute( path: '/authors', @@ -107,7 +107,7 @@ class Bookstore extends StatelessWidget { GoRoute( path: ':authorId', builder: (BuildContext context, GoRouterState state) { - final int authorId = int.parse(state.params['authorId']!); + final int authorId = int.parse(state.pathParameters['authorId']!); final Author? selectedAuthor = libraryInstance.allAuthors .firstWhereOrNull((Author a) => a.id == authorId); @@ -135,7 +135,7 @@ class Bookstore extends StatelessWidget { String? _guard(BuildContext context, GoRouterState state) { final bool signedIn = _auth.signedIn; - final bool signingIn = state.subloc == '/signin'; + final bool signingIn = state.matchedLocation == '/signin'; // Go to /signin if the user is not signed in if (!signedIn && !signingIn) { diff --git a/packages/go_router/example/lib/named_routes.dart b/packages/go_router/example/lib/named_routes.dart index a9d4604dddf..9685fc4a874 100644 --- a/packages/go_router/example/lib/named_routes.dart +++ b/packages/go_router/example/lib/named_routes.dart @@ -84,14 +84,15 @@ class App extends StatelessWidget { name: 'family', path: 'family/:fid', builder: (BuildContext context, GoRouterState state) => - FamilyScreen(fid: state.params['fid']!), + FamilyScreen(fid: state.pathParameters['fid']!), routes: [ GoRoute( name: 'person', path: 'person/:pid', builder: (BuildContext context, GoRouterState state) { return PersonScreen( - fid: state.params['fid']!, pid: state.params['pid']!); + fid: state.pathParameters['fid']!, + pid: state.pathParameters['pid']!); }, ), ], @@ -119,7 +120,7 @@ class HomeScreen extends StatelessWidget { ListTile( title: Text(entry.value.name), onTap: () => context.go(context.namedLocation('family', - params: {'fid': entry.key})), + pathParameters: {'fid': entry.key})), ) ], ), @@ -147,8 +148,8 @@ class FamilyScreen extends StatelessWidget { title: Text(entry.value.name), onTap: () => context.go(context.namedLocation( 'person', - params: {'fid': fid, 'pid': entry.key}, - queryParams: {'qid': 'quid'}, + pathParameters: {'fid': fid, 'pid': entry.key}, + queryParameters: {'qid': 'quid'}, )), ), ], diff --git a/packages/go_router/example/lib/others/nav_observer.dart b/packages/go_router/example/lib/others/nav_observer.dart index 2b8bfcb97c9..038d337c08b 100644 --- a/packages/go_router/example/lib/others/nav_observer.dart +++ b/packages/go_router/example/lib/others/nav_observer.dart @@ -108,8 +108,8 @@ class Page1Screen extends StatelessWidget { ElevatedButton( onPressed: () => context.goNamed( 'page2', - params: {'p1': 'pv1'}, - queryParams: {'q1': 'qv1'}, + pathParameters: {'p1': 'pv1'}, + queryParameters: {'q1': 'qv1'}, ), child: const Text('Go to page 2'), ), @@ -134,7 +134,7 @@ class Page2Screen extends StatelessWidget { ElevatedButton( onPressed: () => context.goNamed( 'page3', - params: {'p1': 'pv2'}, + pathParameters: {'p1': 'pv2'}, ), child: const Text('Go to page 3'), ), diff --git a/packages/go_router/example/lib/others/push.dart b/packages/go_router/example/lib/others/push.dart index 53567b6de28..06e34695d96 100644 --- a/packages/go_router/example/lib/others/push.dart +++ b/packages/go_router/example/lib/others/push.dart @@ -32,7 +32,7 @@ class App extends StatelessWidget { path: '/page2', builder: (BuildContext context, GoRouterState state) => Page2ScreenWithPush( - int.parse(state.queryParams['push-count']!), + int.parse(state.queryParameters['push-count']!), ), ), ], diff --git a/packages/go_router/example/lib/path_and_query_parameters.dart b/packages/go_router/example/lib/path_and_query_parameters.dart index 83d7b631f3e..bf915a273e8 100755 --- a/packages/go_router/example/lib/path_and_query_parameters.dart +++ b/packages/go_router/example/lib/path_and_query_parameters.dart @@ -9,9 +9,9 @@ import 'package:go_router/go_router.dart'; // // The route segments that start with ':' are treated as path parameters when // defining GoRoute[s]. The parameter values can be accessed through -// GoRouterState.params. +// GoRouterState.pathParameters. // -// The query parameters are automatically stored in GoRouterState.queryParams. +// The query parameters are automatically stored in GoRouterState.queryParameters. /// Family data class. class Family { @@ -84,8 +84,8 @@ class App extends StatelessWidget { path: 'family/:fid', builder: (BuildContext context, GoRouterState state) { return FamilyScreen( - fid: state.params['fid']!, - asc: state.queryParams['sort'] == 'asc', + fid: state.pathParameters['fid']!, + asc: state.queryParameters['sort'] == 'asc', ); }), ], @@ -149,7 +149,8 @@ class FamilyScreen extends StatelessWidget { actions: [ IconButton( onPressed: () => context.goNamed('family', - params: {'fid': fid}, queryParams: newQueries), + pathParameters: {'fid': fid}, + queryParameters: newQueries), tooltip: 'sort ascending or descending', icon: const Icon(Icons.sort), ) diff --git a/packages/go_router/example/lib/redirection.dart b/packages/go_router/example/lib/redirection.dart index 4e97f666084..868944c0b63 100644 --- a/packages/go_router/example/lib/redirection.dart +++ b/packages/go_router/example/lib/redirection.dart @@ -75,7 +75,7 @@ class App extends StatelessWidget { redirect: (BuildContext context, GoRouterState state) { // if the user is not logged in, they need to login final bool loggedIn = _loginInfo.loggedIn; - final bool loggingIn = state.subloc == '/login'; + final bool loggingIn = state.matchedLocation == '/login'; if (!loggedIn) { return '/login'; } diff --git a/packages/go_router/lib/fix_data.yaml b/packages/go_router/lib/fix_data.yaml new file mode 100644 index 00000000000..99f978243a0 --- /dev/null +++ b/packages/go_router/lib/fix_data.yaml @@ -0,0 +1,151 @@ +# Copyright 2014 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. + +# For details regarding the *Flutter Fix* feature, see +# https://flutter.dev/docs/development/tools/flutter-fix + +# Please add new fixes to the top of the file, separated by one blank line +# from other fixes. In a comment, include a link to the PR where the change +# requiring the fix was made. + +# Every fix must be tested. See the flutter/packages/flutter/test_fixes/README.md +# file for instructions on testing these data driven fixes. + +# For documentation about this file format, see +# https://dart.dev/go/data-driven-fixes + +version: 1 +transforms: + - title: "Replaces 'params' and 'queryParams' in 'GoRouter.replaceNamed' with `pathParameters` and `queryParameters`" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + method: 'replaceNamed' + inClass: 'GoRouter' + changes: + - kind: 'renameParameter' + oldName: 'params' + newName: 'pathParameters' + - kind: 'renameParameter' + oldName: 'queryParams' + newName: 'queryParameters' + - title: "Replaces 'params' and 'queryParams' in 'GoRouter.pushReplacementNamed' with `pathParameters` and `queryParameters`" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + method: 'pushReplacementNamed' + inClass: 'GoRouter' + changes: + - kind: 'renameParameter' + oldName: 'params' + newName: 'pathParameters' + - kind: 'renameParameter' + oldName: 'queryParams' + newName: 'queryParameters' + + - title: "Replaces 'params' and 'queryParams' in 'GoRouter.pushNamed' with `pathParameters` and `queryParameters`" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + method: 'pushNamed' + inClass: 'GoRouter' + changes: + - kind: 'renameParameter' + oldName: 'params' + newName: 'pathParameters' + - kind: 'renameParameter' + oldName: 'queryParams' + newName: 'queryParameters' + + - title: "Replaces 'params' and 'queryParams' in 'GoRouter.goNamed' with `pathParameters` and `queryParameters`" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + method: 'goNamed' + inClass: 'GoRouter' + changes: + - kind: 'renameParameter' + oldName: 'params' + newName: 'pathParameters' + - kind: 'renameParameter' + oldName: 'queryParams' + newName: 'queryParameters' + + - title: "Replaces 'params' and 'queryParams' in 'GoRouter.namedLocation' with `pathParameters` and `queryParameters`" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + method: 'namedLocation' + inClass: 'GoRouter' + changes: + - kind: 'renameParameter' + oldName: 'params' + newName: 'pathParameters' + - kind: 'renameParameter' + oldName: 'queryParams' + newName: 'queryParameters' + + - title: "Replaces 'params' and 'queryParams' in 'GoRouterState.namedLocation' with `pathParameters` and `queryParameters`" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + method: 'namedLocation' + inClass: 'GoRouterState' + changes: + - kind: 'renameParameter' + oldName: 'params' + newName: 'pathParameters' + - kind: 'renameParameter' + oldName: 'queryParams' + newName: 'queryParameters' + + - title: "Replaces 'GoRouterState.queryParams' with 'GoRouterState.queryParameters'" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + field: 'queryParams' + inClass: 'GoRouterState' + changes: + - kind: 'rename' + newName: 'queryParameters' + + - title: "Replaces 'GoRouterState.fullpath' with 'GoRouterState.fullPath'" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + field: 'fullpath' + inClass: 'GoRouterState' + changes: + - kind: 'rename' + newName: 'fullPath' + + - title: "Replaces 'GoRouterState.params' with 'GoRouterState.pathParameters'" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + field: 'params' + inClass: 'GoRouterState' + changes: + - kind: 'rename' + newName: 'pathParameters' + + - title: "Replaces 'GoRouterState.subloc' with 'GoRouterState.matchedLocation'" + date: 2023-04-24 + bulkApply: true + element: + uris: [ 'go_router.dart' ] + field: 'subloc' + inClass: 'GoRouterState' + changes: + - kind: 'rename' + newName: 'matchedLocation' diff --git a/packages/go_router/lib/src/builder.dart b/packages/go_router/lib/src/builder.dart index 681f1d2ec44..c497a79f437 100644 --- a/packages/go_router/lib/src/builder.dart +++ b/packages/go_router/lib/src/builder.dart @@ -6,7 +6,6 @@ import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'configuration.dart'; -import 'delegate.dart'; import 'logging.dart'; import 'match.dart'; import 'matching.dart'; @@ -254,7 +253,7 @@ class RouteBuilder { } /// Helper method that builds a [GoRouterState] object for the given [match] - /// and [params]. + /// and [pathParameters]. @visibleForTesting GoRouterState buildState(RouteMatchList matchList, RouteMatch match) { final RouteBase route = match.route; @@ -269,13 +268,14 @@ class RouteBuilder { return GoRouterState( configuration, location: effectiveMatchList.uri.toString(), - subloc: match.subloc, + matchedLocation: match.matchedLocation, name: name, path: path, - fullpath: effectiveMatchList.fullpath, - params: Map.from(effectiveMatchList.pathParameters), + fullPath: effectiveMatchList.fullPath, + pathParameters: + Map.from(effectiveMatchList.pathParameters), error: match.error, - queryParams: effectiveMatchList.uri.queryParameters, + queryParameters: effectiveMatchList.uri.queryParameters, queryParametersAll: effectiveMatchList.uri.queryParametersAll, extra: match.extra, pageKey: match.pageKey, @@ -397,7 +397,10 @@ class RouteBuilder { return _pageBuilderForAppType!( key: state.pageKey, name: state.name ?? state.path, - arguments: {...state.params, ...state.queryParams}, + arguments: { + ...state.pathParameters, + ...state.queryParameters + }, restorationId: state.pageKey.value, child: child, ); @@ -444,9 +447,9 @@ class RouteBuilder { final GoRouterState state = GoRouterState( configuration, location: uri.toString(), - subloc: uri.path, + matchedLocation: uri.path, name: null, - queryParams: uri.queryParameters, + queryParameters: uri.queryParameters, queryParametersAll: uri.queryParametersAll, error: Exception(error), pageKey: const ValueKey('error'), diff --git a/packages/go_router/lib/src/configuration.dart b/packages/go_router/lib/src/configuration.dart index c17b7088a97..2c2f8e77678 100644 --- a/packages/go_router/lib/src/configuration.dart +++ b/packages/go_router/lib/src/configuration.dart @@ -97,7 +97,7 @@ class RouteConfiguration { if (route is! GoRoute) { continue; } - for (final String pathParam in route.pathParams) { + for (final String pathParam in route.pathParameters) { if (usedPathParams.containsKey(pathParam)) { final bool sameRoute = usedPathParams[pathParam] == route; throw GoError( @@ -106,7 +106,7 @@ class RouteConfiguration { usedPathParams[pathParam] = route; } _debugVerifyNoDuplicatePathParameter(route.routes, usedPathParams); - route.pathParams.forEach(usedPathParams.remove); + route.pathParameters.forEach(usedPathParams.remove); } return true; } @@ -128,14 +128,14 @@ class RouteConfiguration { /// Looks up the url location by a [GoRoute]'s name. String namedLocation( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, }) { assert(() { log.info('getting location for name: ' '"$name"' - '${params.isEmpty ? '' : ', params: $params'}' - '${queryParams.isEmpty ? '' : ', queryParams: $queryParams'}'); + '${pathParameters.isEmpty ? '' : ', pathParameters: $pathParameters'}' + '${queryParameters.isEmpty ? '' : ', queryParameters: $queryParameters'}'); return true; }()); final String keyName = name.toLowerCase(); @@ -146,24 +146,24 @@ class RouteConfiguration { final List paramNames = []; patternToRegExp(path, paramNames); for (final String paramName in paramNames) { - assert(params.containsKey(paramName), + assert(pathParameters.containsKey(paramName), 'missing param "$paramName" for $path'); } // Check that there are no extra params - for (final String key in params.keys) { + for (final String key in pathParameters.keys) { assert(paramNames.contains(key), 'unknown param "$key" for $path'); } return true; }()); final Map encodedParams = { - for (final MapEntry param in params.entries) + for (final MapEntry param in pathParameters.entries) param.key: Uri.encodeComponent(param.value) }; final String location = patternToPath(path, encodedParams); return Uri( path: location, - queryParameters: queryParams.isEmpty ? null : queryParams) + queryParameters: queryParameters.isEmpty ? null : queryParameters) .toString(); } @@ -196,9 +196,9 @@ class RouteConfiguration { int depth, StringBuffer sb) { for (final RouteBase route in routes) { if (route is GoRoute) { - final String fullpath = concatenatePaths(parentFullpath, route.path); - sb.writeln(' => ${''.padLeft(depth * 2)}$fullpath'); - _debugFullPathsFor(route.routes, fullpath, depth + 1, sb); + final String fullPath = concatenatePaths(parentFullpath, route.path); + sb.writeln(' => ${''.padLeft(depth * 2)}$fullPath'); + _debugFullPathsFor(route.routes, fullPath, depth + 1, sb); } else if (route is ShellRoute) { _debugFullPathsFor(route.routes, parentFullpath, depth, sb); } diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart index 279c10d715e..a62906adf1b 100644 --- a/packages/go_router/lib/src/delegate.dart +++ b/packages/go_router/lib/src/delegate.dart @@ -86,15 +86,19 @@ class GoRouterDelegate extends RouterDelegate RouteMatchList matches, ValueKey pageKey) async { final ImperativeRouteMatch newPageKeyMatch = ImperativeRouteMatch( route: matches.last.route, - subloc: matches.last.subloc, + matchedLocation: matches.last.matchedLocation, extra: matches.last.extra, error: matches.last.error, pageKey: pageKey, matches: matches, ); - _matchList.push(newPageKeyMatch); - return newPageKeyMatch._future; + _matchList = _matchList.push(newPageKeyMatch); + return newPageKeyMatch.future; + } + + void _remove(RouteMatch match) { + _matchList = _matchList.remove(match); } /// Pushes the given location onto the page stack. @@ -108,7 +112,7 @@ class GoRouterDelegate extends RouterDelegate Future push(RouteMatchList matches) async { assert(matches.last.route is! ShellRoute); - final ValueKey pageKey = _getNewKeyForPath(matches.fullpath); + final ValueKey pageKey = _getNewKeyForPath(matches.fullPath); final Future future = _push(matches, pageKey); notifyListeners(); return future; @@ -155,7 +159,7 @@ class GoRouterDelegate extends RouterDelegate if (match is ImperativeRouteMatch) { match.complete(result); } - _matchList.remove(match!); + _remove(match!); notifyListeners(); assert(() { _debugAssertMatchListNotEmpty(); @@ -175,7 +179,7 @@ class GoRouterDelegate extends RouterDelegate /// state and not run any page animation. void pushReplacement(RouteMatchList matches) { assert(matches.last.route is! ShellRoute); - _matchList.remove(_matchList.last); + _remove(_matchList.last); push(matches); // [push] will notify the listeners. } @@ -193,7 +197,7 @@ class GoRouterDelegate extends RouterDelegate assert(matches.last.route is! ShellRoute); final RouteMatch routeMatch = _matchList.last; final ValueKey pageKey = routeMatch.pageKey; - _matchList.remove(routeMatch); + _remove(routeMatch); _push(matches, pageKey); notifyListeners(); } @@ -309,32 +313,3 @@ class _NavigatorStateIterator extends Iterator { return true; } } - -/// The route match that represent route pushed through [GoRouter.push]. -class ImperativeRouteMatch extends RouteMatch { - /// Constructor for [ImperativeRouteMatch]. - ImperativeRouteMatch({ - required super.route, - required super.subloc, - required super.extra, - 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; - - /// Called when the corresponding [Route] associated with this route match is - /// completed. - 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 0cb70417cfb..7c3c736bbeb 100644 --- a/packages/go_router/lib/src/match.dart +++ b/packages/go_router/lib/src/match.dart @@ -2,35 +2,44 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; + import 'package:flutter/widgets.dart'; import 'matching.dart'; import 'path_utils.dart'; import 'route.dart'; -/// An instance of a GoRoute plus information about the current location. +/// An matched result by matching a [RouteBase] against a location. +/// +/// This is typically created by calling [RouteMatch.match]. +@immutable class RouteMatch { /// Constructor for [RouteMatch]. - RouteMatch({ + const RouteMatch({ required this.route, - required this.subloc, + required this.matchedLocation, required this.extra, required this.error, required this.pageKey, }); - // ignore: public_member_api_docs + /// Generate a [RouteMatch] object by matching the `route` with + /// `remainingLocation`. + /// + /// The extracted path parameters, as the result of the matching, are stored + /// into `pathParameters`. static RouteMatch? match({ required RouteBase route, - required String restLoc, // e.g. person/p1 - required String parentSubloc, // e.g. /family/f2 + required String remainingLocation, // e.g. person/p1 + required String matchedLocation, // e.g. /family/f2 required Map pathParameters, required Object? extra, }) { if (route is ShellRoute) { return RouteMatch( route: route, - subloc: restLoc, + matchedLocation: remainingLocation, extra: extra, error: null, pageKey: ValueKey(route.hashCode.toString()), @@ -38,7 +47,7 @@ class RouteMatch { } else if (route is GoRoute) { assert(!route.path.contains('//')); - final RegExpMatch? match = route.matchPatternAsPrefix(restLoc); + final RegExpMatch? match = route.matchPatternAsPrefix(remainingLocation); if (match == null) { return null; } @@ -48,23 +57,31 @@ class RouteMatch { pathParameters[param.key] = Uri.decodeComponent(param.value); } final String pathLoc = patternToPath(route.path, encodedParams); - final String subloc = concatenatePaths(parentSubloc, pathLoc); + final String newMatchedLocation = + concatenatePaths(matchedLocation, pathLoc); return RouteMatch( route: route, - subloc: subloc, + matchedLocation: newMatchedLocation, extra: extra, error: null, pageKey: ValueKey(route.hashCode.toString()), ); } - throw MatcherError('Unexpected route type: $route', restLoc); + throw MatcherError('Unexpected route type: $route', remainingLocation); } /// The matched route. final RouteBase route; - /// The matched location. - final String subloc; // e.g. /family/f2 + /// The location string that matches the [route]. + /// + /// for example: + /// + /// uri = '/family/f2/person/p2' + /// route = GoRoute('/family/:id) + /// + /// matchedLocation = '/family/f2' + final String matchedLocation; /// An extra object to pass along with the navigation. final Object? extra; @@ -74,4 +91,59 @@ class RouteMatch { /// Value key of type string, to hold a unique reference to a page. final ValueKey pageKey; + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is RouteMatch && + route == other.route && + matchedLocation == other.matchedLocation && + extra == other.extra && + pageKey == other.pageKey; + } + + @override + int get hashCode => Object.hash(route, matchedLocation, extra, pageKey); +} + +/// The route match that represent route pushed through [GoRouter.push]. +class ImperativeRouteMatch extends RouteMatch { + /// Constructor for [ImperativeRouteMatch]. + ImperativeRouteMatch({ + required super.route, + required super.matchedLocation, + required super.extra, + 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; + + /// Called when the corresponding [Route] associated with this route match is + /// completed. + 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; + + // An ImperativeRouteMatch has its own life cycle due the the _completer. + // comparing _completer between instances would be the same thing as + // comparing object reference. + @override + bool operator ==(Object other) { + return identical(this, other); + } + + @override + int get hashCode => identityHashCode(this); } diff --git a/packages/go_router/lib/src/matching.dart b/packages/go_router/lib/src/matching.dart index b67c69bcd2c..5855204bd46 100644 --- a/packages/go_router/lib/src/matching.dart +++ b/packages/go_router/lib/src/matching.dart @@ -2,11 +2,11 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'configuration.dart'; -import 'delegate.dart'; import 'match.dart'; import 'path_utils.dart'; @@ -25,16 +25,17 @@ class RouteMatcher { final Map pathParameters = {}; final List matches = _getLocRouteMatches(uri, extra, pathParameters); - return RouteMatchList(matches, uri, pathParameters); + return RouteMatchList( + matches: matches, uri: uri, pathParameters: pathParameters); } List _getLocRouteMatches( Uri uri, Object? extra, Map pathParameters) { final List? result = _getLocRouteRecursively( - loc: uri.path, - restLoc: uri.path, + location: uri.path, + remainingLocation: uri.path, routes: configuration.routes, - parentSubloc: '', + matchedLocation: '', pathParameters: pathParameters, extra: extra, ); @@ -50,19 +51,50 @@ class RouteMatcher { /// The list of [RouteMatch] objects. /// /// This corresponds to the GoRouter's history. +@immutable class RouteMatchList { /// RouteMatchList constructor. - RouteMatchList(List matches, this._uri, this.pathParameters) - : _matches = matches, - fullpath = _generateFullPath(matches); + RouteMatchList({ + required this.matches, + required this.uri, + required this.pathParameters, + }) : fullPath = _generateFullPath(matches); /// Constructs an empty matches object. - static RouteMatchList empty = - RouteMatchList([], Uri.parse(''), const {}); + static RouteMatchList empty = RouteMatchList( + matches: const [], + uri: Uri(), + pathParameters: const {}); + + /// The route matches. + final List matches; + + /// Parameters for the matched route, URI-encoded. + /// + /// The parameters only reflects [RouteMatch]s that are not + /// [ImperativeRouteMatch]. + final Map pathParameters; + + /// The uri of the current match. + /// + /// This uri only reflects [RouteMatch]s that are not [ImperativeRouteMatch]. + final Uri uri; + + /// the full path pattern that matches the uri. + /// + /// For example: + /// + /// ```dart + /// '/family/:fid/person/:pid' + /// ``` + final String fullPath; /// Generates the full path (ex: `'/family/:fid/person/:pid'`) of a list of /// [RouteMatch]. /// + /// This method ignores [ImperativeRouteMatch]s in the `matches`, as they + /// don't contribute to the path. + /// /// This methods considers that [matches]'s elements verify the go route /// structure given to `GoRouter`. For example, if the routes structure is /// @@ -90,7 +122,8 @@ class RouteMatchList { static String _generateFullPath(Iterable matches) { final StringBuffer buffer = StringBuffer(); bool addsSlash = false; - for (final RouteMatch match in matches) { + for (final RouteMatch match in matches + .where((RouteMatch match) => match is! ImperativeRouteMatch)) { final RouteBase route = match.route; if (route is GoRoute) { if (addsSlash) { @@ -103,66 +136,61 @@ class RouteMatchList { return buffer.toString(); } - final List _matches; - - /// the full path pattern that matches the uri. - /// - /// For example: - /// - /// ```dart - /// '/family/:fid/person/:pid' - /// ``` - final String fullpath; - - /// Parameters for the matched route, URI-encoded. - final Map pathParameters; - - /// The uri of the current match. - Uri get uri => _uri; - Uri _uri; - /// Returns true if there are no matches. - bool get isEmpty => _matches.isEmpty; + bool get isEmpty => matches.isEmpty; /// Returns true if there are matches. - bool get isNotEmpty => _matches.isNotEmpty; + bool get isNotEmpty => matches.isNotEmpty; - /// Pushes a match onto the list of matches. - void push(RouteMatch match) { - _matches.add(match); + /// Returns a new instance of RouteMatchList with the input `match` pushed + /// onto the current instance. + RouteMatchList push(ImperativeRouteMatch match) { + // Imperative route match doesn't change the uri and path parameters. + return _copyWith(matches: [...matches, match]); } - /// Removes the match from the list. - void remove(RouteMatch match) { - final int index = _matches.indexOf(match); + /// Returns a new instance of RouteMatchList with the input `match` removed + /// from the current instance. + RouteMatchList remove(RouteMatch match) { + final List newMatches = matches.toList(); + final int index = newMatches.indexOf(match); assert(index != -1); - _matches.removeRange(index, _matches.length); + newMatches.removeRange(index, newMatches.length); // Also pop ShellRoutes when there are no subsequent route matches - while (_matches.isNotEmpty && _matches.last.route is ShellRoute) { - _matches.removeLast(); + while (newMatches.isNotEmpty && newMatches.last.route is ShellRoute) { + newMatches.removeLast(); + } + // Removing ImperativeRouteMatch should not change uri and pathParameters. + if (match is ImperativeRouteMatch) { + return _copyWith(matches: newMatches); } final String fullPath = _generateFullPath( - _matches.where((RouteMatch match) => match is! ImperativeRouteMatch)); + newMatches.where((RouteMatch match) => match is! ImperativeRouteMatch)); // Need to remove path parameters that are no longer in the fullPath. final List newParameters = []; patternToRegExp(fullPath, newParameters); final Set validParameters = newParameters.toSet(); - pathParameters.removeWhere( - (String key, String value) => !validParameters.contains(key)); - - _uri = _uri.replace(path: patternToPath(fullPath, pathParameters)); + final Map newPathParameters = + Map.fromEntries( + pathParameters.entries.where((MapEntry value) => + validParameters.contains(value.key)), + ); + final Uri newUri = + uri.replace(path: patternToPath(fullPath, newPathParameters)); + return _copyWith( + matches: newMatches, + uri: newUri, + pathParameters: newPathParameters, + ); } /// An optional object provided by the app during navigation. - Object? get extra => _matches.isEmpty ? null : _matches.last.extra; + Object? get extra => matches.isEmpty ? null : matches.last.extra; /// The last matching route. - RouteMatch get last => _matches.last; - - /// The route matches. - List get matches => _matches; + RouteMatch get last => matches.last; /// Returns true if the current match intends to display an error screen. bool get isError => matches.length == 1 && matches.first.error != null; @@ -170,9 +198,44 @@ class RouteMatchList { /// Returns the error that this match intends to display. Exception? get error => matches.first.error; + RouteMatchList _copyWith({ + List? matches, + Uri? uri, + Map? pathParameters, + }) { + return RouteMatchList( + matches: matches ?? this.matches, + uri: uri ?? this.uri, + pathParameters: pathParameters ?? this.pathParameters); + } + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is RouteMatchList && + const ListEquality().equals(matches, other.matches) && + uri == other.uri && + const MapEquality() + .equals(pathParameters, other.pathParameters); + } + + @override + int get hashCode { + return Object.hash( + Object.hashAll(matches), + uri, + Object.hashAllUnordered( + pathParameters.entries.map((MapEntry entry) => + Object.hash(entry.key, entry.value)), + ), + ); + } + @override String toString() { - return '${objectRuntimeType(this, 'RouteMatchList')}($fullpath)'; + return '${objectRuntimeType(this, 'RouteMatchList')}($fullPath)'; } } @@ -198,17 +261,17 @@ class MatcherError extends Error { /// For example, for a given `loc` `/a/b/c/d`, this function will return the /// list of [RouteBase] `[GoRouteA(), GoRouterB(), GoRouteC(), GoRouterD()]`. /// -/// - [loc] is the complete URL to match (without the query parameters). For -/// example, for the URL `/a/b?c=0`, [loc] will be `/a/b`. -/// - [restLoc] is the remaining part of the URL to match while [parentSubloc] +/// - [location] is the complete URL to match (without the query parameters). For +/// example, for the URL `/a/b?c=0`, [location] will be `/a/b`. +/// - [remainingLocation] is the remaining part of the URL to match while [matchedLocation] /// is the part of the URL that has already been matched. For examples, for -/// the URL `/a/b/c/d`, at some point, [restLoc] would be `/c/d` and -/// [parentSubloc] will be `/a/b`. -/// - [routes] are the possible [RouteBase] to match to [restLoc]. +/// the URL `/a/b/c/d`, at some point, [remainingLocation] would be `/c/d` and +/// [matchedLocation] will be `/a/b`. +/// - [routes] are the possible [RouteBase] to match to [remainingLocation]. List? _getLocRouteRecursively({ - required String loc, - required String restLoc, - required String parentSubloc, + required String location, + required String remainingLocation, + required String matchedLocation, required List routes, required Map pathParameters, required Object? extra, @@ -221,8 +284,8 @@ List? _getLocRouteRecursively({ final RouteMatch? match = RouteMatch.match( route: route, - restLoc: restLoc, - parentSubloc: parentSubloc, + remainingLocation: remainingLocation, + matchedLocation: matchedLocation, pathParameters: subPathParameters, extra: extra, ); @@ -232,9 +295,9 @@ List? _getLocRouteRecursively({ } if (match.route is GoRoute && - match.subloc.toLowerCase() == loc.toLowerCase()) { + match.matchedLocation.toLowerCase() == location.toLowerCase()) { // If it is a complete match, then return the matched route - // NOTE: need a lower case match because subloc is canonicalized to match + // NOTE: need a lower case match because matchedLocation is canonicalized to match // the path case whereas the location can be of any case and still match result = [match]; } else if (route.routes.isEmpty) { @@ -245,21 +308,21 @@ List? _getLocRouteRecursively({ final String childRestLoc; final String newParentSubLoc; if (match.route is ShellRoute) { - childRestLoc = restLoc; - newParentSubLoc = parentSubloc; + childRestLoc = remainingLocation; + newParentSubLoc = matchedLocation; } else { - assert(loc.startsWith(match.subloc)); - assert(restLoc.isNotEmpty); + assert(location.startsWith(match.matchedLocation)); + assert(remainingLocation.isNotEmpty); - childRestLoc = - loc.substring(match.subloc.length + (match.subloc == '/' ? 0 : 1)); - newParentSubLoc = match.subloc; + childRestLoc = location.substring(match.matchedLocation.length + + (match.matchedLocation == '/' ? 0 : 1)); + newParentSubLoc = match.matchedLocation; } final List? subRouteMatch = _getLocRouteRecursively( - loc: loc, - restLoc: childRestLoc, - parentSubloc: newParentSubLoc, + location: location, + remainingLocation: childRestLoc, + matchedLocation: newParentSubLoc, routes: route.routes, pathParameters: subPathParameters, extra: extra, @@ -284,20 +347,21 @@ List? _getLocRouteRecursively({ RouteMatchList errorScreen(Uri uri, String errorMessage) { final Exception error = Exception(errorMessage); return RouteMatchList( - [ - RouteMatch( - subloc: uri.path, - extra: null, - error: error, - route: GoRoute( - path: uri.toString(), - pageBuilder: (BuildContext context, GoRouterState state) { - throw UnimplementedError(); - }, - ), - pageKey: const ValueKey('error'), + matches: [ + RouteMatch( + matchedLocation: uri.path, + extra: null, + error: error, + route: GoRoute( + path: uri.toString(), + pageBuilder: (BuildContext context, GoRouterState state) { + throw UnimplementedError(); + }, ), - ], - uri, - const {}); + pageKey: const ValueKey('error'), + ), + ], + uri: uri, + pathParameters: const {}, + ); } diff --git a/packages/go_router/lib/src/misc/extensions.dart b/packages/go_router/lib/src/misc/extensions.dart index 47f5b08cd8e..05dbd0f3174 100644 --- a/packages/go_router/lib/src/misc/extensions.dart +++ b/packages/go_router/lib/src/misc/extensions.dart @@ -12,11 +12,11 @@ extension GoRouterHelper on BuildContext { /// Get a location from route name and parameters. String namedLocation( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, }) => - GoRouter.of(this) - .namedLocation(name, params: params, queryParams: queryParams); + GoRouter.of(this).namedLocation(name, + pathParameters: pathParameters, queryParameters: queryParameters); /// Navigate to a location. void go(String location, {Object? extra}) => @@ -25,14 +25,14 @@ extension GoRouterHelper on BuildContext { /// Navigate to a named route. void goNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) => GoRouter.of(this).goNamed( name, - params: params, - queryParams: queryParams, + pathParameters: pathParameters, + queryParameters: queryParameters, extra: extra, ); @@ -50,14 +50,14 @@ extension GoRouterHelper on BuildContext { /// Navigate to a named route onto the page stack. Future pushNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) => GoRouter.of(this).pushNamed( name, - params: params, - queryParams: queryParams, + pathParameters: pathParameters, + queryParameters: queryParameters, extra: extra, ); @@ -81,7 +81,7 @@ extension GoRouterHelper on BuildContext { GoRouter.of(this).pushReplacement(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': + /// optional parameters, e.g. `name='person', pathParameters={'fid': 'f2', 'pid': /// 'p1'}`. /// /// See also: @@ -89,14 +89,14 @@ extension GoRouterHelper on BuildContext { /// * [pushNamed] which pushes a named route onto the page stack. void pushReplacementNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) => GoRouter.of(this).pushReplacementNamed( name, - params: params, - queryParams: queryParams, + pathParameters: pathParameters, + queryParameters: queryParameters, extra: extra, ); @@ -117,8 +117,8 @@ extension GoRouterHelper on BuildContext { /// 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'}`. + /// parameters can be provided to the named route, e.g. `name='person', + /// pathParameters={'fid': 'f2', 'pid': 'p1'}`. /// /// See also: /// * [pushNamed] which pushes the given location onto the page stack. @@ -126,8 +126,8 @@ extension GoRouterHelper on BuildContext { /// stack but always uses a new page key. void replaceNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) => GoRouter.of(this).replaceNamed(name, extra: extra); diff --git a/packages/go_router/lib/src/parser.dart b/packages/go_router/lib/src/parser.dart index a888daf86b2..7fd00cfea39 100644 --- a/packages/go_router/lib/src/parser.dart +++ b/packages/go_router/lib/src/parser.dart @@ -8,7 +8,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/widgets.dart'; import 'configuration.dart'; -import 'delegate.dart'; import 'information_provider.dart'; import 'logging.dart'; import 'match.dart'; @@ -72,12 +71,12 @@ class GoRouteInformationParser extends RouteInformationParser { // If there is a matching error for the initial location, we should // still try to process the top-level redirects. initialMatches = RouteMatchList( - [], + matches: const [], // TODO(chunhtai): remove this ignore and migrate the code // https://github.com/flutter/flutter/issues/124045. // ignore: deprecated_member_use, unnecessary_non_null_assertion - Uri.parse(canonicalUri(routeInformation.location!)), - const {}, + uri: Uri.parse(canonicalUri(routeInformation.location!)), + pathParameters: const {}, ); } Future processRedirectorResult(RouteMatchList matches) { diff --git a/packages/go_router/lib/src/redirection.dart b/packages/go_router/lib/src/redirection.dart index 3ebef5cf294..3ac4a5b9e6c 100644 --- a/packages/go_router/lib/src/redirection.dart +++ b/packages/go_router/lib/src/redirection.dart @@ -96,8 +96,8 @@ FutureOr redirect( name: null, // No name available at the top level trim the query params off the // sub-location to match route.redirect - subloc: prevMatchList.uri.path, - queryParams: prevMatchList.uri.queryParameters, + matchedLocation: prevMatchList.uri.path, + queryParameters: prevMatchList.uri.queryParameters, queryParametersAll: prevMatchList.uri.queryParametersAll, extra: extra, pageKey: const ValueKey('topLevel'), @@ -138,13 +138,13 @@ FutureOr _getRouteLevelRedirect( GoRouterState( configuration, location: matchList.uri.toString(), - subloc: match.subloc, + matchedLocation: match.matchedLocation, name: route.name, path: route.path, - fullpath: matchList.fullpath, + fullPath: matchList.fullPath, extra: match.extra, - params: matchList.pathParameters, - queryParams: matchList.uri.queryParameters, + pathParameters: matchList.pathParameters, + queryParameters: matchList.uri.queryParameters, queryParametersAll: matchList.uri.queryParametersAll, pageKey: match.pageKey, ), diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart index 7f4b9c12868..21e80536491 100644 --- a/packages/go_router/lib/src/route.dart +++ b/packages/go_router/lib/src/route.dart @@ -42,7 +42,7 @@ import 'typedefs.dart'; /// GoRoute( /// path: 'family/:fid', /// pageBuilder: (BuildContext context, GoRouterState state) { -/// final Family family = Families.family(state.params['fid']!); +/// final Family family = Families.family(state.pathParameters['fid']!); /// return MaterialPage( /// key: state.pageKey, /// child: FamilyPage(family: family), @@ -52,8 +52,8 @@ import 'typedefs.dart'; /// GoRoute( /// path: 'person/:pid', /// pageBuilder: (BuildContext context, GoRouterState state) { -/// final Family family = Families.family(state.params['fid']!); -/// final Person person = family.person(state.params['pid']!); +/// final Family family = Families.family(state.pathParameters['fid']!); +/// final Person person = family.person(state.pathParameters['pid']!); /// return MaterialPage( /// key: state.pageKey, /// child: PersonPage(family: family, person: person), @@ -137,7 +137,7 @@ class GoRoute extends RouteBase { 'builder, pageBuilder, or redirect must be provided'), super._() { // cache the path regexp and parameters - _pathRE = patternToRegExp(path, pathParams); + _pathRE = patternToRegExp(path, pathParameters); } /// Optional name of the route. @@ -169,8 +169,8 @@ class GoRoute extends RouteBase { /// /// context.go( /// context.namedLocation('family'), - /// params: {'fid': 123}, - /// queryParams: {'qid': 'quid'}, + /// pathParameters: {'fid': 123}, + /// queryParameters: {'qid': 'quid'}, /// ); /// ``` /// @@ -228,7 +228,7 @@ class GoRoute extends RouteBase { /// path: '/', /// builder: (BuildContext context, GoRouterState state) => FamilyPage( /// families: Families.family( - /// state.params['id'], + /// state.pathParameters['id'], /// ), /// ), /// ), @@ -306,11 +306,11 @@ class GoRoute extends RouteBase { /// Extract the path parameters from a match. Map extractPathParams(RegExpMatch match) => - extractPathParameters(pathParams, match); + extractPathParameters(pathParameters, match); /// The path parameters in this route. @internal - final List pathParams = []; + final List pathParameters = []; @override String toString() { diff --git a/packages/go_router/lib/src/router.dart b/packages/go_router/lib/src/router.dart index 134c46e44c0..e9b4ec64ec5 100644 --- a/packages/go_router/lib/src/router.dart +++ b/packages/go_router/lib/src/router.dart @@ -8,6 +8,7 @@ import 'configuration.dart'; import 'delegate.dart'; import 'information_provider.dart'; import 'logging.dart'; +import 'match.dart'; import 'matching.dart'; import 'misc/inherited_router.dart'; import 'parser.dart'; @@ -179,13 +180,13 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// This is useful for redirecting to a named location. String namedLocation( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, }) => _routeInformationParser.configuration.namedLocation( name, - params: params, - queryParams: queryParams, + pathParameters: pathParameters, + queryParameters: queryParameters, ); /// Navigate to a URI location w/ optional query parameters, e.g. @@ -203,16 +204,17 @@ class GoRouter extends ChangeNotifier implements RouterConfig { } /// Navigate to a named route w/ optional parameters, e.g. - /// `name='person', params={'fid': 'f2', 'pid': 'p1'}` + /// `name='person', pathParameters={'fid': 'f2', 'pid': 'p1'}` /// Navigate to the named route. void goNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) => go( - namedLocation(name, params: params, queryParams: queryParams), + namedLocation(name, + pathParameters: pathParameters, queryParameters: queryParameters), extra: extra, ); @@ -245,15 +247,16 @@ class GoRouter extends ChangeNotifier implements RouterConfig { } /// Push a named route onto the page stack w/ optional parameters, e.g. - /// `name='person', params={'fid': 'f2', 'pid': 'p1'}` + /// `name='person', pathParameters={'fid': 'f2', 'pid': 'p1'}` Future pushNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) => push( - namedLocation(name, params: params, queryParams: queryParams), + namedLocation(name, + pathParameters: pathParameters, queryParameters: queryParameters), extra: extra, ); @@ -283,7 +286,7 @@ 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': + /// optional parameters, e.g. `name='person', pathParameters={'fid': 'f2', 'pid': /// 'p1'}`. /// /// See also: @@ -291,12 +294,13 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// * [pushNamed] which pushes a named route onto the page stack. void pushReplacementNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) { pushReplacement( - namedLocation(name, params: params, queryParams: queryParams), + namedLocation(name, + pathParameters: pathParameters, queryParameters: queryParameters), extra: extra, ); } @@ -332,7 +336,7 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// /// 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'}`. + /// pathParameters={'fid': 'f2', 'pid': 'p1'}`. /// /// See also: /// * [pushNamed] which pushes the given location onto the page stack. @@ -340,12 +344,13 @@ class GoRouter extends ChangeNotifier implements RouterConfig { /// stack but always uses a new page key. void replaceNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) { replace( - namedLocation(name, params: params, queryParams: queryParams), + namedLocation(name, + pathParameters: pathParameters, queryParameters: queryParameters), extra: extra, ); } diff --git a/packages/go_router/lib/src/state.dart b/packages/go_router/lib/src/state.dart index 57c68a28b43..c360cef3d7d 100644 --- a/packages/go_router/lib/src/state.dart +++ b/packages/go_router/lib/src/state.dart @@ -16,12 +16,12 @@ class GoRouterState { const GoRouterState( this._configuration, { required this.location, - required this.subloc, + required this.matchedLocation, required this.name, this.path, - this.fullpath, - this.params = const {}, - this.queryParams = const {}, + this.fullPath, + this.pathParameters = const {}, + this.queryParameters = const {}, this.queryParametersAll = const >{}, this.extra, this.error, @@ -35,8 +35,15 @@ class GoRouterState { /// The full location of the route, e.g. /family/f2/person/p1 final String location; - /// The location of this sub-route, e.g. /family/f2 - final String subloc; + /// The matched location until this point. + /// + /// For example: + /// + /// location = /family/f2/person/p1 + /// route = GoRoute('/family/:id') + /// + /// matchedLocation = /family/f2 + final String matchedLocation; /// The optional name of the route. final String? name; @@ -45,13 +52,13 @@ class GoRouterState { final String? path; /// The full path to this sub-route, e.g. /family/:fid - final String? fullpath; + final String? fullPath; /// The parameters for this sub-route, e.g. {'fid': 'f2'} - final Map params; + final Map pathParameters; /// The query parameters for the location, e.g. {'from': '/family/f2'} - final Map queryParams; + final Map queryParameters; /// The query parameters for the location, /// e.g. `{'q1': ['v1'], 'q2': ['v2', 'v3']}` @@ -98,7 +105,7 @@ class GoRouterState { /// class MyWidget extends StatelessWidget { /// @override /// Widget build(BuildContext context) { - /// return Text('${GoRouterState.of(context).params['id']}'); + /// return Text('${GoRouterState.of(context).pathParameters['id']}'); /// } /// } /// ``` @@ -125,26 +132,27 @@ class GoRouterState { /// Get a location from route name and parameters. /// This is useful for redirecting to a named location. - @Deprecated('Use GoRouter.of(context).namedLocation instead') + // TODO(chunhtai): remove this method when go_router can provide a way to + // look up named location during redirect. String namedLocation( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, }) { return _configuration.namedLocation(name, - params: params, queryParams: queryParams); + pathParameters: pathParameters, queryParameters: queryParameters); } @override bool operator ==(Object other) { return other is GoRouterState && other.location == location && - other.subloc == subloc && + other.matchedLocation == matchedLocation && other.name == name && other.path == path && - other.fullpath == fullpath && - other.params == params && - other.queryParams == queryParams && + other.fullPath == fullPath && + other.pathParameters == pathParameters && + other.queryParameters == queryParameters && other.queryParametersAll == queryParametersAll && other.extra == extra && other.error == error && @@ -152,8 +160,18 @@ class GoRouterState { } @override - int get hashCode => Object.hash(location, subloc, name, path, fullpath, - params, queryParams, queryParametersAll, extra, error, pageKey); + int get hashCode => Object.hash( + location, + matchedLocation, + name, + path, + fullPath, + pathParameters, + queryParameters, + queryParametersAll, + extra, + error, + pageKey); } /// An inherited widget to host a [GoRouterStateRegistry] for the subtree. diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml index bf4b67279b4..4d1cb1fda84 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.5.9 +version: 7.0.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 @@ -21,3 +21,4 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + path: ^1.8.2 diff --git a/packages/go_router/test/builder_test.dart b/packages/go_router/test/builder_test.dart index 04e75432df9..81f4182583f 100644 --- a/packages/go_router/test/builder_test.dart +++ b/packages/go_router/test/builder_test.dart @@ -29,17 +29,17 @@ void main() { ); final RouteMatchList matches = RouteMatchList( - [ + matches: [ RouteMatch( route: config.routes.first as GoRoute, - subloc: '/', + matchedLocation: '/', extra: null, error: null, pageKey: const ValueKey('/'), ), ], - Uri.parse('/'), - const {}); + uri: Uri.parse('/'), + pathParameters: const {}); await tester.pumpWidget( _BuilderTestWidget( @@ -76,17 +76,17 @@ void main() { ); final RouteMatchList matches = RouteMatchList( - [ + matches: [ RouteMatch( route: config.routes.first, - subloc: '/', + matchedLocation: '/', extra: null, error: null, pageKey: const ValueKey('/'), ), ], - Uri.parse('/'), - {}); + uri: Uri.parse('/'), + pathParameters: const {}); await tester.pumpWidget( _BuilderTestWidget( @@ -118,17 +118,17 @@ void main() { ); final RouteMatchList matches = RouteMatchList( - [ + matches: [ RouteMatch( route: config.routes.first as GoRoute, - subloc: '/', + matchedLocation: '/', extra: null, error: null, pageKey: const ValueKey('/'), ), ], - Uri.parse('/'), - {}); + uri: Uri.parse('/'), + pathParameters: const {}); await tester.pumpWidget( _BuilderTestWidget( @@ -173,24 +173,24 @@ void main() { ); final RouteMatchList matches = RouteMatchList( - [ + matches: [ RouteMatch( route: config.routes.first, - subloc: '', + matchedLocation: '', extra: null, error: null, pageKey: const ValueKey(''), ), RouteMatch( route: config.routes.first.routes.first, - subloc: '/details', + matchedLocation: '/details', extra: null, error: null, pageKey: const ValueKey('/details'), ), ], - Uri.parse('/details'), - {}); + uri: Uri.parse('/details'), + pathParameters: const {}); await tester.pumpWidget( _BuilderTestWidget( @@ -248,17 +248,17 @@ void main() { ); final RouteMatchList matches = RouteMatchList( - [ + matches: [ RouteMatch( route: config.routes.first.routes.first as GoRoute, - subloc: '/a/details', + matchedLocation: '/a/details', extra: null, error: null, pageKey: const ValueKey('/a/details'), ), ], - Uri.parse('/a/details'), - {}); + uri: Uri.parse('/a/details'), + pathParameters: const {}); await tester.pumpWidget( _BuilderTestWidget( diff --git a/packages/go_router/test/delegate_test.dart b/packages/go_router/test/delegate_test.dart index e40084a77fd..a0533a1a539 100644 --- a/packages/go_router/test/delegate_test.dart +++ b/packages/go_router/test/delegate_test.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; -import 'package:go_router/src/delegate.dart'; import 'package:go_router/src/match.dart'; import 'package:go_router/src/misc/error_screen.dart'; diff --git a/packages/go_router/test/go_router_state_test.dart b/packages/go_router/test/go_router_state_test.dart index 59cee73feca..b1d0eb5285f 100644 --- a/packages/go_router/test/go_router_state_test.dart +++ b/packages/go_router/test/go_router_state_test.dart @@ -17,13 +17,13 @@ void main() { path: '/', builder: (BuildContext context, _) { final GoRouterState state = GoRouterState.of(context); - return Text('/ ${state.queryParams['p']}'); + return Text('/ ${state.queryParameters['p']}'); }), GoRoute( path: '/a', builder: (BuildContext context, _) { final GoRouterState state = GoRouterState.of(context); - return Text('/a ${state.queryParams['p']}'); + return Text('/a ${state.queryParameters['p']}'); }), ]; final GoRouter router = await createRouter(routes, tester); @@ -83,7 +83,7 @@ void main() { builder: (_, __) { return Builder(builder: (BuildContext context) { return Text( - '2 ${GoRouterState.of(context).params['id']}'); + '2 ${GoRouterState.of(context).pathParameters['id']}'); }); }), ]), diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart index dca119952f6..a93b49213a6 100644 --- a/packages/go_router/test/go_router_test.dart +++ b/packages/go_router/test/go_router_test.dart @@ -11,7 +11,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:go_router/go_router.dart'; -import 'package:go_router/src/delegate.dart'; import 'package:go_router/src/match.dart'; import 'package:go_router/src/matching.dart'; import 'package:logging/logging.dart'; @@ -152,7 +151,7 @@ void main() { await tester.pumpAndSettle(); final List matches = router.routerDelegate.matches.matches; expect(matches, hasLength(1)); - expect(matches.first.subloc, '/login'); + expect(matches.first.matchedLocation, '/login'); expect(find.byType(LoginScreen), findsOneWidget); }); @@ -181,7 +180,7 @@ void main() { await tester.pumpAndSettle(); final List matches = router.routerDelegate.matches.matches; expect(matches, hasLength(1)); - expect(matches.first.subloc, '/login'); + expect(matches.first.matchedLocation, '/login'); expect(find.byType(LoginScreen), findsOneWidget); }); @@ -205,7 +204,7 @@ void main() { await tester.pumpAndSettle(); final List matches = router.routerDelegate.matches.matches; expect(matches, hasLength(1)); - expect(matches.first.subloc, '/login'); + expect(matches.first.matchedLocation, '/login'); expect(find.byType(LoginScreen), findsOneWidget); }); @@ -224,7 +223,7 @@ void main() { await tester.pumpAndSettle(); final List matches = router.routerDelegate.matches.matches; expect(matches, hasLength(1)); - expect(matches.first.subloc, '/profile/foo'); + expect(matches.first.matchedLocation, '/profile/foo'); expect(find.byType(DummyScreen), findsOneWidget); }); @@ -243,7 +242,7 @@ void main() { await tester.pumpAndSettle(); final List matches = router.routerDelegate.matches.matches; expect(matches, hasLength(1)); - expect(matches.first.subloc, '/profile/foo'); + expect(matches.first.matchedLocation, '/profile/foo'); expect(find.byType(DummyScreen), findsOneWidget); }); @@ -355,9 +354,9 @@ void main() { await tester.pumpAndSettle(); final List matches = router.routerDelegate.matches.matches; expect(matches.length, 2); - expect(matches.first.subloc, '/'); + expect(matches.first.matchedLocation, '/'); expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget); - expect(matches[1].subloc, '/login'); + expect(matches[1].matchedLocation, '/login'); expect(find.byType(LoginScreen), findsOneWidget); }); @@ -402,9 +401,9 @@ void main() { { final RouteMatchList matches = router.routerDelegate.matches; expect(matches.matches.length, 2); - expect(matches.matches.first.subloc, '/'); + expect(matches.matches.first.matchedLocation, '/'); expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget); - expect(matches.matches[1].subloc, '/login'); + expect(matches.matches[1].matchedLocation, '/login'); expect(find.byType(LoginScreen), findsOneWidget); } @@ -413,9 +412,9 @@ void main() { { final RouteMatchList matches = router.routerDelegate.matches; expect(matches.matches.length, 2); - expect(matches.matches.first.subloc, '/'); + expect(matches.matches.first.matchedLocation, '/'); expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget); - expect(matches.matches[1].subloc, '/family/f2'); + expect(matches.matches[1].matchedLocation, '/family/f2'); expect(find.byType(FamilyScreen), findsOneWidget); } @@ -424,11 +423,11 @@ void main() { { final RouteMatchList matches = router.routerDelegate.matches; expect(matches.matches.length, 3); - expect(matches.matches.first.subloc, '/'); + expect(matches.matches.first.matchedLocation, '/'); expect(find.byType(HomeScreen, skipOffstage: false), findsOneWidget); - expect(matches.matches[1].subloc, '/family/f2'); + expect(matches.matches[1].matchedLocation, '/family/f2'); expect(find.byType(FamilyScreen, skipOffstage: false), findsOneWidget); - expect(matches.matches[2].subloc, '/family/f2/person/p1'); + expect(matches.matches[2].matchedLocation, '/family/f2/person/p1'); expect(find.byType(PersonScreen), findsOneWidget); } }); @@ -494,11 +493,11 @@ void main() { path: '/', builder: (BuildContext context, GoRouterState state) { expect(state.location, '/'); - expect(state.subloc, '/'); + expect(state.matchedLocation, '/'); expect(state.name, 'home'); expect(state.path, '/'); - expect(state.fullpath, '/'); - expect(state.params, {}); + expect(state.fullPath, '/'); + expect(state.pathParameters, {}); expect(state.error, null); if (state.extra != null) { expect(state.extra! as int, 1); @@ -511,11 +510,11 @@ void main() { path: 'login', builder: (BuildContext context, GoRouterState state) { expect(state.location, '/login'); - expect(state.subloc, '/login'); + expect(state.matchedLocation, '/login'); expect(state.name, 'login'); expect(state.path, 'login'); - expect(state.fullpath, '/login'); - expect(state.params, {}); + expect(state.fullPath, '/login'); + expect(state.pathParameters, {}); expect(state.error, null); expect(state.extra! as int, 2); return const LoginScreen(); @@ -529,14 +528,14 @@ void main() { state.location, anyOf(['/family/f2', '/family/f2/person/p1']), ); - expect(state.subloc, '/family/f2'); + expect(state.matchedLocation, '/family/f2'); expect(state.name, 'family'); expect(state.path, 'family/:fid'); - expect(state.fullpath, '/family/:fid'); - expect(state.params, {'fid': 'f2'}); + expect(state.fullPath, '/family/:fid'); + expect(state.pathParameters, {'fid': 'f2'}); expect(state.error, null); expect(state.extra! as int, 3); - return FamilyScreen(state.params['fid']!); + return FamilyScreen(state.pathParameters['fid']!); }, routes: [ GoRoute( @@ -544,18 +543,18 @@ void main() { path: 'person/:pid', builder: (BuildContext context, GoRouterState state) { expect(state.location, '/family/f2/person/p1'); - expect(state.subloc, '/family/f2/person/p1'); + expect(state.matchedLocation, '/family/f2/person/p1'); expect(state.name, 'person'); expect(state.path, 'person/:pid'); - expect(state.fullpath, '/family/:fid/person/:pid'); + expect(state.fullPath, '/family/:fid/person/:pid'); expect( - state.params, + state.pathParameters, {'fid': 'f2', 'pid': 'p1'}, ); expect(state.error, null); expect(state.extra! as int, 4); - return PersonScreen( - state.params['fid']!, state.params['pid']!); + return PersonScreen(state.pathParameters['fid']!, + state.pathParameters['pid']!); }, ), ], @@ -585,7 +584,7 @@ void main() { GoRoute( path: '/family/:fid', builder: (BuildContext context, GoRouterState state) => - FamilyScreen(state.params['fid']!), + FamilyScreen(state.pathParameters['fid']!), ), ]; @@ -595,7 +594,7 @@ void main() { await tester.pumpAndSettle(); final List matches = router.routerDelegate.matches.matches; - // NOTE: match the lower case, since subloc is canonicalized to match the + // NOTE: match the lower case, since location is canonicalized to match the // path case whereas the location can be any case; so long as the path // produces a match regardless of the location case, we win! expect(router.location.toLowerCase(), loc.toLowerCase()); @@ -1321,7 +1320,7 @@ void main() { name: 'person', path: 'person/:pid', builder: (BuildContext context, GoRouterState state) { - expect(state.params, + expect(state.pathParameters, {'fid': 'f2', 'pid': 'p1'}); return const PersonScreen('dummy', 'dummy'); }, @@ -1334,7 +1333,7 @@ void main() { final GoRouter router = await createRouter(routes, tester); router.goNamed('person', - params: {'fid': 'f2', 'pid': 'p1'}); + pathParameters: {'fid': 'f2', 'pid': 'p1'}); }); testWidgets('too few params', (WidgetTester tester) async { @@ -1364,7 +1363,7 @@ void main() { ]; await expectLater(() async { final GoRouter router = await createRouter(routes, tester); - router.goNamed('person', params: {'fid': 'f2'}); + router.goNamed('person', pathParameters: {'fid': 'f2'}); await tester.pump(); }, throwsA(isAssertionError)); }); @@ -1388,7 +1387,7 @@ void main() { name: 'PeRsOn', path: 'person/:pid', builder: (BuildContext context, GoRouterState state) { - expect(state.params, + expect(state.pathParameters, {'fid': 'f2', 'pid': 'p1'}); return const PersonScreen('dummy', 'dummy'); }, @@ -1401,7 +1400,7 @@ void main() { final GoRouter router = await createRouter(routes, tester); router.goNamed('person', - params: {'fid': 'f2', 'pid': 'p1'}); + pathParameters: {'fid': 'f2', 'pid': 'p1'}); }); testWidgets('too few params', (WidgetTester tester) async { @@ -1431,7 +1430,7 @@ void main() { await expectLater(() async { final GoRouter router = await createRouter(routes, tester); router.goNamed('family', - params: {'fid': 'f2', 'pid': 'p1'}); + pathParameters: {'fid': 'f2', 'pid': 'p1'}); }, throwsA(isAssertionError)); }); @@ -1445,7 +1444,7 @@ void main() { GoRoute( path: '/family/:fid', builder: (BuildContext context, GoRouterState state) => FamilyScreen( - state.params['fid']!, + state.pathParameters['fid']!, ), routes: [ GoRoute( @@ -1453,8 +1452,8 @@ void main() { path: 'person:pid', builder: (BuildContext context, GoRouterState state) => PersonScreen( - state.params['fid']!, - state.params['pid']!, + state.pathParameters['fid']!, + state.pathParameters['pid']!, ), ), ], @@ -1463,7 +1462,7 @@ void main() { final GoRouter router = await createRouter(routes, tester); router.goNamed('person', - params: {'fid': 'f2', 'pid': 'p1'}); + pathParameters: {'fid': 'f2', 'pid': 'p1'}); await tester.pumpAndSettle(); expect(find.byType(PersonScreen), findsOneWidget); }); @@ -1476,15 +1475,15 @@ void main() { name: 'page1', path: '/page1/:param1', builder: (BuildContext c, GoRouterState s) { - expect(s.params['param1'], param1); + expect(s.pathParameters['param1'], param1); return const DummyScreen(); }, ), ]; final GoRouter router = await createRouter(routes, tester); - final String loc = router - .namedLocation('page1', params: {'param1': param1}); + final String loc = router.namedLocation('page1', + pathParameters: {'param1': param1}); router.go(loc); await tester.pumpAndSettle(); @@ -1501,7 +1500,7 @@ void main() { name: 'page1', path: '/page1', builder: (BuildContext c, GoRouterState s) { - expect(s.queryParams['param1'], param1); + expect(s.queryParameters['param1'], param1); return const DummyScreen(); }, ), @@ -1509,7 +1508,7 @@ void main() { final GoRouter router = await createRouter(routes, tester); final String loc = router.namedLocation('page1', - queryParams: {'param1': param1}); + queryParameters: {'param1': param1}); router.go(loc); await tester.pumpAndSettle(); final RouteMatchList matches = router.routerDelegate.matches; @@ -1542,7 +1541,7 @@ void main() { final GoRouter router = await createRouter(routes, tester, redirect: (BuildContext context, GoRouterState state) { redirected = true; - return state.subloc == '/login' ? null : '/login'; + return state.matchedLocation == '/login' ? null : '/login'; }); expect(router.location, '/login'); @@ -1617,7 +1616,9 @@ void main() { routes, tester, redirect: (BuildContext context, GoRouterState state) => - state.subloc == '/login' ? null : state.namedLocation('login'), + state.matchedLocation == '/login' + ? null + : state.namedLocation('login'), ); expect(router.location, '/login'); }); @@ -1682,7 +1683,7 @@ void main() { final GoRouter router = await createRouter(routes, tester, redirect: (BuildContext context, GoRouterState state) { redirected = true; - return state.subloc == '/login' ? null : '/login'; + return state.matchedLocation == '/login' ? null : '/login'; }); redirected = false; // Directly set the url through platform message. @@ -1750,7 +1751,7 @@ void main() { final GoRouter router = await createRouter(routes, tester, redirect: (BuildContext context, GoRouterState state) => - state.subloc == '/dummy1' ? '/dummy2' : null); + state.matchedLocation == '/dummy1' ? '/dummy2' : null); router.go('/dummy1'); await tester.pump(); expect(router.location, '/'); @@ -1759,9 +1760,9 @@ void main() { testWidgets('top-level redirect loop', (WidgetTester tester) async { final GoRouter router = await createRouter([], tester, redirect: (BuildContext context, GoRouterState state) => - state.subloc == '/' + state.matchedLocation == '/' ? '/login' - : state.subloc == '/login' + : state.matchedLocation == '/login' ? '/' : null); @@ -1809,7 +1810,7 @@ void main() { ], tester, redirect: (BuildContext context, GoRouterState state) => - state.subloc == '/' ? '/login' : null, + state.matchedLocation == '/' ? '/login' : null, ); final List matches = router.routerDelegate.matches.matches; @@ -1826,9 +1827,9 @@ void main() { [], tester, redirect: (BuildContext context, GoRouterState state) => - state.subloc == '/' + state.matchedLocation == '/' ? '/login?from=${state.location}' - : state.subloc == '/login' + : state.matchedLocation == '/login' ? '/' : null, ); @@ -1841,7 +1842,7 @@ void main() { expect(screen.ex, isNotNull); }); - testWidgets('expect null path/fullpath on top-level redirect', + testWidgets('expect null path/fullPath on top-level redirect', (WidgetTester tester) async { final List routes = [ GoRoute( @@ -1884,12 +1885,12 @@ void main() { initialLocation: '/login?from=/', redirect: (BuildContext context, GoRouterState state) { expect(Uri.parse(state.location).queryParameters, isNotEmpty); - expect(Uri.parse(state.subloc).queryParameters, isEmpty); + expect(Uri.parse(state.matchedLocation).queryParameters, isEmpty); expect(state.path, isNull); - expect(state.fullpath, isNull); - expect(state.params.length, 0); - expect(state.queryParams.length, 1); - expect(state.queryParams['from'], '/'); + expect(state.fullPath, isNull); + expect(state.pathParameters.length, 0); + expect(state.queryParameters.length, 1); + expect(state.queryParameters['from'], '/'); return null; }, ); @@ -1906,11 +1907,11 @@ void main() { path: '/book/:bookId', redirect: (BuildContext context, GoRouterState state) { expect(state.location, loc); - expect(state.subloc, loc); + expect(state.matchedLocation, loc); expect(state.path, '/book/:bookId'); - expect(state.fullpath, '/book/:bookId'); - expect(state.params, {'bookId': '0'}); - expect(state.queryParams.length, 0); + expect(state.fullPath, '/book/:bookId'); + expect(state.pathParameters, {'bookId': '0'}); + expect(state.queryParameters.length, 0); return null; }, builder: (BuildContext c, GoRouterState s) => const HomeScreen(), @@ -1938,18 +1939,18 @@ void main() { GoRoute( path: 'family/:fid', builder: (BuildContext c, GoRouterState s) => - FamilyScreen(s.params['fid']!), + FamilyScreen(s.pathParameters['fid']!), routes: [ GoRoute( path: 'person/:pid', redirect: (BuildContext context, GoRouterState s) { - expect(s.params['fid'], 'f2'); - expect(s.params['pid'], 'p1'); + expect(s.pathParameters['fid'], 'f2'); + expect(s.pathParameters['pid'], 'p1'); return null; }, builder: (BuildContext c, GoRouterState s) => PersonScreen( - s.params['fid']!, - s.params['pid']!, + s.pathParameters['fid']!, + s.pathParameters['pid']!, ), ), ], @@ -2221,7 +2222,7 @@ void main() { GoRoute( path: '/family/:fid', builder: (BuildContext context, GoRouterState state) => - FamilyScreen(state.params['fid']!), + FamilyScreen(state.pathParameters['fid']!), ), ]; @@ -2249,7 +2250,7 @@ void main() { GoRoute( path: '/family', builder: (BuildContext context, GoRouterState state) => FamilyScreen( - state.queryParams['fid']!, + state.queryParameters['fid']!, ), ), ]; @@ -2275,7 +2276,7 @@ void main() { GoRoute( path: '/page1/:param1', builder: (BuildContext c, GoRouterState s) { - expect(s.params['param1'], param1); + expect(s.pathParameters['param1'], param1); return const DummyScreen(); }, ), @@ -2298,7 +2299,7 @@ void main() { GoRoute( path: '/page1', builder: (BuildContext c, GoRouterState s) { - expect(s.queryParams['param1'], param1); + expect(s.queryParameters['param1'], param1); return const DummyScreen(); }, ), @@ -2346,10 +2347,10 @@ void main() { GoRoute( path: '/', builder: (BuildContext context, GoRouterState state) { - log.info('id= ${state.params['id']}'); - expect(state.params.length, 0); - expect(state.queryParams.length, 1); - expect(state.queryParams['id'], anyOf('0', '1')); + log.info('id= ${state.pathParameters['id']}'); + expect(state.pathParameters.length, 0); + expect(state.queryParameters.length, 1); + expect(state.queryParameters['id'], anyOf('0', '1')); return const HomeScreen(); }, ), @@ -2359,7 +2360,7 @@ void main() { ); final RouteMatchList matches = router.routerDelegate.matches; expect(matches.matches, hasLength(1)); - expect(matches.fullpath, '/'); + expect(matches.fullPath, '/'); expect(find.byType(HomeScreen), findsOneWidget); }); @@ -2369,8 +2370,8 @@ void main() { GoRoute( path: '/:id', builder: (BuildContext context, GoRouterState state) { - expect(state.params, {'id': '0'}); - expect(state.queryParams, {'id': '1'}); + expect(state.pathParameters, {'id': '0'}); + expect(state.queryParameters, {'id': '1'}); return const HomeScreen(); }, ), @@ -2382,7 +2383,7 @@ void main() { await tester.pumpAndSettle(); final RouteMatchList matches = router.routerDelegate.matches; expect(matches.matches, hasLength(1)); - expect(matches.fullpath, '/:id'); + expect(matches.fullPath, '/:id'); expect(find.byType(HomeScreen), findsOneWidget); }); @@ -2394,15 +2395,15 @@ void main() { path: '/family', builder: (BuildContext context, GoRouterState state) => FamilyScreen( - state.queryParams['fid']!, + state.queryParameters['fid']!, ), ), GoRoute( path: '/person', builder: (BuildContext context, GoRouterState state) => PersonScreen( - state.queryParams['fid']!, - state.queryParams['pid']!, + state.queryParameters['fid']!, + state.queryParameters['pid']!, ), ), ], @@ -2470,13 +2471,13 @@ void main() { GoRoute( path: '/family/:fid', builder: (BuildContext context, GoRouterState state) => - FamilyScreen(state.params['fid']!), + FamilyScreen(state.pathParameters['fid']!), routes: [ GoRoute( path: 'person/:pid', builder: (BuildContext context, GoRouterState state) { - final String fid = state.params['fid']!; - final String pid = state.params['pid']!; + final String fid = state.pathParameters['fid']!; + final String pid = state.pathParameters['pid']!; return PersonScreen(fid, pid); }, @@ -2536,7 +2537,7 @@ void main() { final GoRouter router = await createRouter(routes, tester); - router.goNamed('page', queryParams: const { + router.goNamed('page', queryParameters: const { 'q1': 'v1', 'q2': ['v2', 'v3'], }); @@ -2694,12 +2695,12 @@ void main() { ); key.currentContext!.namedLocation( name, - params: params, - queryParams: queryParams, + pathParameters: params, + queryParameters: queryParams, ); expect(router.name, name); - expect(router.params, params); - expect(router.queryParams, queryParams); + expect(router.pathParameters, params); + expect(router.queryParameters, queryParams); }); testWidgets('calls [go] on closest GoRouter', (WidgetTester tester) async { @@ -2729,13 +2730,13 @@ void main() { ); key.currentContext!.goNamed( name, - params: params, - queryParams: queryParams, + pathParameters: params, + queryParameters: queryParams, extra: extra, ); expect(router.name, name); - expect(router.params, params); - expect(router.queryParams, queryParams); + expect(router.pathParameters, params); + expect(router.queryParameters, queryParams); expect(router.extra, extra); }); @@ -2787,13 +2788,13 @@ void main() { ); key.currentContext!.pushNamed( name, - params: params, - queryParams: queryParams, + pathParameters: params, + queryParameters: queryParams, extra: extra, ); expect(router.name, name); - expect(router.params, params); - expect(router.queryParams, queryParams); + expect(router.pathParameters, params); + expect(router.queryParameters, queryParams); expect(router.extra, extra); }); @@ -2810,15 +2811,15 @@ void main() { ); final String? result = await router.pushNamed( name, - params: params, - queryParams: queryParams, + pathParameters: params, + queryParameters: queryParams, extra: extra, ); expect(result, extra); expect(router.extra, extra); expect(router.name, name); - expect(router.params, params); - expect(router.queryParams, queryParams); + expect(router.pathParameters, params); + expect(router.queryParameters, queryParams); }); testWidgets('calls [pop] on closest GoRouter', (WidgetTester tester) async { diff --git a/packages/go_router/test/inherited_test.dart b/packages/go_router/test/inherited_test.dart index 7afa9fe5752..1b7fb3f86d2 100644 --- a/packages/go_router/test/inherited_test.dart +++ b/packages/go_router/test/inherited_test.dart @@ -130,8 +130,8 @@ class MockGoRouter extends GoRouter { @override Future pushNamed(String name, - {Map params = const {}, - Map queryParams = const {}, + {Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra}) { latestPushedName = name; return Future.value(); diff --git a/packages/go_router/test/match_test.dart b/packages/go_router/test/match_test.dart index aa14db17246..712f379d4d1 100644 --- a/packages/go_router/test/match_test.dart +++ b/packages/go_router/test/match_test.dart @@ -17,8 +17,8 @@ void main() { final Map pathParameters = {}; final RouteMatch? match = RouteMatch.match( route: route, - restLoc: '/users/123', - parentSubloc: '', + remainingLocation: '/users/123', + matchedLocation: '', pathParameters: pathParameters, extra: const _Extra('foo'), ); @@ -26,14 +26,14 @@ void main() { fail('Null match'); } expect(match.route, route); - expect(match.subloc, '/users/123'); + expect(match.matchedLocation, '/users/123'); expect(pathParameters['userId'], '123'); expect(match.extra, const _Extra('foo')); expect(match.error, isNull); expect(match.pageKey, isNotNull); }); - test('subloc', () { + test('matchedLocation', () { final GoRoute route = GoRoute( path: 'users/:userId', builder: _builder, @@ -41,8 +41,8 @@ void main() { final Map pathParameters = {}; final RouteMatch? match = RouteMatch.match( route: route, - restLoc: 'users/123', - parentSubloc: '/home', + remainingLocation: 'users/123', + matchedLocation: '/home', pathParameters: pathParameters, extra: const _Extra('foo'), ); @@ -50,7 +50,7 @@ void main() { fail('Null match'); } expect(match.route, route); - expect(match.subloc, '/home/users/123'); + expect(match.matchedLocation, '/home/users/123'); expect(pathParameters['userId'], '123'); expect(match.extra, const _Extra('foo')); expect(match.error, isNull); @@ -70,8 +70,8 @@ void main() { final Map pathParameters = {}; final RouteMatch? match = RouteMatch.match( route: route, - restLoc: 'users/123', - parentSubloc: '/home', + remainingLocation: 'users/123', + matchedLocation: '/home', pathParameters: pathParameters, extra: const _Extra('foo'), ); @@ -94,16 +94,16 @@ void main() { final Map pathParameters = {}; final RouteMatch? match1 = RouteMatch.match( route: route, - restLoc: 'users/123', - parentSubloc: '/home', + remainingLocation: 'users/123', + matchedLocation: '/home', pathParameters: pathParameters, extra: const _Extra('foo'), ); final RouteMatch? match2 = RouteMatch.match( route: route, - restLoc: 'users/1234', - parentSubloc: '/home', + remainingLocation: 'users/1234', + matchedLocation: '/home', pathParameters: pathParameters, extra: const _Extra('foo1'), ); @@ -119,16 +119,16 @@ void main() { final Map pathParameters = {}; final RouteMatch? match1 = RouteMatch.match( route: route, - restLoc: 'users/123', - parentSubloc: '/home', + remainingLocation: 'users/123', + matchedLocation: '/home', pathParameters: pathParameters, extra: const _Extra('foo'), ); final RouteMatch? match2 = RouteMatch.match( route: route, - restLoc: 'users/1234', - parentSubloc: '/home', + remainingLocation: 'users/1234', + matchedLocation: '/home', pathParameters: pathParameters, extra: const _Extra('foo1'), ); diff --git a/packages/go_router/test/matching_test.dart b/packages/go_router/test/matching_test.dart index c92533bf381..daf8ccc46a1 100644 --- a/packages/go_router/test/matching_test.dart +++ b/packages/go_router/test/matching_test.dart @@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.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/router.dart'; @@ -27,4 +28,50 @@ void main() { final RouteMatchList matches = router.routerDelegate.matches; expect(matches.toString(), contains('/page-0')); }); + + test('RouteMatchList compares', () async { + final GoRoute route = GoRoute( + path: '/page-0', + builder: (BuildContext context, GoRouterState state) => + const Placeholder(), + ); + final Map params1 = {}; + final RouteMatch match1 = RouteMatch.match( + route: route, + remainingLocation: '/page-0', + matchedLocation: '', + pathParameters: params1, + extra: null, + )!; + + final Map params2 = {}; + final RouteMatch match2 = RouteMatch.match( + route: route, + remainingLocation: '/page-0', + matchedLocation: '', + pathParameters: params2, + extra: null, + )!; + + final RouteMatchList matches1 = RouteMatchList( + matches: [match1], + uri: Uri.parse(''), + pathParameters: params1, + ); + + final RouteMatchList matches2 = RouteMatchList( + matches: [match2], + uri: Uri.parse(''), + pathParameters: params2, + ); + + final RouteMatchList matches3 = RouteMatchList( + matches: [match2], + uri: Uri.parse('/page-0'), + pathParameters: params2, + ); + + expect(matches1 == matches2, isTrue); + expect(matches1 == matches3, isFalse); + }); } diff --git a/packages/go_router/test/parser_test.dart b/packages/go_router/test/parser_test.dart index 1cc40487cca..76a068b7d85 100644 --- a/packages/go_router/test/parser_test.dart +++ b/packages/go_router/test/parser_test.dart @@ -65,7 +65,7 @@ void main() { expect(matches.length, 1); expect(matchesObj.uri.toString(), '/'); expect(matches[0].extra, isNull); - expect(matches[0].subloc, '/'); + expect(matches[0].matchedLocation, '/'); expect(matches[0].route, routes[0]); final Object extra = Object(); @@ -75,11 +75,11 @@ void main() { expect(matches.length, 2); expect(matchesObj.uri.toString(), '/abc?def=ghi'); expect(matches[0].extra, extra); - expect(matches[0].subloc, '/'); + expect(matches[0].matchedLocation, '/'); expect(matches[0].route, routes[0]); expect(matches[1].extra, extra); - expect(matches[1].subloc, '/abc'); + expect(matches[1].matchedLocation, '/abc'); expect(matches[1].route, routes[0].routes[0]); }); @@ -126,11 +126,11 @@ void main() { expect(configuration.namedLocation('lowercase'), '/abc'); expect( configuration.namedLocation('lowercase', - queryParams: const {'q': '1'}), + queryParameters: const {'q': '1'}), '/abc?q=1'); expect( configuration.namedLocation('lowercase', - queryParams: const {'q': '1', 'g': '2'}), + queryParameters: const {'q': '1', 'g': '2'}), '/abc?q=1&g=2'); }); @@ -160,7 +160,7 @@ void main() { expect( configuration - .namedLocation('routeName', queryParams: const { + .namedLocation('routeName', queryParameters: const { 'q1': 'v1', 'q2': ['v2', 'v3'], }), @@ -198,7 +198,7 @@ void main() { expect(matches.length, 1); expect(matchesObj.uri.toString(), '/def'); expect(matches[0].extra, isNull); - expect(matches[0].subloc, '/def'); + expect(matches[0].matchedLocation, '/def'); expect(matches[0].error!.toString(), 'Exception: no routes for location: /def'); }); @@ -268,10 +268,10 @@ void main() { expect(matchesObj.pathParameters['uid'], '123'); expect(matchesObj.pathParameters['fid'], '456'); expect(matches[0].extra, isNull); - expect(matches[0].subloc, '/'); + expect(matches[0].matchedLocation, '/'); expect(matches[1].extra, isNull); - expect(matches[1].subloc, '/123/family/456'); + expect(matches[1].matchedLocation, '/123/family/456'); }); testWidgets( @@ -309,9 +309,9 @@ void main() { expect(matches.length, 2); expect(matchesObj.uri.toString(), '/123/family/345'); - expect(matches[0].subloc, '/'); + expect(matches[0].matchedLocation, '/'); - expect(matches[1].subloc, '/123/family/345'); + expect(matches[1].matchedLocation, '/123/family/345'); }); testWidgets( @@ -349,9 +349,9 @@ void main() { expect(matches.length, 2); expect(matchesObj.uri.toString(), '/123/family/345'); - expect(matches[0].subloc, '/'); + expect(matches[0].matchedLocation, '/'); - expect(matches[1].subloc, '/123/family/345'); + expect(matches[1].matchedLocation, '/123/family/345'); }); testWidgets( diff --git a/packages/go_router/test/test_helpers.dart b/packages/go_router/test/test_helpers.dart index b81f4c2e894..d2187d4ce28 100644 --- a/packages/go_router/test/test_helpers.dart +++ b/packages/go_router/test/test_helpers.dart @@ -37,18 +37,18 @@ class GoRouterNamedLocationSpy extends GoRouter { GoRouterNamedLocationSpy({required super.routes}); String? name; - Map? params; - Map? queryParams; + Map? pathParameters; + Map? queryParameters; @override String namedLocation( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, }) { this.name = name; - this.params = params; - this.queryParams = queryParams; + this.pathParameters = pathParameters; + this.queryParameters = queryParameters; return ''; } } @@ -70,20 +70,20 @@ class GoRouterGoNamedSpy extends GoRouter { GoRouterGoNamedSpy({required super.routes}); String? name; - Map? params; - Map? queryParams; + Map? pathParameters; + Map? queryParameters; Object? extra; @override void goNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) { this.name = name; - this.params = params; - this.queryParams = queryParams; + this.pathParameters = pathParameters; + this.queryParameters = queryParameters; this.extra = extra; } } @@ -106,20 +106,20 @@ class GoRouterPushNamedSpy extends GoRouter { GoRouterPushNamedSpy({required super.routes}); String? name; - Map? params; - Map? queryParams; + Map? pathParameters; + Map? queryParameters; Object? extra; @override Future pushNamed( String name, { - Map params = const {}, - Map queryParams = const {}, + Map pathParameters = const {}, + Map queryParameters = const {}, Object? extra, }) { this.name = name; - this.params = params; - this.queryParams = queryParams; + this.pathParameters = pathParameters; + this.queryParameters = queryParameters; this.extra = extra; return Future.value(extra as T?); } diff --git a/packages/go_router/test_fixes/README.md b/packages/go_router/test_fixes/README.md new file mode 100644 index 00000000000..994d67d127c --- /dev/null +++ b/packages/go_router/test_fixes/README.md @@ -0,0 +1,17 @@ +## Directory contents + +The Dart files and golden master `.expect` files in this directory are used to +test the [`dart fix` framework](https://dart.dev/tools/dart-fix) refactorings +used by the go_router package + +See the packages/packages/go_router/lib/fix_data.yaml directory for the current +package:go_router data-driven fixes. + +To run these tests locally, execute this command in the +packages/packages/go_router/test_fixes directory. +```sh +dart fix --compare-to-golden +``` + +For more documentation about Data Driven Fixes, see +https://dart.dev/go/data-driven-fixes#test-folder. diff --git a/packages/go_router/test_fixes/analysis_options.yaml b/packages/go_router/test_fixes/analysis_options.yaml new file mode 100644 index 00000000000..7cca7b1d5ce --- /dev/null +++ b/packages/go_router/test_fixes/analysis_options.yaml @@ -0,0 +1 @@ +# This ensures that parent analysis options do not accidentally break the fix tests. diff --git a/packages/go_router/test_fixes/go_router.dart b/packages/go_router/test_fixes/go_router.dart new file mode 100644 index 00000000000..58117378d88 --- /dev/null +++ b/packages/go_router/test_fixes/go_router.dart @@ -0,0 +1,44 @@ +// 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:go_router/go_router.dart'; + +void main() { + const GoRouterState state = GoRouterState(); + final GoRouter router = GoRouter(routes: []); + state.fullpath; + state.params; + state.subloc; + state.queryParams; + state.namedLocation( + 'name', + params: {}, + queryParams: {}, + ); + router.namedLocation( + 'name', + params: {}, + queryParams: {}, + ); + router.goNamed( + 'name', + params: {}, + queryParams: {}, + ); + router.pushNamed( + 'name', + params: {}, + queryParams: {}, + ); + router.pushReplacementNamed( + 'name', + params: {}, + queryParams: {}, + ); + router.replaceNamed( + 'name', + params: {}, + queryParams: {}, + ); +} diff --git a/packages/go_router/test_fixes/go_router.dart.expect b/packages/go_router/test_fixes/go_router.dart.expect new file mode 100644 index 00000000000..c2c5aa614f6 --- /dev/null +++ b/packages/go_router/test_fixes/go_router.dart.expect @@ -0,0 +1,44 @@ +// 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:go_router/go_router.dart'; + +void main() { + const GoRouterState state = GoRouterState(); + final GoRouter router = GoRouter(routes: []); + state.fullPath; + state.pathParameters; + state.matchedLocation; + state.queryParameters; + state.namedLocation( + 'name', + pathParameters: {}, + queryParameters: {}, + ); + router.namedLocation( + 'name', + pathParameters: {}, + queryParameters: {}, + ); + router.goNamed( + 'name', + pathParameters: {}, + queryParameters: {}, + ); + router.pushNamed( + 'name', + pathParameters: {}, + queryParameters: {}, + ); + router.pushReplacementNamed( + 'name', + pathParameters: {}, + queryParameters: {}, + ); + router.replaceNamed( + 'name', + pathParameters: {}, + queryParameters: {}, + ); +} diff --git a/packages/go_router/tool/run_tests.dart b/packages/go_router/tool/run_tests.dart new file mode 100644 index 00000000000..39fe8cc0361 --- /dev/null +++ b/packages/go_router/tool/run_tests.dart @@ -0,0 +1,51 @@ +// 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. + +// Called from the custom-tests CI action. +// +// usage: dart run tool/run_tests.dart + +// ignore_for_file: avoid_print + +import 'dart:io'; +import 'package:path/path.dart' as p; + +Future main(List args) async { + if (!Platform.isMacOS) { + print('This test can only be run on macOS.'); + exit(0); + } + final Directory packageRoot = + Directory(p.dirname(Platform.script.path)).parent; + final int status = await _runProcess( + 'dart', + [ + 'fix', + '--compare-to-golden', + ], + workingDirectory: p.join(packageRoot.path, 'test_fixes'), + ); + + exit(status); +} + +Future _streamOutput(Future processFuture) async { + final Process process = await processFuture; + stdout.addStream(process.stdout); + stderr.addStream(process.stderr); + return process; +} + +Future _runProcess( + String command, + List arguments, { + String? workingDirectory, +}) async { + final Process process = await _streamOutput(Process.start( + command, + arguments, + workingDirectory: workingDirectory, + )); + return process.exitCode; +} diff --git a/script/configs/custom_analysis.yaml b/script/configs/custom_analysis.yaml index 93e274431c6..569f9e1208d 100644 --- a/script/configs/custom_analysis.yaml +++ b/script/configs/custom_analysis.yaml @@ -23,3 +23,5 @@ - rfw/example # Disables docs requirements, as it is test code. - web_benchmarks/testing/test_app +# Has some test files that are intentionally broken to conduct dart fix tests. +- go_router