Skip to content

Commit 26f660b

Browse files
[go_router] Add GoRouterState state parameter to GoRouterData.onExit (flutter#6495)
Part of 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
1 parent 1292dc3 commit 26f660b

File tree

8 files changed

+145
-35
lines changed

8 files changed

+145
-35
lines changed

packages/go_router/CHANGELOG.md

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
## 13.2.5
1+
## 14.0.0
22

3-
- Fixes an issue where route future does not complete when popping shell route.
3+
- **BREAKING CHANGE**
4+
- `GoRouteData`'s `onExit` now takes 2 parameters `BuildContext context, GoRouterState state`.
45

56
## 13.2.4
67

@@ -30,7 +31,7 @@
3031

3132
## 13.0.1
3233

33-
* Fixes new lint warnings.
34+
- Fixes new lint warnings.
3435

3536
## 13.0.0
3637

@@ -41,12 +42,12 @@
4142

4243
## 12.1.3
4344

44-
* Fixes a typo in `navigation.md`.
45+
- Fixes a typo in `navigation.md`.
4546

4647
## 12.1.2
4748

48-
* Fixes an incorrect use of `extends` for Dart 3 compatibility.
49-
* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
49+
- Fixes an incorrect use of `extends` for Dart 3 compatibility.
50+
- Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.
5051

5152
## 12.1.1
5253

@@ -121,7 +122,7 @@
121122

122123
## 10.1.2
123124

124-
* Adds pub topics to package metadata.
125+
- Adds pub topics to package metadata.
125126

126127
## 10.1.1
127128

@@ -452,7 +453,7 @@
452453

453454
- Fixes a bug where intermediate route redirect methods are not called.
454455
- GoRouter implements the RouterConfig interface, allowing you to call
455-
MaterialApp.router(routerConfig: _myGoRouter) instead of passing
456+
MaterialApp.router(routerConfig: \_myGoRouter) instead of passing
456457
the RouterDelegate, RouteInformationParser, and RouteInformationProvider
457458
fields.
458459
- **BREAKING CHANGE**

packages/go_router/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ See the API documentation for details on the following topics:
3737
- [Error handling](https://pub.dev/documentation/go_router/latest/topics/Error%20handling-topic.html)
3838

3939
## Migration Guides
40+
- [Migrating to 14.0.0](https://flutter.dev/go/go-router-v14-breaking-changes).
4041
- [Migrating to 13.0.0](https://flutter.dev/go/go-router-v13-breaking-changes).
4142
- [Migrating to 12.0.0](https://flutter.dev/go/go-router-v12-breaking-changes).
4243
- [Migrating to 11.0.0](https://flutter.dev/go/go-router-v11-breaking-changes).

packages/go_router/example/lib/on_exit.dart

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ final GoRouter _router = GoRouter(
2222
builder: (BuildContext context, GoRouterState state) {
2323
return const DetailsScreen();
2424
},
25-
onExit: (BuildContext context) async {
25+
onExit: (
26+
BuildContext context,
27+
GoRouterState state,
28+
) async {
2629
final bool? confirmed = await showDialog<bool>(
2730
context: context,
2831
builder: (_) {

packages/go_router/lib/src/delegate.dart

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,17 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
6666
}
6767
walker = walker.matches.last;
6868
}
69+
assert(walker is RouteMatch);
6970
if (state != null) {
7071
return state.maybePop();
7172
}
7273
// This should be the only place where the last GoRoute exit the screen.
7374
final GoRoute lastRoute = currentConfiguration.last.route;
7475
if (lastRoute.onExit != null && navigatorKey.currentContext != null) {
75-
return !(await lastRoute.onExit!(navigatorKey.currentContext!));
76+
return !(await lastRoute.onExit!(
77+
navigatorKey.currentContext!,
78+
walker.buildState(_configuration, currentConfiguration),
79+
));
7680
}
7781
return false;
7882
}
@@ -137,8 +141,10 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
137141
// a microtask in case the onExit callback want to launch dialog or other
138142
// navigator operations.
139143
scheduleMicrotask(() async {
140-
final bool onExitResult =
141-
await routeBase.onExit!(navigatorKey.currentContext!);
144+
final bool onExitResult = await routeBase.onExit!(
145+
navigatorKey.currentContext!,
146+
match.buildState(_configuration, currentConfiguration),
147+
);
142148
if (onExitResult) {
143149
_completeRouteMatch(result, match);
144150
}
@@ -221,14 +227,14 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
221227
}
222228

223229
if (indexOfFirstDiff < currentGoRouteMatches.length) {
224-
final List<GoRoute> exitingGoRoutes = currentGoRouteMatches
225-
.sublist(indexOfFirstDiff)
226-
.map<RouteBase>((RouteMatch match) => match.route)
227-
.whereType<GoRoute>()
228-
.toList();
229-
return _callOnExitStartsAt(exitingGoRoutes.length - 1,
230-
context: navigatorContext, routes: exitingGoRoutes)
231-
.then<void>((bool exit) {
230+
final List<RouteMatch> exitingMatches =
231+
currentGoRouteMatches.sublist(indexOfFirstDiff).toList();
232+
return _callOnExitStartsAt(
233+
exitingMatches.length - 1,
234+
context: navigatorContext,
235+
matches: exitingMatches,
236+
configuration: configuration,
237+
).then<void>((bool exit) {
232238
if (!exit) {
233239
return SynchronousFuture<void>(null);
234240
}
@@ -244,24 +250,42 @@ class GoRouterDelegate extends RouterDelegate<RouteMatchList>
244250
///
245251
/// The returned future resolves to true if all routes below the index all
246252
/// return true. Otherwise, the returned future resolves to false.
247-
static Future<bool> _callOnExitStartsAt(int index,
248-
{required BuildContext context, required List<GoRoute> routes}) {
253+
Future<bool> _callOnExitStartsAt(
254+
int index, {
255+
required BuildContext context,
256+
required List<RouteMatch> matches,
257+
required RouteMatchList configuration,
258+
}) {
249259
if (index < 0) {
250260
return SynchronousFuture<bool>(true);
251261
}
252-
final GoRoute goRoute = routes[index];
262+
final RouteMatch match = matches[index];
263+
final GoRoute goRoute = match.route;
253264
if (goRoute.onExit == null) {
254-
return _callOnExitStartsAt(index - 1, context: context, routes: routes);
265+
return _callOnExitStartsAt(
266+
index - 1,
267+
context: context,
268+
matches: matches,
269+
configuration: configuration,
270+
);
255271
}
256272

257273
Future<bool> handleOnExitResult(bool exit) {
258274
if (exit) {
259-
return _callOnExitStartsAt(index - 1, context: context, routes: routes);
275+
return _callOnExitStartsAt(
276+
index - 1,
277+
context: context,
278+
matches: matches,
279+
configuration: configuration,
280+
);
260281
}
261282
return SynchronousFuture<bool>(false);
262283
}
263284

264-
final FutureOr<bool> exitFuture = goRoute.onExit!(context);
285+
final FutureOr<bool> exitFuture = goRoute.onExit!(
286+
context,
287+
match.buildState(_configuration, configuration),
288+
);
265289
if (exitFuture is bool) {
266290
return handleOnExitResult(exitFuture);
267291
}

packages/go_router/lib/src/route.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ typedef NavigatorBuilder = Widget Function(
6363
///
6464
/// If the return value is true or the future resolve to true, the route will
6565
/// exit as usual. Otherwise, the operation will abort.
66-
typedef ExitCallback = FutureOr<bool> Function(BuildContext context);
66+
typedef ExitCallback = FutureOr<bool> Function(
67+
BuildContext context, GoRouterState state);
6768

6869
/// The base class for [GoRoute] and [ShellRoute].
6970
///

packages/go_router/lib/src/route_data.dart

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ abstract class GoRouteData extends RouteData {
6363
/// Corresponds to [GoRoute.redirect].
6464
FutureOr<String?> redirect(BuildContext context, GoRouterState state) => null;
6565

66+
/// Called when this route is removed from GoRouter's route history.
67+
///
68+
/// Corresponds to [GoRoute.onExit].
69+
FutureOr<bool> onExit(BuildContext context, GoRouterState state) => true;
70+
6671
/// A helper function used by generated code.
6772
///
6873
/// Should not be used directly.
@@ -106,6 +111,9 @@ abstract class GoRouteData extends RouteData {
106111
FutureOr<String?> redirect(BuildContext context, GoRouterState state) =>
107112
factoryImpl(state).redirect(context, state);
108113

114+
FutureOr<bool> onExit(BuildContext context, GoRouterState state) =>
115+
factoryImpl(state).onExit(context, state);
116+
109117
return GoRoute(
110118
path: path,
111119
name: name,
@@ -114,6 +122,7 @@ abstract class GoRouteData extends RouteData {
114122
redirect: redirect,
115123
routes: routes,
116124
parentNavigatorKey: parentNavigatorKey,
125+
onExit: onExit,
117126
);
118127
}
119128

packages/go_router/pubspec.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name: go_router
22
description: A declarative router for Flutter based on Navigation 2 supporting
33
deep linking, data-driven routes and more
4-
version: 13.2.5
4+
version: 14.0.0
55
repository: https://github.com/flutter/packages/tree/main/packages/go_router
66
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
77

packages/go_router/test/on_exit_test.dart

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ void main() {
2525
path: '1',
2626
builder: (BuildContext context, GoRouterState state) =>
2727
DummyScreen(key: page1),
28-
onExit: (BuildContext context) {
28+
onExit: (BuildContext context, GoRouterState state) {
2929
return allow;
3030
},
3131
)
@@ -61,7 +61,7 @@ void main() {
6161
path: '/1',
6262
builder: (BuildContext context, GoRouterState state) =>
6363
DummyScreen(key: page1),
64-
onExit: (BuildContext context) {
64+
onExit: (BuildContext context, GoRouterState state) {
6565
return allow;
6666
},
6767
)
@@ -95,7 +95,7 @@ void main() {
9595
path: '1',
9696
builder: (BuildContext context, GoRouterState state) =>
9797
DummyScreen(key: page1),
98-
onExit: (BuildContext context) async {
98+
onExit: (BuildContext context, GoRouterState state) async {
9999
return allow.future;
100100
},
101101
)
@@ -139,7 +139,7 @@ void main() {
139139
path: '/1',
140140
builder: (BuildContext context, GoRouterState state) =>
141141
DummyScreen(key: page1),
142-
onExit: (BuildContext context) async {
142+
onExit: (BuildContext context, GoRouterState state) async {
143143
return allow.future;
144144
},
145145
)
@@ -176,7 +176,7 @@ void main() {
176176
path: '/',
177177
builder: (BuildContext context, GoRouterState state) =>
178178
DummyScreen(key: home),
179-
onExit: (BuildContext context) {
179+
onExit: (BuildContext context, GoRouterState state) {
180180
return allow;
181181
},
182182
),
@@ -201,7 +201,7 @@ void main() {
201201
path: '/',
202202
builder: (BuildContext context, GoRouterState state) =>
203203
DummyScreen(key: home),
204-
onExit: (BuildContext context) async {
204+
onExit: (BuildContext context, GoRouterState state) async {
205205
return allow;
206206
},
207207
),
@@ -227,7 +227,7 @@ void main() {
227227
path: '/',
228228
builder: (BuildContext context, GoRouterState state) =>
229229
DummyScreen(key: home),
230-
onExit: (BuildContext context) {
230+
onExit: (BuildContext context, GoRouterState state) {
231231
return allow;
232232
},
233233
),
@@ -243,4 +243,75 @@ void main() {
243243
allow = true;
244244
expect(await router.routerDelegate.popRoute(), false);
245245
});
246+
247+
testWidgets('It should provide the correct state to the onExit callback',
248+
(WidgetTester tester) async {
249+
final UniqueKey home = UniqueKey();
250+
final UniqueKey page1 = UniqueKey();
251+
final UniqueKey page2 = UniqueKey();
252+
final UniqueKey page3 = UniqueKey();
253+
late final GoRouterState onExitState1;
254+
late final GoRouterState onExitState2;
255+
late final GoRouterState onExitState3;
256+
final List<GoRoute> routes = <GoRoute>[
257+
GoRoute(
258+
path: '/',
259+
builder: (BuildContext context, GoRouterState state) =>
260+
DummyScreen(key: home),
261+
routes: <GoRoute>[
262+
GoRoute(
263+
path: '1',
264+
builder: (BuildContext context, GoRouterState state) =>
265+
DummyScreen(key: page1),
266+
onExit: (BuildContext context, GoRouterState state) {
267+
onExitState1 = state;
268+
return true;
269+
},
270+
routes: <GoRoute>[
271+
GoRoute(
272+
path: '2',
273+
builder: (BuildContext context, GoRouterState state) =>
274+
DummyScreen(key: page2),
275+
onExit: (BuildContext context, GoRouterState state) {
276+
onExitState2 = state;
277+
return true;
278+
},
279+
routes: <GoRoute>[
280+
GoRoute(
281+
path: '3',
282+
builder: (BuildContext context, GoRouterState state) =>
283+
DummyScreen(key: page3),
284+
onExit: (BuildContext context, GoRouterState state) {
285+
onExitState3 = state;
286+
return true;
287+
},
288+
)
289+
],
290+
)
291+
],
292+
)
293+
],
294+
),
295+
];
296+
297+
final GoRouter router =
298+
await createRouter(routes, tester, initialLocation: '/1/2/3');
299+
expect(find.byKey(page3), findsOneWidget);
300+
301+
router.pop();
302+
await tester.pumpAndSettle();
303+
expect(find.byKey(page2), findsOneWidget);
304+
305+
expect(onExitState3.uri.toString(), '/1/2/3');
306+
307+
router.pop();
308+
await tester.pumpAndSettle();
309+
expect(find.byKey(page1), findsOneWidget);
310+
expect(onExitState2.uri.toString(), '/1/2');
311+
312+
router.pop();
313+
await tester.pumpAndSettle();
314+
expect(find.byKey(home), findsOneWidget);
315+
expect(onExitState1.uri.toString(), '/1');
316+
});
246317
}

0 commit comments

Comments
 (0)