mirror of
https://github.com/flutter/packages.git
synced 2025-07-01 23:51:55 +08:00
[go_router] fixes pop and push to update urls correctly (#2904)
* [go_router] fixes pop and push to update urls correctly * bump version
This commit is contained in:
@ -1,3 +1,7 @@
|
||||
## 5.2.2
|
||||
|
||||
- Fixes `pop` and `push` to update urls correctly.
|
||||
|
||||
## 5.2.1
|
||||
|
||||
- Refactors `GoRouter.pop` to be able to pop individual pageless route with result.
|
||||
|
@ -133,6 +133,7 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
|
||||
return false;
|
||||
}
|
||||
_matchList.pop();
|
||||
notifyListeners();
|
||||
assert(() {
|
||||
_debugAssertMatchListNotEmpty();
|
||||
return true;
|
||||
|
@ -48,7 +48,7 @@ class RouteMatcher {
|
||||
/// The list of [RouteMatch] objects.
|
||||
class RouteMatchList {
|
||||
/// RouteMatchList constructor.
|
||||
RouteMatchList(List<RouteMatch> matches, this.uri, this.pathParameters)
|
||||
RouteMatchList(List<RouteMatch> matches, this._uri, this.pathParameters)
|
||||
: _matches = matches,
|
||||
fullpath = _generateFullPath(matches);
|
||||
|
||||
@ -82,7 +82,8 @@ class RouteMatchList {
|
||||
final Map<String, String> pathParameters;
|
||||
|
||||
/// The uri of the current match.
|
||||
final Uri uri;
|
||||
Uri get uri => _uri;
|
||||
Uri _uri;
|
||||
|
||||
/// Returns true if there are no matches.
|
||||
bool get isEmpty => _matches.isEmpty;
|
||||
@ -97,8 +98,11 @@ class RouteMatchList {
|
||||
|
||||
/// Removes the last match.
|
||||
void pop() {
|
||||
if (_matches.last.route is GoRoute) {
|
||||
final GoRoute route = _matches.last.route as GoRoute;
|
||||
_uri = _uri.replace(path: removePatternFromPath(route.path, _uri.path));
|
||||
}
|
||||
_matches.removeLast();
|
||||
|
||||
// Also pop ShellRoutes when there are no subsequent route matches
|
||||
while (_matches.isNotEmpty && _matches.last.route is ShellRoute) {
|
||||
_matches.removeLast();
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'configuration.dart';
|
||||
import 'delegate.dart';
|
||||
import 'information_provider.dart';
|
||||
import 'logging.dart';
|
||||
import 'matching.dart';
|
||||
@ -98,6 +99,10 @@ class GoRouteInformationParser extends RouteInformationParser<RouteMatchList> {
|
||||
/// for use by the Router architecture as part of the RouteInformationParser
|
||||
@override
|
||||
RouteInformation restoreRouteInformation(RouteMatchList configuration) {
|
||||
if (configuration.matches.last is ImperativeRouteMatch) {
|
||||
configuration =
|
||||
(configuration.matches.last as ImperativeRouteMatch).matches;
|
||||
}
|
||||
return RouteInformation(
|
||||
location: configuration.uri.toString(),
|
||||
state: configuration.extra,
|
||||
|
@ -47,10 +47,51 @@ RegExp patternToRegExp(String pattern, List<String> parameters) {
|
||||
return RegExp(buffer.toString(), caseSensitive: false);
|
||||
}
|
||||
|
||||
String _escapeGroup(String group, String name) {
|
||||
/// Removes string from the end of the path that matches a `pattern`.
|
||||
///
|
||||
/// The path parameters can be specified by prefixing them with `:`. The
|
||||
/// `parameters` are used for storing path parameter names.
|
||||
///
|
||||
///
|
||||
/// For example:
|
||||
///
|
||||
/// `path` = `/user/123/book/345`
|
||||
/// `pattern` = `book/:id`
|
||||
///
|
||||
/// The return value = `/user/123`.
|
||||
String removePatternFromPath(String pattern, String path) {
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
int start = 0;
|
||||
for (final RegExpMatch match in _parameterRegExp.allMatches(pattern)) {
|
||||
if (match.start > start) {
|
||||
buffer.write(RegExp.escape(pattern.substring(start, match.start)));
|
||||
}
|
||||
final String? optionalPattern = match[2];
|
||||
final String regex =
|
||||
optionalPattern != null ? _escapeGroup(optionalPattern) : '[^/]+';
|
||||
buffer.write(regex);
|
||||
start = match.end;
|
||||
}
|
||||
|
||||
if (start < pattern.length) {
|
||||
buffer.write(RegExp.escape(pattern.substring(start)));
|
||||
}
|
||||
|
||||
if (!pattern.endsWith('/')) {
|
||||
buffer.write(r'(?=/|$)');
|
||||
}
|
||||
buffer.write(r'$');
|
||||
final RegExp regexp = RegExp(buffer.toString(), caseSensitive: false);
|
||||
return path.replaceFirst(regexp, '');
|
||||
}
|
||||
|
||||
String _escapeGroup(String group, [String? name]) {
|
||||
final String escapedGroup = group.replaceFirstMapped(
|
||||
RegExp(r'[:=!]'), (Match match) => '\\${match[0]}');
|
||||
return '(?<$name>$escapedGroup)';
|
||||
if (name != null) {
|
||||
return '(?<$name>$escapedGroup)';
|
||||
}
|
||||
return escapedGroup;
|
||||
}
|
||||
|
||||
/// Reconstructs the full path from a [pattern] and path parameters.
|
||||
|
@ -300,6 +300,7 @@ class GoRoute extends RouteBase {
|
||||
/// Navigator instead of the nearest ShellRoute ancestor.
|
||||
final GlobalKey<NavigatorState>? parentNavigatorKey;
|
||||
|
||||
// TODO(chunhtai): move all regex related help methods to path_utils.dart.
|
||||
/// Match this route against a location.
|
||||
RegExpMatch? matchPatternAsPrefix(String loc) =>
|
||||
_pathRE.matchAsPrefix(loc) as RegExpMatch?;
|
||||
|
@ -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: 5.2.1
|
||||
version: 5.2.2
|
||||
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
|
||||
|
||||
|
@ -879,6 +879,138 @@ void main() {
|
||||
});
|
||||
});
|
||||
|
||||
group('report correct url', () {
|
||||
final List<MethodCall> log = <MethodCall>[];
|
||||
setUp(() {
|
||||
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.navigation,
|
||||
(MethodCall methodCall) async {
|
||||
log.add(methodCall);
|
||||
return null;
|
||||
});
|
||||
});
|
||||
tearDown(() {
|
||||
TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(SystemChannels.navigation, null);
|
||||
log.clear();
|
||||
});
|
||||
|
||||
testWidgets('on push', (WidgetTester tester) async {
|
||||
final List<GoRoute> routes = <GoRoute>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
),
|
||||
GoRoute(
|
||||
path: '/settings',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
),
|
||||
];
|
||||
|
||||
final GoRouter router = await createRouter(routes, tester);
|
||||
|
||||
log.clear();
|
||||
router.push('/settings');
|
||||
await tester.pumpAndSettle();
|
||||
expect(log, <Object>[
|
||||
isMethodCall('selectMultiEntryHistory', arguments: null),
|
||||
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
|
||||
'location': '/settings',
|
||||
'state': null,
|
||||
'replace': false
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('on pop', (WidgetTester tester) async {
|
||||
final List<GoRoute> routes = <GoRoute>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: 'settings',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
),
|
||||
]),
|
||||
];
|
||||
|
||||
final GoRouter router =
|
||||
await createRouter(routes, tester, initialLocation: '/settings');
|
||||
|
||||
log.clear();
|
||||
router.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(log, <Object>[
|
||||
isMethodCall('selectMultiEntryHistory', arguments: null),
|
||||
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
|
||||
'location': '/',
|
||||
'state': null,
|
||||
'replace': false
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('on pop with path parameters', (WidgetTester tester) async {
|
||||
final List<GoRoute> routes = <GoRoute>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: 'settings/:id',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
),
|
||||
]),
|
||||
];
|
||||
|
||||
final GoRouter router =
|
||||
await createRouter(routes, tester, initialLocation: '/settings/123');
|
||||
|
||||
log.clear();
|
||||
router.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(log, <Object>[
|
||||
isMethodCall('selectMultiEntryHistory', arguments: null),
|
||||
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
|
||||
'location': '/',
|
||||
'state': null,
|
||||
'replace': false
|
||||
}),
|
||||
]);
|
||||
});
|
||||
|
||||
testWidgets('on pop with path parameters case 2',
|
||||
(WidgetTester tester) async {
|
||||
final List<GoRoute> routes = <GoRoute>[
|
||||
GoRoute(
|
||||
path: '/',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
routes: <RouteBase>[
|
||||
GoRoute(
|
||||
path: ':id',
|
||||
builder: (_, __) => const DummyScreen(),
|
||||
),
|
||||
]),
|
||||
];
|
||||
|
||||
final GoRouter router =
|
||||
await createRouter(routes, tester, initialLocation: '/123/');
|
||||
|
||||
log.clear();
|
||||
router.pop();
|
||||
await tester.pumpAndSettle();
|
||||
expect(log, <Object>[
|
||||
isMethodCall('selectMultiEntryHistory', arguments: null),
|
||||
isMethodCall('routeInformationUpdated', arguments: <String, dynamic>{
|
||||
'location': '/',
|
||||
'state': null,
|
||||
'replace': false
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
group('named routes', () {
|
||||
testWidgets('match home route', (WidgetTester tester) async {
|
||||
final List<GoRoute> routes = <GoRoute>[
|
||||
|
Reference in New Issue
Block a user