An open API service indexing awesome lists of open source software.

https://github.com/yelmuratoff/go_router_builder


https://github.com/yelmuratoff/go_router_builder

Last synced: about 2 months ago
JSON representation

Awesome Lists containing this project

README

        

## Usage

### Dependencies

To use `go_router_builder`, you need to have the following dependencies in
`pubspec.yaml`.

```yaml
dependencies:
# ...along with your other dependencies
go_router: ^9.0.3

dev_dependencies:
# ...along with your other dev-dependencies
build_runner: ^2.0.0
go_router_builder: ^2.3.0
```

### Source code

Instructions below explain how to create and annotate types to use this builder.
Along with importing the `go_router.dart` library, it's essential to also
include a `part` directive that references the generated Dart file. The
generated file will always have the name `[source_file].g.dart`.

```dart
import 'package:go_router/go_router.dart';

part 'readme_excerpts.g.dart';
```

### Running `build_runner`

To do a one-time build:

```console
dart run build_runner build
```

Read more about using
[`build_runner` on pub.dev](https://pub.dev/packages/build_runner).

## Overview

`go_router` fundamentally relies on the ability to match a string-based location
in a URI format into one or more page builders, each that require zero or more
arguments that are passed as path and query parameters as part of the location.
`go_router` does a good job of making the path and query parameters available
via the `pathParameters` and `queryParameters` properties of the `GoRouterState` object, but
often the page builder must first parse the parameters into types that aren't
`String`s, e.g.

```dart
GoRoute(
path: ':familyId',
builder: (BuildContext context, GoRouterState state) {
// Require the familyId to be present and be an integer.
final int familyId = int.parse(state.pathParameters['familyId']!);
return FamilyScreen(familyId);
},
);
```

In this example, the `familyId` parameter is a) required and b) must be an
`int`. However, neither of these requirements are checked until run-time, making
it easy to write code that is not type-safe, e.g.

```dart
void tap() =>
context.go('/familyId/a42'); // This is an error: `a42` is not an `int`.
```

Dart's type system allows mistakes to be caught at compile-time instead of
run-time. The goal of the routing is to provide a way to define the required and
optional parameters that a specific route consumes and to use code generation to
take out the drudgery of writing a bunch of `go`, `push` and `location`
boilerplate code implementations ourselves.

## Defining a route

Define each route as a class extending `GoRouteData` and overriding the `build`
method.

```dart
class HomeRoute extends GoRouteData {
const HomeRoute();

@override
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
```

## Route tree

The tree of routes is defined as an attribute on each of the top-level routes:

```dart
@TypedGoRoute(
path: '/',
routes: >[
TypedGoRoute(
path: 'family/:fid',
),
],
)
class HomeRoute extends GoRouteData {
const HomeRoute();

@override
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}

class RedirectRoute extends GoRouteData {
// There is no need to implement [build] when this [redirect] is unconditional.
@override
String? redirect(BuildContext context, GoRouterState state) {
return const HomeRoute().location;
}
}

@TypedGoRoute(path: '/login')
class LoginRoute extends GoRouteData {
LoginRoute({this.from});
final String? from;

@override
Widget build(BuildContext context, GoRouterState state) {
return LoginScreen(from: from);
}
}
```

## `GoRouter` initialization

The code generator aggregates all top-level routes into a single list called
`$appRoutes` for use in initializing the `GoRouter` instance:

```dart
final GoRouter router = GoRouter(routes: $appRoutes);
```

## Error builder

One can use typed routes to provide an error builder as well:

```dart
class ErrorRoute extends GoRouteData {
ErrorRoute({required this.error});
final Exception error;

@override
Widget build(BuildContext context, GoRouterState state) {
return ErrorScreen(error: error);
}
}
```

With this in place, you can provide the `errorBuilder` parameter like so:

```dart
final GoRouter routerWithErrorBuilder = GoRouter(
routes: $appRoutes,
errorBuilder: (BuildContext context, GoRouterState state) {
return ErrorRoute(error: state.error!).build(context, state);
},
);
```

## Navigation

Navigate using the `go` or `push` methods provided by the code generator:

```dart
void onTap() => const FamilyRoute(fid: 'f2').go(context);
```

If you get this wrong, the compiler will complain:

```dart
// This is an error: missing required parameter 'fid'.
void errorTap() => const FamilyRoute().go(context);
```

This is the point of typed routing: the error is found statically.

## Return value

Starting from `go_router` 6.5.0, pushing a route and subsequently popping it, can produce
a return value. The generated routes also follow this functionality.

```dart
final bool? result =
await const FamilyRoute(fid: 'John').push(context);
```

## Query parameters

Parameters (named or positional) not listed in the path of `TypedGoRoute` indicate query parameters:

```dart
@TypedGoRoute(path: '/login')
class LoginRoute extends GoRouteData {
LoginRoute({this.from});
final String? from;

@override
Widget build(BuildContext context, GoRouterState state) {
return LoginScreen(from: from);
}
}
```

### Default values

For query parameters with a **non-nullable** type, you can define a default value:

```dart
@TypedGoRoute(path: '/my-route')
class MyRoute extends GoRouteData {
MyRoute({this.queryParameter = 'defaultValue'});
final String queryParameter;

@override
Widget build(BuildContext context, GoRouterState state) {
return MyScreen(queryParameter: queryParameter);
}
}
```

A query parameter that equals to its default value is not included in the location.

## Extra parameter

A route can consume an extra parameter by taking it as a typed constructor
parameter with the special name `$extra`:

```dart
class PersonRouteWithExtra extends GoRouteData {
PersonRouteWithExtra(this.$extra);
final Person? $extra;

@override
Widget build(BuildContext context, GoRouterState state) {
return PersonScreen($extra);
}
}
```

Pass the extra param as a typed object:

```dart
void tapWithExtra() {
PersonRouteWithExtra(Person(id: 1, name: 'Marvin', age: 42)).go(context);
}
```

The `$extra` parameter is still passed outside the location, still defeats
dynamic and deep linking (including the browser back button) and is still not
recommended when targeting Flutter web.

## Mixed parameters

You can, of course, combine the use of path, query and $extra parameters:

```dart
@TypedGoRoute(path: '/:ketchup')
class HotdogRouteWithEverything extends GoRouteData {
HotdogRouteWithEverything(this.ketchup, this.mustard, this.$extra);
final bool ketchup; // A required path parameter.
final String? mustard; // An optional query parameter.
final Sauce $extra; // A special $extra parameter.

@override
Widget build(BuildContext context, GoRouterState state) {
return HotdogScreen(ketchup, mustard, $extra);
}
}
```

This seems kinda silly, but it works.

## Redirection

Redirect using the `location` property on a route provided by the code
generator:

```dart
redirect: (BuildContext context, GoRouterState state) {
final bool loggedIn = loginInfo.loggedIn;
final bool loggingIn = state.matchedLocation == LoginRoute().location;
if (!loggedIn && !loggingIn) {
return LoginRoute(from: state.matchedLocation).location;
}
if (loggedIn && loggingIn) {
return const HomeRoute().location;
}
return null;
},
```

## Route-level redirection

Handle route-level redirects by implementing the `redirect` method on the route:

```dart
class RedirectRoute extends GoRouteData {
// There is no need to implement [build] when this [redirect] is unconditional.
@override
String? redirect(BuildContext context, GoRouterState state) {
return const HomeRoute().location;
}
}
```

## Type conversions

The code generator can convert simple types like `int` and `enum` to/from the
`String` type of the underlying pathParameters:

```dart
enum BookKind { all, popular, recent }

class BooksRoute extends GoRouteData {
BooksRoute({this.kind = BookKind.popular});
final BookKind kind;

@override
Widget build(BuildContext context, GoRouterState state) {
return BooksScreen(kind: kind);
}
}
```

## Transitions

By default, the `GoRouter` will use the app it finds in the widget tree, e.g.
`MaterialApp`, `CupertinoApp`, `WidgetApp`, etc. and use the corresponding page
type to create the page that wraps the `Widget` returned by the route's `build`
method, e.g. `MaterialPage`, `CupertinoPage`, `NoTransitionPage`, etc.
Furthermore, it will use the `state.pageKey` property to set the `key` property
of the page and the `restorationId` of the page.

### Transition override

If you'd like to change how the page is created, e.g. to use a different page
type, pass non-default parameters when creating the page (like a custom key) or
access the `GoRouteState` object, you can override the `buildPage`
method of the base class instead of the `build` method:

```dart
class MyMaterialRouteWithKey extends GoRouteData {
static const LocalKey _key = ValueKey('my-route-with-key');
@override
MaterialPage buildPage(BuildContext context, GoRouterState state) {
return const MaterialPage(
key: _key,
child: MyPage(),
);
}
}
```

### Custom transitions

Overriding the `buildPage` method is also useful for custom transitions:

```dart
class FancyRoute extends GoRouteData {
@override
CustomTransitionPage buildPage(
BuildContext context,
GoRouterState state,
) {
return CustomTransitionPage(
key: state.pageKey,
child: const MyPage(),
transitionsBuilder: (BuildContext context, Animation animation,
Animation secondaryAnimation, Widget child) {
return RotationTransition(turns: animation, child: child);
});
}
}
```

## TypedShellRoute and navigator keys

There may be situations where a child route of a shell needs to be displayed on a
different navigator. This kind of scenarios can be achieved by declaring a
**static** navigator key named:

- `$navigatorKey` for ShellRoutes
- `$parentNavigatorKey` for GoRoutes

Example:

```dart
final GlobalKey shellNavigatorKey = GlobalKey();
final GlobalKey rootNavigatorKey = GlobalKey();

class MyShellRouteData extends ShellRouteData {
const MyShellRouteData();

static final GlobalKey $navigatorKey = shellNavigatorKey;

@override
Widget builder(BuildContext context, GoRouterState state, Widget navigator) {
return MyShellRoutePage(navigator);
}
}

// For GoRoutes:
class MyGoRouteData extends GoRouteData {
const MyGoRouteData();

static final GlobalKey $parentNavigatorKey = rootNavigatorKey;

@override
Widget build(BuildContext context, GoRouterState state) => const MyPage();
}
```

An example is available [here](https://github.com/flutter/packages/blob/main/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart).

## Run tests

To run unit tests, run command `dart tool/run_tests.dart` from `packages/go_router_builder/`.

To run tests in examples, run `flutter test` from `packages/go_router_builder/example`.