Skip to content

Commit

Permalink
WIP route match tree
Browse files Browse the repository at this point in the history
  • Loading branch information
chunhtai committed Nov 29, 2023
1 parent 83cb110 commit 723b388
Show file tree
Hide file tree
Showing 3 changed files with 488 additions and 192 deletions.
135 changes: 29 additions & 106 deletions packages/go_router/lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,9 @@ class RouteConfiguration {
final Uri uri = Uri.parse(canonicalUri(location));

final Map<String, String> pathParameters = <String, String>{};
final List<RouteMatch>? matches = _getLocRouteMatches(uri, pathParameters);
final List<RouteMatchBase> matches = _getLocRouteMatches(uri, pathParameters);

if (matches == null) {
if (matches.isEmpty) {
return _errorRouteMatchList(
uri,
GoException('no routes for location: $uri'),
Expand Down Expand Up @@ -328,96 +328,23 @@ class RouteConfiguration {
return result;
}

List<RouteMatch>? _getLocRouteMatches(
List<RouteMatchBase> _getLocRouteMatches(
Uri uri, Map<String, String> pathParameters) {
final List<RouteMatch>? result = _getLocRouteRecursively(
location: uri.path,
remainingLocation: uri.path,
matchedLocation: '',
matchedPath: '',
pathParameters: pathParameters,
routes: _routingConfig.value.routes,
);
return result;
}

List<RouteMatch>? _getLocRouteRecursively({
required String location,
required String remainingLocation,
required String matchedLocation,
required String matchedPath,
required Map<String, String> pathParameters,
required List<RouteBase> routes,
}) {
List<RouteMatch>? result;
late Map<String, String> subPathParameters;
// find the set of matches at this level of the tree
for (final RouteBase route in routes) {
subPathParameters = <String, String>{};

final RouteMatch? match = RouteMatch.match(
for (final RouteBase route in _routingConfig.value.routes) {
final List<RouteMatchBase> result = RouteMatchBase.match(
rootNavigatorKey: navigatorKey,
route: route,
remainingLocation: remainingLocation,
matchedLocation: matchedLocation,
matchedPath: matchedPath,
pathParameters: subPathParameters,
uri: uri,
remainingLocation: uri.path,
matchedLocation: '',
matchedPath: '',
pathParameters: pathParameters,
);

if (match == null) {
continue;
}

if (match.route is GoRoute &&
match.matchedLocation.toLowerCase() == location.toLowerCase()) {
// If it is a complete match, then return the matched route
// 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 = <RouteMatch>[match];
} else if (route.routes.isEmpty) {
// If it is partial match but no sub-routes, bail.
continue;
} else {
// Otherwise, recurse
final String childRestLoc;
final String newParentSubLoc;
final String newParentPath;
if (match.route is ShellRouteBase) {
childRestLoc = remainingLocation;
newParentSubLoc = matchedLocation;
newParentPath = matchedPath;
} else {
assert(location.startsWith(match.matchedLocation));
assert(remainingLocation.isNotEmpty);

childRestLoc = location.substring(match.matchedLocation.length +
(match.matchedLocation == '/' ? 0 : 1));
newParentSubLoc = match.matchedLocation;
newParentPath =
concatenatePaths(matchedPath, (match.route as GoRoute).path);
}

final List<RouteMatch>? subRouteMatch = _getLocRouteRecursively(
location: location,
remainingLocation: childRestLoc,
matchedLocation: newParentSubLoc,
matchedPath: newParentPath,
pathParameters: subPathParameters,
routes: route.routes,
);

// If there's no sub-route matches, there is no match for this location
if (subRouteMatch == null) {
continue;
}
result = <RouteMatch>[match, ...subRouteMatch];
if (result.isNotEmpty) {
return result;
}
// Should only reach here if there is a match.
break;
}
if (result != null) {
pathParameters.addAll(subPathParameters);
}
return result;
return const <RouteMatchBase>[];
}

/// Processes redirects by returning a new [RouteMatchList] representing the new
Expand Down Expand Up @@ -467,9 +394,16 @@ class RouteConfiguration {
}
return prevMatchList;
}
final List<RouteMatch> routeMatches = <RouteMatch>[];
prevMatchList.visitRouteMatches((RouteMatchBase match) {
if (match is RouteMatch) {
routeMatches.add(match);
}
});

final FutureOr<String?> routeLevelRedirectResult =
_getRouteLevelRedirect(context, prevMatchList, 0);
_getRouteLevelRedirect(context, prevMatchList, routeMatches, 0);

if (routeLevelRedirectResult is String?) {
return processRouteLevelRedirect(routeLevelRedirectResult);
}
Expand Down Expand Up @@ -499,33 +433,22 @@ class RouteConfiguration {
FutureOr<String?> _getRouteLevelRedirect(
BuildContext context,
RouteMatchList matchList,
List<RouteMatch> routeMatches,
int currentCheckIndex,
) {
if (currentCheckIndex >= matchList.matches.length) {
if (currentCheckIndex >= routeMatches.length) {
return null;
}
final RouteMatch match = matchList.matches[currentCheckIndex];
final RouteMatch match = routeMatches[currentCheckIndex];
FutureOr<String?> processRouteRedirect(String? newLocation) =>
newLocation ??
_getRouteLevelRedirect(context, matchList, currentCheckIndex + 1);
final RouteBase route = match.route;
_getRouteLevelRedirect(context, matchList, routeMatches, currentCheckIndex + 1);
final GoRoute route = match.route;
FutureOr<String?> routeRedirectResult;
if (route is GoRoute && route.redirect != null) {
final RouteMatchList effectiveMatchList =
match is ImperativeRouteMatch ? match.matches : matchList;
if (route.redirect != null) {
routeRedirectResult = route.redirect!(
context,
GoRouterState(
this,
uri: effectiveMatchList.uri,
matchedLocation: match.matchedLocation,
name: route.name,
path: route.path,
fullPath: effectiveMatchList.fullPath,
extra: effectiveMatchList.extra,
pathParameters: effectiveMatchList.pathParameters,
pageKey: match.pageKey,
),
match.buildState(this, matchList),
);
}
if (routeRedirectResult is String?) {
Expand Down
36 changes: 25 additions & 11 deletions packages/go_router/lib/src/delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -173,25 +173,39 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
final BuildContext? navigatorContext = navigatorKey.currentContext;
// If navigator is not built or disposed, the GoRoute.onExit is irrelevant.
if (navigatorContext != null) {
final List<RouteMatch> currentGoRouteMatches = <RouteMatch>[];
currentConfiguration.visitRouteMatches((RouteMatchBase match) {
if (match is RouteMatch) {
currentGoRouteMatches.add(match);
}
});
final List<RouteMatch> newGoRouteMatches = <RouteMatch>[];
configuration.visitRouteMatches((RouteMatchBase match) {
if (match is RouteMatch) {
newGoRouteMatches.add(match);
}
});

final int compareUntil = math.min(
currentConfiguration.matches.length,
configuration.matches.length,
currentGoRouteMatches.length,
newGoRouteMatches.length,
);
int indexOfFirstDiff = 0;
for (; indexOfFirstDiff < compareUntil; indexOfFirstDiff++) {
if (currentConfiguration.matches[indexOfFirstDiff] !=
configuration.matches[indexOfFirstDiff]) {
if (currentGoRouteMatches[indexOfFirstDiff] !=
newGoRouteMatches[indexOfFirstDiff]) {
break;
}
}
if (indexOfFirstDiff < currentConfiguration.matches.length) {
final List<GoRoute> exitingGoRoutes = currentConfiguration.matches

if (indexOfFirstDiff < currentGoRouteMatches.length) {
final List<GoRoute> exitingGoRoutes = currentGoRouteMatches
.sublist(indexOfFirstDiff)
.map<RouteBase>((RouteMatch match) => match.route)
.whereType<GoRoute>()
.toList();
return _callOnExitStartsAt(exitingGoRoutes.length - 1,
navigatorContext: navigatorContext, routes: exitingGoRoutes)
context: navigatorContext, routes: exitingGoRoutes)
.then<void>((bool exit) {
if (!exit) {
return SynchronousFuture<void>(null);
Expand All @@ -209,25 +223,25 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
/// The returned future resolves to true if all routes below the index all
/// return true. Otherwise, the returned future resolves to false.
static Future<bool> _callOnExitStartsAt(int index,
{required BuildContext navigatorContext, required List<GoRoute> routes}) {
{required BuildContext context, required List<GoRoute> routes}) {
if (index < 0) {
return SynchronousFuture<bool>(true);
}
final GoRoute goRoute = routes[index];
if (goRoute.onExit == null) {
return _callOnExitStartsAt(index - 1,
navigatorContext: navigatorContext, routes: routes);
context: context, routes: routes);
}

Future<bool> handleOnExitResult(bool exit) {
if (exit) {
return _callOnExitStartsAt(index - 1,
navigatorContext: navigatorContext, routes: routes);
context: context, routes: routes);
}
return SynchronousFuture<bool>(false);
}

final FutureOr<bool> exitFuture = goRoute.onExit!(navigatorContext);
final FutureOr<bool> exitFuture = goRoute.onExit!(context);
if (exitFuture is bool) {
return handleOnExitResult(exitFuture);
}
Expand Down
Loading

0 comments on commit 723b388

Please sign in to comment.