go_router_builder

Creator: coderz1093

Last updated:

Add to Cart

Description:

go router builder

Usage #
Dependencies #
To use go_router_builder, you need to have the following dependencies in
pubspec.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
copied to clipboard
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.

import 'package:go_router/go_router.dart';

part 'readme_excerpts.g.dart';
copied to clipboard
Running build_runner #
To do a one-time build:
dart run build_runner build
copied to clipboard
Read more about using
build_runner on pub.dev.
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
Strings, e.g.

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);
},
);
copied to clipboard
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.

void tap() =>
context.go('/familyId/a42'); // This is an error: `a42` is not an `int`.
copied to clipboard
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.

class HomeRoute extends GoRouteData {
const HomeRoute();

@override
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}
copied to clipboard
Route tree #
The tree of routes is defined as an attribute on each of the top-level routes:

@TypedGoRoute<HomeRoute>(
path: '/',
routes: <TypedGoRoute<GoRouteData>>[
TypedGoRoute<FamilyRoute>(
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<LoginRoute>(path: '/login')
class LoginRoute extends GoRouteData {
LoginRoute({this.from});
final String? from;

@override
Widget build(BuildContext context, GoRouterState state) {
return LoginScreen(from: from);
}
}
copied to clipboard
GoRouter initialization #
The code generator aggregates all top-level routes into a single list called
$appRoutes for use in initializing the GoRouter instance:

final GoRouter router = GoRouter(routes: $appRoutes);
copied to clipboard
Error builder #
One can use typed routes to provide an error builder as well:

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

@override
Widget build(BuildContext context, GoRouterState state) {
return ErrorScreen(error: error);
}
}
copied to clipboard
With this in place, you can provide the errorBuilder parameter like so:

final GoRouter routerWithErrorBuilder = GoRouter(
routes: $appRoutes,
errorBuilder: (BuildContext context, GoRouterState state) {
return ErrorRoute(error: state.error!).build(context, state);
},
);
copied to clipboard
Navigation #
Navigate using the go or push methods provided by the code generator:

void onTap() => const FamilyRoute(fid: 'f2').go(context);
copied to clipboard
If you get this wrong, the compiler will complain:

// This is an error: missing required parameter 'fid'.
void errorTap() => const FamilyRoute().go(context);
copied to clipboard
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.

final bool? result =
await const FamilyRoute(fid: 'John').push<bool>(context);
copied to clipboard
Query parameters #
Parameters (named or positional) not listed in the path of TypedGoRoute indicate query parameters:

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

@override
Widget build(BuildContext context, GoRouterState state) {
return LoginScreen(from: from);
}
}
copied to clipboard
Default values #
For query parameters with a non-nullable type, you can define a default value:

@TypedGoRoute<MyRoute>(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);
}
}
copied to clipboard
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:

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

@override
Widget build(BuildContext context, GoRouterState state) {
return PersonScreen($extra);
}
}
copied to clipboard
Pass the extra param as a typed object:

void tapWithExtra() {
PersonRouteWithExtra(Person(id: 1, name: 'Marvin', age: 42)).go(context);
}
copied to clipboard
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:

@TypedGoRoute<HotdogRouteWithEverything>(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);
}
}
copied to clipboard
This seems kinda silly, but it works.
Redirection #
Redirect using the location property on a route provided by the code
generator:

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;
},
copied to clipboard
Route-level redirection #
Handle route-level redirects by implementing the redirect method on the route:

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;
}
}
copied to clipboard
Type conversions #
The code generator can convert simple types like int and enum to/from the
String type of the underlying pathParameters:

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);
}
}
copied to clipboard
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:

class MyMaterialRouteWithKey extends GoRouteData {
static const LocalKey _key = ValueKey<String>('my-route-with-key');
@override
MaterialPage<void> buildPage(BuildContext context, GoRouterState state) {
return const MaterialPage<void>(
key: _key,
child: MyPage(),
);
}
}
copied to clipboard
Custom transitions #
Overriding the buildPage method is also useful for custom transitions:

class FancyRoute extends GoRouteData {
@override
CustomTransitionPage<void> buildPage(
BuildContext context,
GoRouterState state,
) {
return CustomTransitionPage<void>(
key: state.pageKey,
child: const MyPage(),
transitionsBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation, Widget child) {
return RotationTransition(turns: animation, child: child);
});
}
}
copied to clipboard
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:

final GlobalKey<NavigatorState> shellNavigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();

class MyShellRouteData extends ShellRouteData {
const MyShellRouteData();

static final GlobalKey<NavigatorState> $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<NavigatorState> $parentNavigatorKey = rootNavigatorKey;

@override
Widget build(BuildContext context, GoRouterState state) => const MyPage();
}
copied to clipboard
An example is available here.
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.

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Customer Reviews

There are no reviews.