Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 17.2.0

- Adds `overrideOnExit` parameter to `TypedGoRoute` and `TypedRelativeGoRoute` to control whether the `onExit` method of route data classes should be invoked.

## 17.1.0

- Adds `TypedQueryParameter` annotation to override parameter names in `TypedGoRoute` constructors.
Expand Down
41 changes: 37 additions & 4 deletions packages/go_router/lib/src/route_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,19 +90,20 @@ class _GoRouteParameters {
required this.builder,
required this.pageBuilder,
required this.redirect,
required this.onExit,
this.onExit,
});

final GoRouterWidgetBuilder builder;
final GoRouterPageBuilder pageBuilder;
final GoRouterRedirect redirect;
final ExitCallback onExit;
final ExitCallback? onExit;
}

/// Helper to create [GoRoute] parameters from a factory function and an Expando.
_GoRouteParameters _createGoRouteParameters<T extends _GoRouteDataBase>({
required T Function(GoRouterState) factory,
required Expando<_GoRouteDataBase> expando,
bool overrideOnExit = false,
}) {
T factoryImpl(GoRouterState state) {
final Object? extra = state.extra;
Expand All @@ -123,8 +124,10 @@ _GoRouteParameters _createGoRouteParameters<T extends _GoRouteDataBase>({
factoryImpl(state).buildPage(context, state),
redirect: (BuildContext context, GoRouterState state) =>
factoryImpl(state).redirect(context, state),
onExit: (BuildContext context, GoRouterState state) =>
factoryImpl(state).onExit(context, state),
onExit: overrideOnExit
? (BuildContext context, GoRouterState state) =>
factoryImpl(state).onExit(context, state)
: null,
);
}

Expand Down Expand Up @@ -156,10 +159,12 @@ abstract class GoRouteData extends _GoRouteDataBase {
required T Function(GoRouterState) factory,
GlobalKey<NavigatorState>? parentNavigatorKey,
List<RouteBase> routes = const <RouteBase>[],
bool overrideOnExit = false,
}) {
final _GoRouteParameters params = _createGoRouteParameters<T>(
factory: factory,
expando: _GoRouteDataBase.stateObjectExpando,
overrideOnExit: overrideOnExit,
);

return GoRoute(
Expand Down Expand Up @@ -227,10 +232,12 @@ abstract class RelativeGoRouteData extends _GoRouteDataBase {
required T Function(GoRouterState) factory,
GlobalKey<NavigatorState>? parentNavigatorKey,
List<RouteBase> routes = const <RouteBase>[],
bool overrideOnExit = false,
}) {
final _GoRouteParameters params = _createGoRouteParameters<T>(
factory: factory,
expando: _GoRouteDataBase.stateObjectExpando,
overrideOnExit: overrideOnExit,
);

return GoRoute(
Expand Down Expand Up @@ -483,6 +490,7 @@ class TypedGoRoute<T extends GoRouteData> extends TypedRoute<T> {
this.name,
this.routes = const <TypedRoute<RouteData>>[],
this.caseSensitive = true,
this.overrideOnExit = false,
});

/// The path that corresponds to this route.
Expand Down Expand Up @@ -515,6 +523,18 @@ class TypedGoRoute<T extends GoRouteData> extends TypedRoute<T> {
///
/// Defaults to `true`.
final bool caseSensitive;

/// Whether to override the default behavior of [GoRoute.onExit] to invoke the
/// [GoRouteData.onExit] method of the route data class.
///
/// When `true`, the [GoRouteData.onExit] method will be invoked when
/// the route is removed from GoRouter's route history.
///
/// When `false`, the default behavior is used, which does not invoke
/// the [GoRouteData.onExit] method.
///
/// Defaults to `false`.
final bool overrideOnExit;
Comment on lines +527 to +537

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The name overrideOnExit is a bit confusing. It seems to imply it's overriding something, but it's actually enabling a feature. A name like enableOnExit or useDataOnExit might be clearer.

Also, the documentation says 'Whether to override the default behavior of GoRoute.onExit...'. The onExit parameter on GoRoute doesn't have a 'default behavior'; it's either provided or it's null.

Perhaps the documentation could be rephrased for clarity. This comment also applies to TypedRelativeGoRoute on lines 575-585.

Suggested change
/// Whether to override the default behavior of [GoRoute.onExit] to invoke the
/// [GoRouteData.onExit] method of the route data class.
///
/// When `true`, the [GoRouteData.onExit] method will be invoked when
/// the route is removed from GoRouter's route history.
///
/// When `false`, the default behavior is used, which does not invoke
/// the [GoRouteData.onExit] method.
///
/// Defaults to `false`.
final bool overrideOnExit;
/// Whether to use the `onExit` callback from the associated
/// [GoRouteData] class.
///
/// When `true`, the [GoRouteData.onExit] method will be invoked when
/// the route is removed from GoRouter's route history.
///
/// When `false` (the default), the [GoRouteData.onExit] method is not
/// invoked.
///
/// Defaults to `false`.
final bool overrideOnExit;

}

/// A superclass for each typed relative go route descendant
Expand All @@ -526,6 +546,7 @@ class TypedRelativeGoRoute<T extends RelativeGoRouteData>
required this.path,
this.routes = const <TypedRoute<RouteData>>[],
this.caseSensitive = true,
this.overrideOnExit = false,
});

/// The relative path that corresponds to this route.
Expand All @@ -550,6 +571,18 @@ class TypedRelativeGoRoute<T extends RelativeGoRouteData>
///
/// Defaults to `true`.
final bool caseSensitive;

/// Whether to override the default behavior of [GoRoute.onExit] to invoke the
/// [RelativeGoRouteData.onExit] method of the route data class.
///
/// When `true`, the [RelativeGoRouteData.onExit] method will be invoked when
/// the route is removed from GoRouter's route history.
///
/// When `false`, the default behavior is used, which does not invoke
/// the [RelativeGoRouteData.onExit] method.
///
/// Defaults to `false`.
final bool overrideOnExit;
}

/// A superclass for each typed shell route descendant
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: 17.1.0
version: 17.2.0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This change appears to be a breaking change. Previously, onExit on a GoRouteData subclass was always wired up. With this change, it is now opt-in via the overrideOnExit flag, which defaults to false. This means existing code that relies on onExit will silently stop working until overrideOnExit: true is added. According to semantic versioning, this should probably be a major version bump, and the changelog should reflect this with a BREAKING CHANGE notice.

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
19 changes: 19 additions & 0 deletions packages/go_router/test/route_data_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ void main() {
expect(typedGoRoute.path, '/path');
expect(typedGoRoute.name, isNull);
expect(typedGoRoute.caseSensitive, true);
expect(typedGoRoute.overrideOnExit, false);
expect(typedGoRoute.routes, isEmpty);
});

Expand All @@ -709,18 +710,21 @@ void main() {
path: '/path',
name: 'name',
caseSensitive: false,
overrideOnExit: true,
routes: <TypedRoute<RouteData>>[
TypedGoRoute<GoRouteData>(
path: 'sub-path',
name: 'subName',
caseSensitive: false,
overrideOnExit: true,
),
],
);

expect(typedGoRoute.path, '/path');
expect(typedGoRoute.name, 'name');
expect(typedGoRoute.caseSensitive, false);
expect(typedGoRoute.overrideOnExit, true);
expect(typedGoRoute.routes, hasLength(1));
expect(
typedGoRoute.routes.single,
Expand All @@ -739,6 +743,11 @@ void main() {
(TypedGoRoute<GoRouteData> route) => route.caseSensitive,
'caseSensitive',
false,
)
.having(
(TypedGoRoute<GoRouteData> route) => route.overrideOnExit,
'overrideOnExit',
true,
),
);
});
Expand All @@ -760,23 +769,27 @@ void main() {

expect(typedGoRoute.path, 'path');
expect(typedGoRoute.caseSensitive, true);
expect(typedGoRoute.overrideOnExit, false);
expect(typedGoRoute.routes, isEmpty);
});

test('TypedRelativeGoRoute with provided parameters', () {
const typedGoRoute = TypedRelativeGoRoute<RelativeGoRouteData>(
path: 'path',
caseSensitive: false,
overrideOnExit: true,
routes: <TypedRoute<RouteData>>[
TypedRelativeGoRoute<RelativeGoRouteData>(
path: 'sub-path',
caseSensitive: false,
overrideOnExit: true,
),
],
);

expect(typedGoRoute.path, 'path');
expect(typedGoRoute.caseSensitive, false);
expect(typedGoRoute.overrideOnExit, true);
expect(typedGoRoute.routes, hasLength(1));
expect(
typedGoRoute.routes.single,
Expand All @@ -791,6 +804,12 @@ void main() {
route.caseSensitive,
'caseSensitive',
false,
)
.having(
(TypedRelativeGoRoute<RelativeGoRouteData> route) =>
route.overrideOnExit,
'overrideOnExit',
true,
),
);
});
Expand Down