Skip to content
Merged
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
6 changes: 4 additions & 2 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
## NEXT
## 16.2.0

* Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.
- Adds `RelativeGoRouteData` and `TypedRelativeGoRoute`.
- Updates minimum supported SDK version to Flutter 3.29/Dart 3.7.

## 16.1.0

- Adds annotation for go_router_builder that enable custom string encoder/decoder [#110781](https://github.com/flutter/flutter/issues/110781). **Requires go_router_builder >= 3.1.0**.

## 16.0.0
Expand Down
263 changes: 208 additions & 55 deletions packages/go_router/lib/src/route_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
import 'package:meta/meta.dart';
import 'package:meta/meta_meta.dart';

import 'configuration.dart';
import 'route.dart';
import 'state.dart';

Expand All @@ -18,17 +19,10 @@ abstract class RouteData {
const RouteData();
}

/// A class to represent a [GoRoute] in
/// [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).
///
/// Subclasses must override one of [build], [buildPage], or
/// [redirect].
/// {@category Type-safe routes}
abstract class GoRouteData extends RouteData {
/// Allows subclasses to have `const` constructors.
///
/// [GoRouteData] is abstract and cannot be instantiated directly.
const GoRouteData();
/// A base class for [GoRouteData] and [RelativeGoRouteData] that provides
/// common functionality for type-safe routing.
abstract class _GoRouteDataBase extends RouteData {
const _GoRouteDataBase();

/// Creates the [Widget] for `this` route.
///
Expand Down Expand Up @@ -68,17 +62,93 @@ abstract class GoRouteData extends RouteData {
/// Corresponds to [GoRoute.onExit].
FutureOr<bool> onExit(BuildContext context, GoRouterState state) => true;

/// The error thrown when a user-facing method is not implemented by the
/// generated code.
static UnimplementedError get shouldBeGeneratedError => UnimplementedError(
'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).',
);

/// Used to cache [_GoRouteDataBase] that corresponds to a given [GoRouterState]
/// to minimize the number of times it has to be deserialized.
static final Expando<_GoRouteDataBase> stateObjectExpando =
Expando<_GoRouteDataBase>('GoRouteState to _GoRouteDataBase expando');
}

/// Helper to build a location string from a path and query parameters.
String _buildLocation(String path, {Map<String, dynamic>? queryParams}) =>
Uri.parse(path)
.replace(
queryParameters:
// Avoid `?` in generated location if `queryParams` is empty
queryParams?.isNotEmpty ?? false ? queryParams : null,
)
.toString();

/// Holds the parameters for constructing a [GoRoute].
class _GoRouteParameters {
const _GoRouteParameters({
required this.builder,
required this.pageBuilder,
required this.redirect,
required this.onExit,
});

final GoRouterWidgetBuilder builder;
final GoRouterPageBuilder pageBuilder;
final GoRouterRedirect redirect;
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,
}) {
T factoryImpl(GoRouterState state) {
final Object? extra = state.extra;

// If the "extra" value is of type `T` then we know it's the source
// instance, so it doesn't need to be recreated.
if (extra is T) {
return extra;
}

return (expando[state] ??= factory(state)) as T;
}

return _GoRouteParameters(
builder:
(BuildContext context, GoRouterState state) =>
factoryImpl(state).build(context, state),
pageBuilder:
(BuildContext context, GoRouterState state) =>
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),
);
}

/// A class to represent a [GoRoute] in
/// [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).
///
/// Subclasses must override one of [build], [buildPage], or
/// [redirect].
/// {@category Type-safe routes}
abstract class GoRouteData extends _GoRouteDataBase {
/// Allows subclasses to have `const` constructors.
///
/// [GoRouteData] is abstract and cannot be instantiated directly.
const GoRouteData();

/// A helper function used by generated code.
///
/// Should not be used directly.
static String $location(String path, {Map<String, dynamic>? queryParams}) =>
Uri.parse(path)
.replace(
queryParameters:
// Avoid `?` in generated location if `queryParams` is empty
queryParams?.isNotEmpty ?? false ? queryParams : null,
)
.toString();
_buildLocation(path, queryParams: queryParams);

/// A helper function used by generated code.
///
Expand All @@ -91,72 +161,120 @@ abstract class GoRouteData extends RouteData {
GlobalKey<NavigatorState>? parentNavigatorKey,
List<RouteBase> routes = const <RouteBase>[],
}) {
T factoryImpl(GoRouterState state) {
final Object? extra = state.extra;
final _GoRouteParameters params = _createGoRouteParameters<T>(
factory: factory,
expando: _GoRouteDataBase.stateObjectExpando,
);

// If the "extra" value is of type `T` then we know it's the source
// instance of `GoRouteData`, so it doesn't need to be recreated.
if (extra is T) {
return extra;
}
return GoRoute(
path: path,
name: name,
caseSensitive: caseSensitive,
builder: params.builder,
pageBuilder: params.pageBuilder,
redirect: params.redirect,
routes: routes,
parentNavigatorKey: parentNavigatorKey,
onExit: params.onExit,
);
}

return (_stateObjectExpando[state] ??= factory(state)) as T;
}
/// The location of this route, e.g. /family/f2/person/p1
String get location => throw _GoRouteDataBase.shouldBeGeneratedError;

/// Navigate to the route.
void go(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;

Widget builder(BuildContext context, GoRouterState state) =>
factoryImpl(state).build(context, state);
/// Push the route onto the page stack.
Future<T?> push<T>(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;

Page<void> pageBuilder(BuildContext context, GoRouterState state) =>
factoryImpl(state).buildPage(context, state);
/// Replaces the top-most page of the page stack with the route.
void pushReplacement(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;

FutureOr<String?> redirect(BuildContext context, GoRouterState state) =>
factoryImpl(state).redirect(context, state);
/// Replaces the top-most page of the page stack with the route but treats
/// it as the same page.
///
/// The page key will be reused. This will preserve the state and not run any
/// page animation.
///
void replace(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;
}

FutureOr<bool> onExit(BuildContext context, GoRouterState state) =>
factoryImpl(state).onExit(context, state);
/// A class to represent a relative [GoRoute] in
/// [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).
///
/// Subclasses must override one of [build], [buildPage], or
/// [redirect].
/// {@category Type-safe routes}
abstract class RelativeGoRouteData extends _GoRouteDataBase {
/// Allows subclasses to have `const` constructors.
///
/// [RelativeGoRouteData] is abstract and cannot be instantiated directly.
const RelativeGoRouteData();

/// A helper function used by generated code.
///
/// Should not be used directly.
static String $location(String path, {Map<String, dynamic>? queryParams}) =>
_buildLocation(path, queryParams: queryParams);

/// A helper function used by generated code.
///
/// Should not be used directly.
static GoRoute $route<T extends RelativeGoRouteData>({
required String path,
bool caseSensitive = true,
required T Function(GoRouterState) factory,
GlobalKey<NavigatorState>? parentNavigatorKey,
List<RouteBase> routes = const <RouteBase>[],
}) {
final _GoRouteParameters params = _createGoRouteParameters<T>(
factory: factory,
expando: _GoRouteDataBase.stateObjectExpando,
);

return GoRoute(
path: path,
name: name,
caseSensitive: caseSensitive,
builder: builder,
pageBuilder: pageBuilder,
redirect: redirect,
builder: params.builder,
pageBuilder: params.pageBuilder,
redirect: params.redirect,
routes: routes,
parentNavigatorKey: parentNavigatorKey,
onExit: onExit,
onExit: params.onExit,
);
}

/// Used to cache [GoRouteData] that corresponds to a given [GoRouterState]
/// to minimize the number of times it has to be deserialized.
static final Expando<GoRouteData> _stateObjectExpando = Expando<GoRouteData>(
'GoRouteState to GoRouteData expando',
);
/// The sub-location of this route, e.g. person/p1
String get subLocation => throw _GoRouteDataBase.shouldBeGeneratedError;

/// The location of this route.
String get location => throw _shouldBeGeneratedError;
/// The relative location of this route, e.g. ./person/p1
String get relativeLocation => throw _GoRouteDataBase.shouldBeGeneratedError;

/// Navigate to the route.
void go(BuildContext context) => throw _shouldBeGeneratedError;
void goRelative(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;

/// Push the route onto the page stack.
Future<T?> push<T>(BuildContext context) => throw _shouldBeGeneratedError;
Future<T?> pushRelative<T>(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;

/// Replaces the top-most page of the page stack with the route.
void pushReplacement(BuildContext context) => throw _shouldBeGeneratedError;
void pushReplacementRelative(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;

/// Replaces the top-most page of the page stack with the route but treats
/// it as the same page.
///
/// The page key will be reused. This will preserve the state and not run any
/// page animation.
///
void replace(BuildContext context) => throw _shouldBeGeneratedError;

static UnimplementedError get _shouldBeGeneratedError => UnimplementedError(
'Should be generated using [Type-safe routing](https://pub.dev/documentation/go_router/latest/topics/Type-safe%20routes-topic.html).',
);
void replaceRelative(BuildContext context) =>
throw _GoRouteDataBase.shouldBeGeneratedError;
}

/// A class to represent a [ShellRoute] in
Expand Down Expand Up @@ -402,6 +520,41 @@ class TypedGoRoute<T extends GoRouteData> extends TypedRoute<T> {
final bool caseSensitive;
}

/// A superclass for each typed relative go route descendant
@Target(<TargetKind>{TargetKind.library, TargetKind.classType})
class TypedRelativeGoRoute<T extends RelativeGoRouteData>
extends TypedRoute<T> {
/// Default const constructor
const TypedRelativeGoRoute({
required this.path,
this.routes = const <TypedRoute<RouteData>>[],
this.caseSensitive = true,
});

/// The relative path that corresponds to this route.
///
/// See [GoRoute.path].
///
///
final String path;

/// Child route definitions.
///
/// See [RouteBase.routes].
final List<TypedRoute<RouteData>> routes;

/// Determines whether the route matching is case sensitive.
///
/// When `true`, the path must match the specified case. For example,
/// a route with `path: '/family/:fid'` will not match `/FaMiLy/f2`.
///
/// When `false`, the path matching is case insensitive. The route
/// with `path: '/family/:fid'` will match `/FaMiLy/f2`.
///
/// Defaults to `true`.
final bool caseSensitive;
}

/// A superclass for each typed shell route descendant
@Target(<TargetKind>{TargetKind.library, TargetKind.classType})
class TypedShellRoute<T extends ShellRouteData> extends TypedRoute<T> {
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: 16.1.0
version: 16.2.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
Loading