Skip to content

Commit

Permalink
Fix: fix an error when popping a nested route after flutter 3.22.0 #1973
Browse files Browse the repository at this point in the history
  • Loading branch information
Milad-Akarie committed Jun 12, 2024
1 parent 66a9faa commit 98c31c1
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 49 deletions.
2 changes: 0 additions & 2 deletions auto_route/example/lib/web_demo/router/web_verify_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@ class WebVerifyPage extends StatelessWidget {
body: Center(
child: ElevatedButton(
onPressed: () {
// print('root has Guards: ${context.router.activeGuardObserver.guardInProgress}' );
// print('nested has guards: ${context.router.innerRouterOf<StackRouter>(UserRoute.name)?.activeGuardObserver.guardInProgress}' );
App.of(context).authService.isVerified = true;
if (onResult != null) {
onResult!(true);
Expand Down
18 changes: 13 additions & 5 deletions auto_route/lib/src/matcher/route_match.dart
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ class RouteMatch<T> {
List<String> allSegments({bool includeEmpty = false}) => [
if (segments.isEmpty && includeEmpty) '',
...segments,
if (hasChildren) ...children!.last.allSegments(includeEmpty: includeEmpty)
if (hasChildren)
...children!.last.allSegments(includeEmpty: includeEmpty)
];

/// Joins all segments to a valid path
Expand Down Expand Up @@ -250,7 +251,8 @@ class HierarchySegment {
'name': name,
if (pathParams?.isNotEmpty == true) 'pathParams': pathParams!.rawMap,
if (queryParams?.isNotEmpty == true) 'queryParams': queryParams!.rawMap,
if (children.isNotEmpty) 'children': children.map((e) => e.toJson()).toList(),
if (children.isNotEmpty)
'children': children.map((e) => e.toJson()).toList(),
};
}

Expand All @@ -270,7 +272,11 @@ class HierarchySegment {
const ListEquality().equals(children, other.children);

@override
int get hashCode => name.hashCode ^ pathParams.hashCode ^ queryParams.hashCode ^ const ListEquality().hash(children);
int get hashCode =>
name.hashCode ^
pathParams.hashCode ^
queryParams.hashCode ^
const ListEquality().hash(children);
}

/// An extension to create a pretty json output of
Expand All @@ -284,8 +290,10 @@ extension PrettyHierarchySegmentX on List<HierarchySegment> {
Map toMap(List<HierarchySegment> segments) {
return Map.fromEntries(segments.map(
(e) => MapEntry(e.name, {
if (e.pathParams?.isNotEmpty == true) 'pathParams': e.pathParams!.rawMap,
if (e.queryParams?.isNotEmpty == true) 'queryParams': e.queryParams!.rawMap,
if (e.pathParams?.isNotEmpty == true)
'pathParams': e.pathParams!.rawMap,
if (e.queryParams?.isNotEmpty == true)
'queryParams': e.queryParams!.rawMap,
if (e.children.isNotEmpty) 'children': toMap(e.children),
}),
));
Expand Down
26 changes: 18 additions & 8 deletions auto_route/lib/src/router/auto_route_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -119,15 +119,20 @@ class _PageBasedMaterialPageRoute<T> extends PageRoute<T>
AutoRoutePage get _page => settings as AutoRoutePage;

@override
bool get willHandlePopInternally {
bool get popGestureEnabled {
/// This fixes the issue of nested navigators back-gesture
/// It prevents back-gesture on parent navigator if sub-router
/// can pop
if (isCurrent) {
if (super.popGestureEnabled) {
final router = _page.routeData.router;
return router.activeRouterCanPop();
final topMostRouter = router.topMostRouter();
return (router.isTopMost ||
!topMostRouter.canPop(
ignoreParentRoutes: true,
ignorePagelessRoutes: true,
));
}
return super.willHandlePopInternally;
return false;
}

@override
Expand Down Expand Up @@ -351,14 +356,19 @@ class _PageBasedCupertinoPageRoute<T> extends PageRoute<T>
String get debugLabel => '${super.debugLabel}(${_page.name})';

@override
bool get willHandlePopInternally {
bool get popGestureEnabled {
/// This fixes the issue of nested navigators back-gesture
/// It prevents back-gesture on parent navigator if sub-router
/// can pop
if (isCurrent) {
if (super.popGestureEnabled) {
final router = _page.routeData.router;
return router.activeRouterCanPop();
final topMostRouter = router.topMostRouter();
return (router.isTopMost ||
!topMostRouter.canPop(
ignoreParentRoutes: true,
ignorePagelessRoutes: true,
));
}
return super.willHandlePopInternally;
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,7 @@ class _AutoRootRouterState extends State<_AutoRootRouter> {
controller: router,
child: AutoRouteNavigator(
router: router,
key: GlobalObjectKey(widget.router.hashCode),
placeholder: widget.placeholder,
navRestorationScopeId: widget.navRestorationScopeId,
navigatorObservers: widget.navigatorObservers,
Expand Down Expand Up @@ -350,6 +351,7 @@ class _DeclarativeAutoRouterDelegate extends AutoRouterDelegate {
stateHash: stateHash,
child: AutoRouteNavigator(
router: controller,
key: GlobalObjectKey(controller.hashCode),
declarativeRoutesBuilder: routes,
navRestorationScopeId: navRestorationScopeId,
navigatorObservers: _navigatorObservers,
Expand Down
97 changes: 67 additions & 30 deletions auto_route/lib/src/router/controller/routing_controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ typedef RouteDataPredicate = bool Function(RouteData route);
typedef OnNestedNavigateCallBack = void Function(List<RouteMatch> routes);

/// Signature of a callback used in declarative routing
typedef RoutesBuilder = List<PageRouteInfo> Function(PendingRoutesHandler handler);
typedef RoutesBuilder = List<PageRouteInfo> Function(
PendingRoutesHandler handler);

/// Signature of a callback to report route pop events
typedef RoutePopCallBack = void Function(RouteMatch route, dynamic results);
Expand Down Expand Up @@ -79,7 +80,8 @@ abstract class RoutingController with ChangeNotifier {
_childControllers.remove(childController);
}

RoutingController? _innerControllerOf(Key? key) => _childControllers.lastWhereOrNull(
RoutingController? _innerControllerOf(Key? key) =>
_childControllers.lastWhereOrNull(
(c) => c.key == key,
);

Expand Down Expand Up @@ -206,16 +208,20 @@ abstract class RoutingController with ChangeNotifier {
HierarchySegment(
segmentName,
children: childSegments,
pathParams: ignoreParams || data.pathParams.isEmpty ? null : data.pathParams,
queryParams: ignoreParams || data.queryParams.isEmpty ? null : data.queryParams,
pathParams:
ignoreParams || data.pathParams.isEmpty ? null : data.pathParams,
queryParams: ignoreParams || data.queryParams.isEmpty
? null
: data.queryParams,
),
);
}
return hierarchy;
}

/// Returns an unmodifiable lis of current pages stack data
List<RouteData> get stackData => List.unmodifiable(_pages.map((e) => e.routeData));
List<RouteData> get stackData =>
List.unmodifiable(_pages.map((e) => e.routeData));

/// See [NavigationHistory.isRouteActive]
bool isRouteActive(String routeName) {
Expand Down Expand Up @@ -306,9 +312,12 @@ abstract class RoutingController with ChangeNotifier {
return routeCollection.containsKey(route.routeName);
}

_RouterScopeResult<T>? _findPathScopeOrReportFailure<T extends RoutingController>(String path,
{bool includePrefixMatches = false, OnNavigationFailure? onFailure}) {
final routers = _topMostRouter(ignorePagelessRoutes: true)._buildRoutersHierarchy().whereType<T>();
_RouterScopeResult<T>?
_findPathScopeOrReportFailure<T extends RoutingController>(String path,
{bool includePrefixMatches = false, OnNavigationFailure? onFailure}) {
final routers = _topMostRouter(ignorePagelessRoutes: true)
._buildRoutersHierarchy()
.whereType<T>();

for (var router in routers) {
final matches = router.matcher.match(
Expand All @@ -329,8 +338,11 @@ abstract class RoutingController with ChangeNotifier {
return null;
}

RoutingController _findScope<T extends RoutingController>(PageRouteInfo route) {
return _topMostRouter(ignorePagelessRoutes: true)._buildRoutersHierarchy().firstWhere(
RoutingController _findScope<T extends RoutingController>(
PageRouteInfo route) {
return _topMostRouter(ignorePagelessRoutes: true)
._buildRoutersHierarchy()
.firstWhere(
(r) => r._canHandleNavigation(route),
orElse: () => this,
);
Expand All @@ -341,13 +353,15 @@ abstract class RoutingController with ChangeNotifier {
///
/// if [onFailure] callback is provided, navigation errors will be passed to it
/// otherwise they'll be thrown
Future<dynamic> navigate(PageRouteInfo route, {OnNavigationFailure? onFailure}) async {
Future<dynamic> navigate(PageRouteInfo route,
{OnNavigationFailure? onFailure}) async {
return _findScope(route)._navigate(route, onFailure: onFailure);
}

void _onNavigate(List<RouteMatch> routes);

Future<dynamic> _navigate(PageRouteInfo route, {OnNavigationFailure? onFailure}) async {
Future<dynamic> _navigate(PageRouteInfo route,
{OnNavigationFailure? onFailure}) async {
final match = _matchOrReportFailure(route, onFailure);
if (match != null) {
return _navigateAll([match], onFailure: onFailure);
Expand All @@ -357,7 +371,8 @@ abstract class RoutingController with ChangeNotifier {
}

List<RoutingController> _buildRoutersHierarchy() {
void collectRouters(RoutingController currentParent, List<RoutingController> all) {
void collectRouters(
RoutingController currentParent, List<RoutingController> all) {
all.add(currentParent);
if (currentParent._parent != null) {
collectRouters(currentParent._parent!, all);
Expand Down Expand Up @@ -512,7 +527,8 @@ abstract class RoutingController with ChangeNotifier {

/// Calls [maybePop] on the controller with the top-most visible page
@optionalTypeArgs
Future<bool> maybePopTop<T extends Object?>([T? result]) => _topMostRouter().maybePop<T>(result);
Future<bool> maybePopTop<T extends Object?>([T? result]) =>
_topMostRouter().maybePop<T>(result);

/// Whether this controller can preform [maybePop]
///
Expand Down Expand Up @@ -544,7 +560,6 @@ abstract class RoutingController with ChangeNotifier {
);
}


/// returns true if the active child controller can pop
bool activeRouterCanPop({bool ignorePagelessRoutes = false}) {
final innerRouter = _innerControllerOf(currentChild?.key);
Expand Down Expand Up @@ -592,21 +607,30 @@ abstract class RoutingController with ChangeNotifier {
}

/// Finds match of [path] then returns a route-able entity
PageRouteInfo? buildPageRoute(String? path, {bool includePrefixMatches = true}) {
PageRouteInfo? buildPageRoute(String? path,
{bool includePrefixMatches = true}) {
if (path == null) return null;
return matcher.match(path, includePrefixMatches: includePrefixMatches)?.firstOrNull?.toPageRouteInfo();
return matcher
.match(path, includePrefixMatches: includePrefixMatches)
?.firstOrNull
?.toPageRouteInfo();
}

/// Finds matches of [path] then returns a list of route-able entities
List<PageRouteInfo>? buildPageRoutesStack(String? path, {bool includePrefixMatches = true}) {
List<PageRouteInfo>? buildPageRoutesStack(String? path,
{bool includePrefixMatches = true}) {
if (path == null) return null;
return matcher.match(path, includePrefixMatches: includePrefixMatches)?.map((m) => m.toPageRouteInfo()).toList();
return matcher
.match(path, includePrefixMatches: includePrefixMatches)
?.map((m) => m.toPageRouteInfo())
.toList();
}

@override
String toString() => '${routeData.name} Router';

Future<void> _navigateAll(List<RouteMatch> routes, {OnNavigationFailure? onFailure});
Future<void> _navigateAll(List<RouteMatch> routes,
{OnNavigationFailure? onFailure});
}

/// An implementation of a [RoutingController] that handles parallel routeing
Expand Down Expand Up @@ -799,7 +823,10 @@ class TabsRouter extends RoutingController {
if (routes != null) {
routesToUse = routes;
} else {
routesToUse = routeCollection.routes.where((e) => e is! RedirectRoute).map((e) => PageRouteInfo(e.name)).toList();
routesToUse = routeCollection.routes
.where((e) => e is! RedirectRoute)
.map((e) => PageRouteInfo(e.name))
.toList();
}
return _matchAllOrReportFailure(routesToUse)!;
}
Expand All @@ -813,15 +840,17 @@ class TabsRouter extends RoutingController {
_pages.clear();
_childControllers.clear();
_pushAll(routesToPush, fromDefault: routes == null);
var targetIndex = routesToPush.indexWhere((r) => r.name == previousActiveRoute.name);
var targetIndex =
routesToPush.indexWhere((r) => r.name == previousActiveRoute.name);
if (targetIndex == -1) {
targetIndex = homeIndex == -1 ? 0 : homeIndex;
}
setActiveIndex(targetIndex, notify: false);
}

@override
Future<void> _navigateAll(List<RouteMatch> routes, {OnNavigationFailure? onFailure}) async {
Future<void> _navigateAll(List<RouteMatch> routes,
{OnNavigationFailure? onFailure}) async {
if (routes.isNotEmpty) {
final mayUpdateRoute = routes.last;

Expand Down Expand Up @@ -920,7 +949,8 @@ class TabsRouter extends RoutingController {
fragment: fragment,
));
if (includeAncestors && _parent != null) {
_parent!._updateSharedPathData(queryParams: queryParams, fragment: fragment);
_parent!
._updateSharedPathData(queryParams: queryParams, fragment: fragment);
}
}

Expand Down Expand Up @@ -1090,7 +1120,8 @@ abstract class StackRouter extends RoutingController {

@override
RoutingController _topMostRouter({bool ignorePagelessRoutes = false}) {
if (_childControllers.isNotEmpty && (ignorePagelessRoutes || !hasPagelessTopRoute)) {
if (_childControllers.isNotEmpty &&
(ignorePagelessRoutes || !hasPagelessTopRoute)) {
var topRouteKey = currentChild?.key;
final innerRouter = _innerControllerOf(topRouteKey);
if (innerRouter != null) {
Expand Down Expand Up @@ -1208,19 +1239,23 @@ abstract class StackRouter extends RoutingController {
/// if [onFailure] callback is provided, navigation errors will be passed to it
/// otherwise they'll be thrown
@optionalTypeArgs
Future<T?> push<T extends Object?>(PageRouteInfo route, {OnNavigationFailure? onFailure}) async {
Future<T?> push<T extends Object?>(PageRouteInfo route,
{OnNavigationFailure? onFailure}) async {
return _findStackScope(route)._push<T>(route, onFailure: onFailure);
}

StackRouter _findStackScope(PageRouteInfo route) {
final stackRouters = _topMostRouter(ignorePagelessRoutes: true)._buildRoutersHierarchy().whereType<StackRouter>();
final stackRouters = _topMostRouter(ignorePagelessRoutes: true)
._buildRoutersHierarchy()
.whereType<StackRouter>();
return stackRouters.firstWhere(
(c) => c._canHandleNavigation(route),
orElse: () => this,
);
}

Future<dynamic> _popUntilOrPushAll(List<RouteMatch> routes, {OnNavigationFailure? onFailure}) async {
Future<dynamic> _popUntilOrPushAll(List<RouteMatch> routes,
{OnNavigationFailure? onFailure}) async {
final anchor = routes.first;
final anchorPage = _pages.lastWhereOrNull(
(p) => p.routeKey == anchor.key,
Expand Down Expand Up @@ -1530,7 +1565,8 @@ abstract class StackRouter extends RoutingController {
final result = await _canNavigate(
route,
onFailure: onFailure,
pendingRoutes: routes.whereIndexed((index, element) => index > i).toList(),
pendingRoutes:
routes.whereIndexed((index, element) => index > i).toList(),
isReevaluating: isReevaluating,
);
if (result.continueNavigation) {
Expand Down Expand Up @@ -1575,7 +1611,8 @@ abstract class StackRouter extends RoutingController {
return (page as AutoRoutePage<T>).popped;
}

late final AutoRouteGuard? _rootGuard = (root is AutoRouteGuard) ? (root as AutoRouteGuard) : null;
late final AutoRouteGuard? _rootGuard =
(root is AutoRouteGuard) ? (root as AutoRouteGuard) : null;

Future<ResolverResult> _canNavigate(
RouteMatch route, {
Expand Down
1 change: 0 additions & 1 deletion auto_route/lib/src/router/widgets/auto_leading_button.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import 'package:auto_route/auto_route.dart';
import 'package:auto_route/src/router/controller/pageless_routes_observer.dart';
import 'package:flutter/material.dart';

/// AppBar Leading button types
Expand Down
Loading

0 comments on commit 98c31c1

Please sign in to comment.