Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
1884e2c
add `transitionDelegate` to `GoRouter` and penetrate it through layer…
thangmoxielabs Dec 5, 2023
7896370
Merge branch 'main' of github.com:ThangVuNguyenViet/packages
thangmoxielabs Jan 4, 2024
46bac7a
Merge branch 'flutter:main' into main
ThangVuNguyenViet Jan 20, 2024
449d5a8
Merge branch 'flutter:main' into main
ThangVuNguyenViet May 28, 2024
50416dd
add [GoRouter.goRelative] and tests. Add TypedRelativeGoRoute and its…
ThangVuNguyenViet May 28, 2024
e0d7fcc
Merge branch 'flutter:main' into main
ThangVuNguyenViet May 28, 2024
f397458
Revert "add `transitionDelegate` to `GoRouter` and penetrate it throu…
ThangVuNguyenViet May 28, 2024
f411ceb
TypedRelativeGoRoute shouldn't inherit TypedGoRoute, and it should on…
ThangVuNguyenViet May 28, 2024
c6de772
update versions go_router & go_router_builder
ThangVuNguyenViet May 28, 2024
5abfe4a
Strip away changes in go_router_builder. Update go_router changelog
ThangVuNguyenViet May 29, 2024
833f838
fix changelog title
ThangVuNguyenViet May 29, 2024
ad6b246
[webview]: Bump androidx.webkit:webkit from 1.10.0 to 1.11.0 in /pack…
dependabot[bot] May 28, 2024
327bae2
[go_router] docs: updated link in navigation.md to correct file path …
altynbek132 May 28, 2024
af3a3d1
[many] Remove references to v1 embedding (#6494)
gmackall May 28, 2024
023e3ba
[google_maps_flutter] Implement polyline patterns in google maps ios …
Hari-07 May 28, 2024
2a051d7
[pigeon]: Bump org.jetbrains.kotlin:kotlin-gradle-plugin from 1.9.22 …
dependabot[bot] May 28, 2024
52101d3
update versions go_router & go_router_builder
ThangVuNguyenViet May 28, 2024
101d03b
fix changelog title
ThangVuNguyenViet May 29, 2024
f3fb748
Merge branch 'main' of github.com:flutter/packages into go_router/go-…
ThangVuNguyenViet May 29, 2024
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
5 changes: 5 additions & 0 deletions packages/go_router/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
## 14.1.5

- Adds `GoRouter.goRelative`
- Adds `TypedRelativeGoRoute`

## 14.1.4

- Fixes a URL in `navigation.md`.
Expand Down
27 changes: 27 additions & 0 deletions packages/go_router/lib/src/information_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,33 @@ class GoRouteInformationProvider extends RouteInformationProvider
);
}

/// Relatively go to [relativeLocation].
void goRelative(String relativeLocation, {Object? extra}) {
assert(
!relativeLocation.startsWith('/'),
"Relative locations must not start with a '/'.",
);

final Uri currentUri = value.uri;
Uri newUri = Uri.parse(
currentUri.path.endsWith('/')
? '${currentUri.path}$relativeLocation'
: '${currentUri.path}/$relativeLocation',
);
newUri = newUri.replace(queryParameters: <String, dynamic>{
...currentUri.queryParameters,
...newUri.queryParameters,
});

_setValue(
newUri.toString(),
RouteInformationState<void>(
extra: extra,
type: NavigatingType.go,
),
);
}

/// Restores the current route matches with the `matchList`.
void restore(String location, {required RouteMatchList matchList}) {
_setValue(
Expand Down
7 changes: 7 additions & 0 deletions packages/go_router/lib/src/misc/extensions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ extension GoRouterHelper on BuildContext {
void go(String location, {Object? extra}) =>
GoRouter.of(this).go(location, extra: extra);

/// Navigate relative to a location.
void goRelative(String location, {Object? extra}) =>
GoRouter.of(this).goRelative(
location,
extra: extra,
);

/// Navigate to a named route.
void goNamed(
String name, {
Expand Down
22 changes: 22 additions & 0 deletions packages/go_router/lib/src/route_data.dart
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,28 @@ class TypedGoRoute<T extends GoRouteData> extends TypedRoute<T> {
final List<TypedRoute<RouteData>> routes;
}

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

/// 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;
}

/// A superclass for each typed shell route descendant
@Target(<TargetKind>{TargetKind.library, TargetKind.classType})
class TypedShellRoute<T extends ShellRouteData> extends TypedRoute<T> {
Expand Down
9 changes: 9 additions & 0 deletions packages/go_router/lib/src/router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,15 @@ class GoRouter implements RouterConfig<RouteMatchList> {
routeInformationProvider.go(location, extra: extra);
}

/// Navigate to a URI location by appending [relativeLocation] to the current [GoRouterState.matchedLocation] w/ optional query parameters, e.g.
void goRelative(
String relativeLocation, {
Object? extra,
}) {
log('going relative to $relativeLocation');
routeInformationProvider.goRelative(relativeLocation, extra: extra);
}

/// Restore the RouteMatchList
void restore(RouteMatchList matchList) {
log('restoring ${matchList.uri}');
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: 14.1.4
version: 14.1.5
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
277 changes: 277 additions & 0 deletions packages/go_router/test/go_router_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1791,6 +1791,283 @@ void main() {
});
});

group('go relative', () {
testWidgets('from default route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];

final GoRouter router = await createRouter(routes, tester);
router.goRelative('login');
await tester.pumpAndSettle();
expect(find.byType(LoginScreen), findsOneWidget);
});

testWidgets('from non-default route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'login',
builder: (BuildContext context, GoRouterState state) =>
const LoginScreen(),
),
],
),
];

final GoRouter router = await createRouter(routes, tester);
router.go('/home');
router.goRelative('login');
await tester.pumpAndSettle();
expect(find.byType(LoginScreen), findsOneWidget);
});

testWidgets('match w/ path params', (WidgetTester tester) async {
const String fid = 'f2';
const String pid = 'p1';

final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
name: 'person',
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) {
expect(state.pathParameters,
<String, String>{'fid': fid, 'pid': pid});
return const PersonScreen('dummy', 'dummy');
},
),
],
),
],
),
];

final GoRouter router =
await createRouter(routes, tester, initialLocation: '/home');
router.go('/');

router.goRelative('family/$fid');
await tester.pumpAndSettle();
expect(find.byType(FamilyScreen), findsOneWidget);

router.goRelative('person/$pid');
await tester.pumpAndSettle();
expect(find.byType(PersonScreen), findsOneWidget);
});

testWidgets('match w/ query params', (WidgetTester tester) async {
const String fid = 'f2';
const String pid = 'p1';

final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'family',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
path: 'person',
builder: (BuildContext context, GoRouterState state) {
expect(state.uri.queryParameters,
<String, String>{'fid': fid, 'pid': pid});
return const PersonScreen('dummy', 'dummy');
},
),
],
),
],
),
];

final GoRouter router =
await createRouter(routes, tester, initialLocation: '/home');

router.goRelative('family?fid=$fid');
await tester.pumpAndSettle();
expect(find.byType(FamilyScreen), findsOneWidget);

router.goRelative('person?pid=$pid');
await tester.pumpAndSettle();
expect(find.byType(PersonScreen), findsOneWidget);
});

testWidgets('too few params', (WidgetTester tester) async {
const String pid = 'p1';

final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'family/:fid',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
path: 'person/:pid',
builder: (BuildContext context, GoRouterState state) =>
const PersonScreen('dummy', 'dummy'),
),
],
),
],
),
];
// await expectLater(() async {
final GoRouter router = await createRouter(
routes,
tester,
initialLocation: '/home',
errorBuilder: (BuildContext context, GoRouterState state) =>
TestErrorScreen(state.error!),
);
router.goRelative('family/person/$pid');
await tester.pumpAndSettle();
expect(find.byType(TestErrorScreen), findsOneWidget);

final List<RouteMatchBase> matches =
router.routerDelegate.currentConfiguration.matches;
expect(matches, hasLength(0));
});

testWidgets('match no route', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) =>
const HomeScreen(),
routes: <GoRoute>[
GoRoute(
path: 'family',
builder: (BuildContext context, GoRouterState state) =>
const FamilyScreen('dummy'),
routes: <GoRoute>[
GoRoute(
path: 'person',
builder: (BuildContext context, GoRouterState state) =>
const PersonScreen('dummy', 'dummy'),
),
],
),
],
),
];

final GoRouter router = await createRouter(
routes,
tester,
initialLocation: '/home',
errorBuilder: (BuildContext context, GoRouterState state) =>
TestErrorScreen(state.error!),
);
router.go('person');

await tester.pumpAndSettle();
expect(find.byType(TestErrorScreen), findsOneWidget);

final List<RouteMatchBase> matches =
router.routerDelegate.currentConfiguration.matches;
expect(matches, hasLength(0));
});

testWidgets('preserve path param spaces and slashes',
(WidgetTester tester) async {
const String param1 = 'param w/ spaces and slashes';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/home',
builder: dummy,
routes: <RouteBase>[
GoRoute(
path: 'page1/:param1',
builder: (BuildContext c, GoRouterState s) {
expect(s.pathParameters['param1'], param1);
return const DummyScreen();
},
),
],
)
];

final GoRouter router =
await createRouter(routes, tester, initialLocation: '/home');
final String loc = 'page1/${Uri.encodeComponent(param1)}';
router.goRelative(loc);

await tester.pumpAndSettle();
expect(find.byType(DummyScreen), findsOneWidget);

final RouteMatchList matches = router.routerDelegate.currentConfiguration;
expect(matches.pathParameters['param1'], param1);
});

testWidgets('preserve query param spaces and slashes',
(WidgetTester tester) async {
const String param1 = 'param w/ spaces and slashes';
final List<GoRoute> routes = <GoRoute>[
GoRoute(
path: '/home',
builder: dummy,
routes: <RouteBase>[
GoRoute(
path: 'page1',
builder: (BuildContext c, GoRouterState s) {
expect(s.uri.queryParameters['param1'], param1);
return const DummyScreen();
},
),
],
)
];

final GoRouter router =
await createRouter(routes, tester, initialLocation: '/home');

router.goRelative(Uri(
path: 'page1',
queryParameters: <String, dynamic>{'param1': param1},
).toString());

await tester.pumpAndSettle();
expect(find.byType(DummyScreen), findsOneWidget);

final RouteMatchList matches = router.routerDelegate.currentConfiguration;
expect(matches.uri.queryParameters['param1'], param1);
});
});

group('redirects', () {
testWidgets('top-level redirect', (WidgetTester tester) async {
final List<GoRoute> routes = <GoRoute>[
Expand Down
Loading