[go_router] Add `GoRouterState state` parameter to `GoRouterData.onExit` (#6495)
Part of https://github.com/flutter/flutter/issues/137394
I need to add the `state` parameter to the `onExit` method so I can use the `factoryImpl(state)`:
https://github.com/flutter/packages/blob/d4cd4f00254b2fdb50767c837ecd27bcbac488cd/packages/go_router/lib/src/route_data.dart#L100-L118
diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index 4cb037e..d38b99e 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,6 +1,7 @@
-## 13.2.5
+## 14.0.0
-- Fixes an issue where route future does not complete when popping shell route.
+- **BREAKING CHANGE**
+ - `GoRouteData`'s `onExit` now takes 2 parameters `BuildContext context, GoRouterState state`.
## 13.2.4
@@ -30,7 +31,7 @@
## 13.0.1
-* Fixes new lint warnings.
+- Fixes new lint warnings.
## 13.0.0
@@ -41,12 +42,12 @@
## 12.1.3
-* Fixes a typo in `navigation.md`.
+- Fixes a typo in `navigation.md`.
## 12.1.2
-* Fixes an incorrect use of `extends` for Dart 3 compatibility.
-* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
+- Fixes an incorrect use of `extends` for Dart 3 compatibility.
+- Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
## 12.1.1
@@ -121,7 +122,7 @@
## 10.1.2
-* Adds pub topics to package metadata.
+- Adds pub topics to package metadata.
## 10.1.1
@@ -452,7 +453,7 @@
- Fixes a bug where intermediate route redirect methods are not called.
- GoRouter implements the RouterConfig interface, allowing you to call
- MaterialApp.router(routerConfig: _myGoRouter) instead of passing
+ MaterialApp.router(routerConfig: \_myGoRouter) instead of passing
the RouterDelegate, RouteInformationParser, and RouteInformationProvider
fields.
- **BREAKING CHANGE**
diff --git a/packages/go_router/README.md b/packages/go_router/README.md
index a9f9b6b..7d506d0 100644
--- a/packages/go_router/README.md
+++ b/packages/go_router/README.md
@@ -37,6 +37,7 @@
- [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html)
## Migration Guides
+- [Migrating to 14.0.0](https://flutter.dev/go/go-router-v14-breaking-changes).
- [Migrating to 13.0.0](https://flutter.dev/go/go-router-v13-breaking-changes).
- [Migrating to 12.0.0](https://flutter.dev/go/go-router-v12-breaking-changes).
- [Migrating to 11.0.0](https://flutter.dev/go/go-router-v11-breaking-changes).
diff --git a/packages/go_router/example/lib/on_exit.dart b/packages/go_router/example/lib/on_exit.dart
index fba83a7..bf397ef 100644
--- a/packages/go_router/example/lib/on_exit.dart
+++ b/packages/go_router/example/lib/on_exit.dart
@@ -22,7 +22,10 @@
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen();
},
- onExit: (BuildContext context) async {
+ onExit: (
+ BuildContext context,
+ GoRouterState state,
+ ) async {
final bool? confirmed = await showDialog<bool>(
context: context,
builder: (_) {
diff --git a/packages/go_router/lib/src/delegate.dart b/packages/go_router/lib/src/delegate.dart
index bf5840c..f018589 100644
--- a/packages/go_router/lib/src/delegate.dart
+++ b/packages/go_router/lib/src/delegate.dart
@@ -66,13 +66,17 @@
}
walker = walker.matches.last;
}
+ assert(walker is RouteMatch);
if (state != null) {
return state.maybePop();
}
// This should be the only place where the last GoRoute exit the screen.
final GoRoute lastRoute = currentConfiguration.last.route;
if (lastRoute.onExit != null && navigatorKey.currentContext != null) {
- return !(await lastRoute.onExit!(navigatorKey.currentContext!));
+ return !(await lastRoute.onExit!(
+ navigatorKey.currentContext!,
+ walker.buildState(_configuration, currentConfiguration),
+ ));
}
return false;
}
@@ -137,8 +141,10 @@
// a microtask in case the onExit callback want to launch dialog or other
// navigator operations.
scheduleMicrotask(() async {
- final bool onExitResult =
- await routeBase.onExit!(navigatorKey.currentContext!);
+ final bool onExitResult = await routeBase.onExit!(
+ navigatorKey.currentContext!,
+ match.buildState(_configuration, currentConfiguration),
+ );
if (onExitResult) {
_completeRouteMatch(result, match);
}
@@ -221,14 +227,14 @@
}
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,
- context: navigatorContext, routes: exitingGoRoutes)
- .then<void>((bool exit) {
+ final List<RouteMatch> exitingMatches =
+ currentGoRouteMatches.sublist(indexOfFirstDiff).toList();
+ return _callOnExitStartsAt(
+ exitingMatches.length - 1,
+ context: navigatorContext,
+ matches: exitingMatches,
+ configuration: configuration,
+ ).then<void>((bool exit) {
if (!exit) {
return SynchronousFuture<void>(null);
}
@@ -244,24 +250,42 @@
///
/// 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 context, required List<GoRoute> routes}) {
+ Future<bool> _callOnExitStartsAt(
+ int index, {
+ required BuildContext context,
+ required List<RouteMatch> matches,
+ required RouteMatchList configuration,
+ }) {
if (index < 0) {
return SynchronousFuture<bool>(true);
}
- final GoRoute goRoute = routes[index];
+ final RouteMatch match = matches[index];
+ final GoRoute goRoute = match.route;
if (goRoute.onExit == null) {
- return _callOnExitStartsAt(index - 1, context: context, routes: routes);
+ return _callOnExitStartsAt(
+ index - 1,
+ context: context,
+ matches: matches,
+ configuration: configuration,
+ );
}
Future<bool> handleOnExitResult(bool exit) {
if (exit) {
- return _callOnExitStartsAt(index - 1, context: context, routes: routes);
+ return _callOnExitStartsAt(
+ index - 1,
+ context: context,
+ matches: matches,
+ configuration: configuration,
+ );
}
return SynchronousFuture<bool>(false);
}
- final FutureOr<bool> exitFuture = goRoute.onExit!(context);
+ final FutureOr<bool> exitFuture = goRoute.onExit!(
+ context,
+ match.buildState(_configuration, configuration),
+ );
if (exitFuture is bool) {
return handleOnExitResult(exitFuture);
}
diff --git a/packages/go_router/lib/src/route.dart b/packages/go_router/lib/src/route.dart
index 2fa4bd0..69dd904 100644
--- a/packages/go_router/lib/src/route.dart
+++ b/packages/go_router/lib/src/route.dart
@@ -63,7 +63,8 @@
///
/// If the return value is true or the future resolve to true, the route will
/// exit as usual. Otherwise, the operation will abort.
-typedef ExitCallback = FutureOr<bool> Function(BuildContext context);
+typedef ExitCallback = FutureOr<bool> Function(
+ BuildContext context, GoRouterState state);
/// The base class for [GoRoute] and [ShellRoute].
///
diff --git a/packages/go_router/lib/src/route_data.dart b/packages/go_router/lib/src/route_data.dart
index 26a6fc4..b10f2b1 100644
--- a/packages/go_router/lib/src/route_data.dart
+++ b/packages/go_router/lib/src/route_data.dart
@@ -63,6 +63,11 @@
/// Corresponds to [GoRoute.redirect].
FutureOr<String?> redirect(BuildContext context, GoRouterState state) => null;
+ /// Called when this route is removed from GoRouter's route history.
+ ///
+ /// Corresponds to [GoRoute.onExit].
+ FutureOr<bool> onExit(BuildContext context, GoRouterState state) => true;
+
/// A helper function used by generated code.
///
/// Should not be used directly.
@@ -106,6 +111,9 @@
FutureOr<String?> redirect(BuildContext context, GoRouterState state) =>
factoryImpl(state).redirect(context, state);
+ FutureOr<bool> onExit(BuildContext context, GoRouterState state) =>
+ factoryImpl(state).onExit(context, state);
+
return GoRoute(
path: path,
name: name,
@@ -114,6 +122,7 @@
redirect: redirect,
routes: routes,
parentNavigatorKey: parentNavigatorKey,
+ onExit: onExit,
);
}
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index 25ec466..febb4db 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: 13.2.5
+version: 14.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
diff --git a/packages/go_router/test/on_exit_test.dart b/packages/go_router/test/on_exit_test.dart
index 044a6ff..ceeb680 100644
--- a/packages/go_router/test/on_exit_test.dart
+++ b/packages/go_router/test/on_exit_test.dart
@@ -25,7 +25,7 @@
path: '1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
- onExit: (BuildContext context) {
+ onExit: (BuildContext context, GoRouterState state) {
return allow;
},
)
@@ -61,7 +61,7 @@
path: '/1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
- onExit: (BuildContext context) {
+ onExit: (BuildContext context, GoRouterState state) {
return allow;
},
)
@@ -95,7 +95,7 @@
path: '1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
- onExit: (BuildContext context) async {
+ onExit: (BuildContext context, GoRouterState state) async {
return allow.future;
},
)
@@ -139,7 +139,7 @@
path: '/1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
- onExit: (BuildContext context) async {
+ onExit: (BuildContext context, GoRouterState state) async {
return allow.future;
},
)
@@ -176,7 +176,7 @@
path: '/',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: home),
- onExit: (BuildContext context) {
+ onExit: (BuildContext context, GoRouterState state) {
return allow;
},
),
@@ -201,7 +201,7 @@
path: '/',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: home),
- onExit: (BuildContext context) async {
+ onExit: (BuildContext context, GoRouterState state) async {
return allow;
},
),
@@ -227,7 +227,7 @@
path: '/',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: home),
- onExit: (BuildContext context) {
+ onExit: (BuildContext context, GoRouterState state) {
return allow;
},
),
@@ -243,4 +243,75 @@
allow = true;
expect(await router.routerDelegate.popRoute(), false);
});
+
+ testWidgets('It should provide the correct state to the onExit callback',
+ (WidgetTester tester) async {
+ final UniqueKey home = UniqueKey();
+ final UniqueKey page1 = UniqueKey();
+ final UniqueKey page2 = UniqueKey();
+ final UniqueKey page3 = UniqueKey();
+ late final GoRouterState onExitState1;
+ late final GoRouterState onExitState2;
+ late final GoRouterState onExitState3;
+ final List<GoRoute> routes = <GoRoute>[
+ GoRoute(
+ path: '/',
+ builder: (BuildContext context, GoRouterState state) =>
+ DummyScreen(key: home),
+ routes: <GoRoute>[
+ GoRoute(
+ path: '1',
+ builder: (BuildContext context, GoRouterState state) =>
+ DummyScreen(key: page1),
+ onExit: (BuildContext context, GoRouterState state) {
+ onExitState1 = state;
+ return true;
+ },
+ routes: <GoRoute>[
+ GoRoute(
+ path: '2',
+ builder: (BuildContext context, GoRouterState state) =>
+ DummyScreen(key: page2),
+ onExit: (BuildContext context, GoRouterState state) {
+ onExitState2 = state;
+ return true;
+ },
+ routes: <GoRoute>[
+ GoRoute(
+ path: '3',
+ builder: (BuildContext context, GoRouterState state) =>
+ DummyScreen(key: page3),
+ onExit: (BuildContext context, GoRouterState state) {
+ onExitState3 = state;
+ return true;
+ },
+ )
+ ],
+ )
+ ],
+ )
+ ],
+ ),
+ ];
+
+ final GoRouter router =
+ await createRouter(routes, tester, initialLocation: '/1/2/3');
+ expect(find.byKey(page3), findsOneWidget);
+
+ router.pop();
+ await tester.pumpAndSettle();
+ expect(find.byKey(page2), findsOneWidget);
+
+ expect(onExitState3.uri.toString(), '/1/2/3');
+
+ router.pop();
+ await tester.pumpAndSettle();
+ expect(find.byKey(page1), findsOneWidget);
+ expect(onExitState2.uri.toString(), '/1/2');
+
+ router.pop();
+ await tester.pumpAndSettle();
+ expect(find.byKey(home), findsOneWidget);
+ expect(onExitState1.uri.toString(), '/1');
+ });
}