Skip to content

Commit

Permalink
[go_router] Add GoRouterState state parameter to `GoRouterData.onEx…
Browse files Browse the repository at this point in the history
…it` (#6495)

Part of flutter/flutter#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
  • Loading branch information
ValentinVignal committed Apr 24, 2024
1 parent 1292dc3 commit 26f660b
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 35 deletions.
17 changes: 9 additions & 8 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -30,7 +31,7 @@

## 13.0.1

* Fixes new lint warnings.
- Fixes new lint warnings.

## 13.0.0

Expand All @@ -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

Expand Down Expand Up @@ -121,7 +122,7 @@

## 10.1.2

* Adds pub topics to package metadata.
- Adds pub topics to package metadata.

## 10.1.1

Expand Down Expand Up @@ -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**
Expand Down
1 change: 1 addition & 0 deletions packages/go_router/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 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).
Expand Down
5 changes: 4 additions & 1 deletion packages/go_router/example/lib/on_exit.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ final GoRouter _router = GoRouter(
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: (_) {
Expand Down
58 changes: 41 additions & 17 deletions packages/go_router/lib/src/delegate.dart
Original file line number Diff line number Diff line change
Expand Up @@ -66,13 +66,17 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
}
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;
}
Expand Down Expand Up @@ -137,8 +141,10 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
// 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);
}
Expand Down Expand Up @@ -221,14 +227,14 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
}

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);
}
Expand All @@ -244,24 +250,42 @@ 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 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);
}
Expand Down
3 changes: 2 additions & 1 deletion packages/go_router/lib/src/route.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ typedef NavigatorBuilder = Widget Function(
///
/// 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].
///
Expand Down
9 changes: 9 additions & 0 deletions packages/go_router/lib/src/route_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ abstract class GoRouteData extends RouteData {
/// 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.
Expand Down Expand Up @@ -106,6 +111,9 @@ abstract class GoRouteData extends RouteData {
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,
Expand All @@ -114,6 +122,7 @@ abstract class GoRouteData extends RouteData {
redirect: redirect,
routes: routes,
parentNavigatorKey: parentNavigatorKey,
onExit: onExit,
);
}

Expand Down
2 changes: 1 addition & 1 deletion packages/go_router/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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

Expand Down
85 changes: 78 additions & 7 deletions packages/go_router/test/on_exit_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ void main() {
path: '1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
onExit: (BuildContext context) {
onExit: (BuildContext context, GoRouterState state) {
return allow;
},
)
Expand Down Expand Up @@ -61,7 +61,7 @@ void main() {
path: '/1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
onExit: (BuildContext context) {
onExit: (BuildContext context, GoRouterState state) {
return allow;
},
)
Expand Down Expand Up @@ -95,7 +95,7 @@ void main() {
path: '1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
onExit: (BuildContext context) async {
onExit: (BuildContext context, GoRouterState state) async {
return allow.future;
},
)
Expand Down Expand Up @@ -139,7 +139,7 @@ void main() {
path: '/1',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: page1),
onExit: (BuildContext context) async {
onExit: (BuildContext context, GoRouterState state) async {
return allow.future;
},
)
Expand Down Expand Up @@ -176,7 +176,7 @@ void main() {
path: '/',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: home),
onExit: (BuildContext context) {
onExit: (BuildContext context, GoRouterState state) {
return allow;
},
),
Expand All @@ -201,7 +201,7 @@ void main() {
path: '/',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: home),
onExit: (BuildContext context) async {
onExit: (BuildContext context, GoRouterState state) async {
return allow;
},
),
Expand All @@ -227,7 +227,7 @@ void main() {
path: '/',
builder: (BuildContext context, GoRouterState state) =>
DummyScreen(key: home),
onExit: (BuildContext context) {
onExit: (BuildContext context, GoRouterState state) {
return allow;
},
),
Expand All @@ -243,4 +243,75 @@ void main() {
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');
});
}

0 comments on commit 26f660b

Please sign in to comment.