diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index 3332a70e1b8..72be3473bf7 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.7.1 + +- Fixes readme typos and uses code excerpts. + ## 2.7.0 - Adds an example and a test with `onExit`. diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 73faf973937..5d90b22f285 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -23,10 +23,11 @@ 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 'this_file.g.dart'; +part 'readme_excerpts.g.dart'; ``` ### Running `build_runner` @@ -34,7 +35,7 @@ part 'this_file.g.dart'; To do a one-time build: ```console -flutter pub run build_runner build +dart run build_runner build ``` Read more about using @@ -50,23 +51,26 @@ via the `pathParameters` and `queryParameters` properties of the `GoRouterState` often the page builder must first parse the parameters into types that aren't `String`s, e.g. + ```dart GoRoute( - path: ':authorId', - builder: (context, state) { - // require the authorId to be present and be an integer - final authorId = int.parse(state.pathParameters['authorId']!); - return AuthorDetailsScreen(authorId: authorId); + 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 `authorId` parameter is a) required and b) must be an +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('/author/a42'); // error: `a42` is not an `int` +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 @@ -80,6 +84,7 @@ boilerplate code implementations ourselves. Define each route as a class extending `GoRouteData` and overriding the `build` method. + ```dart class HomeRoute extends GoRouteData { const HomeRoute(); @@ -93,24 +98,41 @@ class HomeRoute extends GoRouteData { The tree of routes is defined as an attribute on each of the top-level routes: + ```dart @TypedGoRoute( path: '/', routes: >[ TypedGoRoute( - path: 'family/:familyId', - ) + path: 'family/:fid', + ), ], ) class HomeRoute extends GoRouteData { const HomeRoute(); @override - Widget build(BuildContext context, GoRouterState state) => HomeScreen(families: familyData); + 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 {...} +class LoginRoute extends GoRouteData { + LoginRoute({this.from}); + final String? from; + + @override + Widget build(BuildContext context, GoRouterState state) { + return LoginScreen(from: from); + } +} ``` ## `GoRouter` initialization @@ -118,30 +140,37 @@ class LoginRoute extends GoRouteData {...} The code generator aggregates all top-level routes into a single list called `$appRoutes` for use in initializing the `GoRouter` instance: + ```dart -final _router = GoRouter(routes: $appRoutes); +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) => ErrorScreen(error: error); + Widget build(BuildContext context, GoRouterState state) { + return ErrorScreen(error: error); + } } ``` With this in place, you can provide the `errorBuilder` parameter like so: + ```dart -final _router = GoRouter( +final GoRouter routerWithErrorBuilder = GoRouter( routes: $appRoutes, - errorBuilder: (c, s) => ErrorRoute(s.error!).build(c), + errorBuilder: (BuildContext context, GoRouterState state) { + return ErrorRoute(error: state.error!).build(context, state); + }, ); ``` @@ -149,15 +178,17 @@ final _router = GoRouter( Navigate using the `go` or `push` methods provided by the code generator: + ```dart -void _tap() => PersonRoute(fid: 'f2', pid: 'p1').go(context); +void onTap() => const FamilyRoute(fid: 'f2').go(context); ``` If you get this wrong, the compiler will complain: + ```dart -// error: missing required parameter 'fid' -void _tap() => PersonRoute(pid: 'p1').go(context); +// 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. @@ -167,24 +198,27 @@ This is the point of typed routing: the error is found statically. 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 -void _tap() async { - final result = await PersonRoute(pid: 'p1').go(context); -} +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') +@TypedGoRoute(path: '/login') class LoginRoute extends GoRouteData { LoginRoute({this.from}); final String? from; @override - Widget build(BuildContext context, GoRouterState state) => LoginScreen(from: from); + Widget build(BuildContext context, GoRouterState state) { + return LoginScreen(from: from); + } } ``` @@ -192,14 +226,17 @@ class LoginRoute extends GoRouteData { For query parameters with a **non-nullable** type, you can define a default value: + ```dart -@TypedGoRoute(path: '/my-route') +@TypedGoRoute(path: '/my-route') class MyRoute extends GoRouteData { MyRoute({this.queryParameter = 'defaultValue'}); final String queryParameter; @override - Widget build(BuildContext context, GoRouterState state) => MyScreen(queryParameter: queryParameter); + Widget build(BuildContext context, GoRouterState state) { + return MyScreen(queryParameter: queryParameter); + } } ``` @@ -211,20 +248,26 @@ A query parameter that equals to its default value is not included in the locati 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 int? $extra; + PersonRouteWithExtra(this.$extra); + final Person? $extra; @override - Widget build(BuildContext context, GoRouterState state) => PersonScreen(personId: $extra); + Widget build(BuildContext context, GoRouterState state) { + return PersonScreen($extra); + } } ``` Pass the extra param as a typed object: + ```dart -void _tap() => PersonRouteWithExtra(Person(name: 'Marvin', age: 42)).go(context); +void tapWithExtra() { + PersonRouteWithExtra(Person(id: 1, name: 'Marvin', age: 42)).go(context); +} ``` The `$extra` parameter is still passed outside the location, still defeats @@ -235,16 +278,19 @@ recommended when targeting Flutter web. 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; // required path parameter - final String? mustard; // optional query parameter - final Sauce $extra; // special $extra parameter + 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) => HotdogScreen(ketchup, mustard, $extra); + Widget build(BuildContext context, GoRouterState state) { + return HotdogScreen(ketchup, mustard, $extra); + } } ``` @@ -255,25 +301,33 @@ This seems kinda silly, but it works. Redirect using the `location` property on a route provided by the code generator: + ```dart -redirect: (state) { - final loggedIn = loginInfo.loggedIn; - final loggingIn = state.matchedLocation == LoginRoute().location; - if( !loggedIn && !loggingIn ) return LoginRoute(from: state.matchedLocation).location; - if( loggedIn && loggingIn ) return HomeRoute().location; +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 HomeRoute extends GoRouteData { - // no need to implement [build] when this [redirect] is unconditional +class RedirectRoute extends GoRouteData { + // There is no need to implement [build] when this [redirect] is unconditional. @override - String? redirect(BuildContext context, GoRouterState state) => BooksRoute().location; + String? redirect(BuildContext context, GoRouterState state) { + return const HomeRoute().location; + } } ``` @@ -282,6 +336,7 @@ class HomeRoute extends GoRouteData { 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 } @@ -290,7 +345,9 @@ class BooksRoute extends GoRouteData { final BookKind kind; @override - Widget build(BuildContext context, GoRouterState state) => BooksScreen(kind: kind); + Widget build(BuildContext context, GoRouterState state) { + return BooksScreen(kind: kind); + } } ``` @@ -310,15 +367,17 @@ 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 final _key = LocalKey('my-route-with-key'); + static const LocalKey _key = ValueKey('my-route-with-key'); @override - MaterialPage buildPage(BuildContext context, GoRouterState state) => - MaterialPage( + MaterialPage buildPage(BuildContext context, GoRouterState state) { + return const MaterialPage( key: _key, child: MyPage(), ); + } } ``` @@ -326,16 +385,22 @@ class MyMaterialRouteWithKey extends GoRouteData { Overriding the `buildPage` method is also useful for custom transitions: + ```dart class FancyRoute extends GoRouteData { @override - MaterialPage buildPage(BuildContext context, GoRouterState state) => - CustomTransitionPage( - key: state.pageKey, - child: FancyPage(), - transitionsBuilder: (context, animation, animation2, child) => - RotationTransition(turns: animation, child: child), - ), + 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); + }); + } } ``` @@ -350,9 +415,10 @@ different navigator. This kind of scenarios can be achieved by declaring a Example: + ```dart -// For ShellRoutes: final GlobalKey shellNavigatorKey = GlobalKey(); +final GlobalKey rootNavigatorKey = GlobalKey(); class MyShellRouteData extends ShellRouteData { const MyShellRouteData(); @@ -361,7 +427,7 @@ class MyShellRouteData extends ShellRouteData { @override Widget builder(BuildContext context, GoRouterState state, Widget navigator) { - // ... + return MyShellRoutePage(navigator); } } @@ -372,9 +438,7 @@ class MyGoRouteData extends GoRouteData { static final GlobalKey $parentNavigatorKey = rootNavigatorKey; @override - Widget build(BuildContext context, GoRouterState state) { - // ... - } + Widget build(BuildContext context, GoRouterState state) => const MyPage(); } ``` @@ -384,4 +448,4 @@ An example is available [here](https://github.com/flutter/packages/blob/main/pac 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`. \ No newline at end of file +To run tests in examples, run `flutter test` from `packages/go_router_builder/example`. diff --git a/packages/go_router_builder/example/lib/readme_excerpts.dart b/packages/go_router_builder/example/lib/readme_excerpts.dart new file mode 100644 index 00000000000..28eabdf1fa5 --- /dev/null +++ b/packages/go_router_builder/example/lib/readme_excerpts.dart @@ -0,0 +1,417 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// ignore_for_file: public_member_api_docs, unreachable_from_main, avoid_print, unused_element, unused_local_variable, directives_ordering + +import 'package:flutter/material.dart'; +import 'shared/data.dart'; +// #docregion import +import 'package:go_router/go_router.dart'; + +part 'readme_excerpts.g.dart'; +// #enddocregion import + +void otherDoc(BuildContext context) { + // #docregion GoRoute + 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); + }, + ); + // #enddocregion GoRoute + + // #docregion GoWrong + void tap() => + context.go('/familyId/a42'); // This is an error: `a42` is not an `int`. + // #enddocregion GoWrong + + // #docregion GoRouter + final GoRouter router = GoRouter(routes: $appRoutes); + // #enddocregion GoRouter + + // #docregion routerWithErrorBuilder + final GoRouter routerWithErrorBuilder = GoRouter( + routes: $appRoutes, + errorBuilder: (BuildContext context, GoRouterState state) { + return ErrorRoute(error: state.error!).build(context, state); + }, + ); + // #enddocregion routerWithErrorBuilder + + // #docregion go + void onTap() => const FamilyRoute(fid: 'f2').go(context); + // #enddocregion go + + // #docregion goError + // This is an error: missing required parameter 'fid'. + void errorTap() => const FamilyRoute().go(context); + // #enddocregion goError + + // #docregion tapWithExtra + void tapWithExtra() { + PersonRouteWithExtra(Person(id: 1, name: 'Marvin', age: 42)).go(context); + } + // #enddocregion tapWithExtra + + final LoginInfo loginInfo = LoginInfo(); + + final GoRouter routerWithRedirect = GoRouter( + routes: $appRoutes, + // #docregion redirect + 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; + }, + // #enddocregion redirect + ); +} + +// #docregion TypedGoRouteHomeRoute +@TypedGoRoute( + path: '/', + routes: >[ + TypedGoRoute( + path: 'family/:fid', + ), + ], +) +// #docregion HomeRoute +class HomeRoute extends GoRouteData { + const HomeRoute(); + + @override + Widget build(BuildContext context, GoRouterState state) => const HomeScreen(); +} +// #enddocregion HomeRoute + +// #docregion RedirectRoute +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; + } +} +// #enddocregion RedirectRoute + +// #docregion login +@TypedGoRoute(path: '/login') +class LoginRoute extends GoRouteData { + LoginRoute({this.from}); + final String? from; + + @override + Widget build(BuildContext context, GoRouterState state) { + return LoginScreen(from: from); + } +} +// #enddocregion login +// #enddocregion TypedGoRouteHomeRoute + +class HomeScreen extends StatelessWidget { + const HomeScreen({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('home'), + ), + body: TextButton( + onPressed: () async { + // #docregion awaitPush + final bool? result = + await const FamilyRoute(fid: 'John').push(context); + // #enddocregion awaitPush + print('result is $result'); + }, + child: const Text('push'), + ), + ); + } +} + +class FamilyRoute extends GoRouteData { + const FamilyRoute({this.fid}); + + final String? fid; + + @override + Widget build(BuildContext context, GoRouterState state) { + return FamilyScreen(int.parse(fid!)); + } +} + +class FamilyScreen extends StatelessWidget { + const FamilyScreen(this.fid, {super.key}); + + final int fid; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('family'), + ), + body: TextButton( + onPressed: () { + context.pop(true); + }, + child: const Text('pop with true'), + ), + ); + } +} + +// #docregion ErrorRoute +class ErrorRoute extends GoRouteData { + ErrorRoute({required this.error}); + final Exception error; + + @override + Widget build(BuildContext context, GoRouterState state) { + return ErrorScreen(error: error); + } +} +// #enddocregion ErrorRoute + +class ErrorScreen extends StatelessWidget { + const ErrorScreen({required this.error, super.key}); + + final Exception error; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Error'), + ), + body: Text(error.toString()), + ); + } +} + +class LoginScreen extends StatelessWidget { + const LoginScreen({required this.from, super.key}); + final String? from; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Login'), + ), + ); + } +} + +// #docregion MyRoute +@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); + } +} +// #enddocregion MyRoute + +class MyScreen extends StatelessWidget { + const MyScreen({required this.queryParameter, super.key}); + final String queryParameter; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('MyScreen'), + ), + ); + } +} + +@TypedGoRoute(path: '/person') +// #docregion PersonRouteWithExtra +class PersonRouteWithExtra extends GoRouteData { + PersonRouteWithExtra(this.$extra); + final Person? $extra; + + @override + Widget build(BuildContext context, GoRouterState state) { + return PersonScreen($extra); + } +} +// #enddocregion PersonRouteWithExtra + +class PersonScreen extends StatelessWidget { + const PersonScreen(this.person, {super.key}); + final Person? person; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('PersonScreen'), + ), + ); + } +} + +// #docregion HotdogRouteWithEverything +@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); + } +} +// #enddocregion HotdogRouteWithEverything + +class Sauce {} + +class HotdogScreen extends StatelessWidget { + const HotdogScreen(this.ketchup, this.mustard, this.extra, {super.key}); + final bool ketchup; + final String? mustard; + final Sauce extra; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Hotdog'), + ), + ); + } +} + +// #docregion BookKind +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); + } +} +// #enddocregion BookKind + +class BooksScreen extends StatelessWidget { + const BooksScreen({required this.kind, super.key}); + final BookKind kind; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('BooksScreen'), + ), + ); + } +} + +// #docregion MyMaterialRouteWithKey +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(), + ); + } +} +// #enddocregion MyMaterialRouteWithKey + +class MyPage extends StatelessWidget { + const MyPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('MyPage'), + ), + ); + } +} + +class MyShellRoutePage extends StatelessWidget { + const MyShellRoutePage(this.child, {super.key}); + final Widget child; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('MyShellRoutePage'), + ), + body: child, + ); + } +} + +// #docregion FancyRoute +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); + }); + } +} +// #enddocregion FancyRoute + +// #docregion MyShellRouteData +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(); +} +// #enddocregion MyShellRouteData diff --git a/packages/go_router_builder/example/lib/readme_excerpts.g.dart b/packages/go_router_builder/example/lib/readme_excerpts.g.dart new file mode 100644 index 00000000000..f6ec99f9d2c --- /dev/null +++ b/packages/go_router_builder/example/lib/readme_excerpts.g.dart @@ -0,0 +1,190 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'readme_excerpts.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $homeRoute, + $loginRoute, + $myRoute, + $personRouteWithExtra, + $hotdogRouteWithEverything, + ]; + +RouteBase get $homeRoute => GoRouteData.$route( + path: '/', + factory: $HomeRouteExtension._fromState, + routes: [ + GoRouteData.$route( + path: 'family/:fid', + factory: $FamilyRouteExtension._fromState, + ), + ], + ); + +extension $HomeRouteExtension on HomeRoute { + static HomeRoute _fromState(GoRouterState state) => const HomeRoute(); + + String get location => GoRouteData.$location( + '/', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +extension $FamilyRouteExtension on FamilyRoute { + static FamilyRoute _fromState(GoRouterState state) => FamilyRoute( + fid: state.pathParameters['fid']!, + ); + + String get location => GoRouteData.$location( + '/family/${Uri.encodeComponent(fid)}', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $loginRoute => GoRouteData.$route( + path: '/login', + factory: $LoginRouteExtension._fromState, + ); + +extension $LoginRouteExtension on LoginRoute { + static LoginRoute _fromState(GoRouterState state) => LoginRoute( + from: state.uri.queryParameters['from'], + ); + + String get location => GoRouteData.$location( + '/login', + queryParams: { + if (from != null) 'from': from, + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $myRoute => GoRouteData.$route( + path: '/my-route', + factory: $MyRouteExtension._fromState, + ); + +extension $MyRouteExtension on MyRoute { + static MyRoute _fromState(GoRouterState state) => MyRoute( + queryParameter: + state.uri.queryParameters['query-parameter'] ?? 'defaultValue', + ); + + String get location => GoRouteData.$location( + '/my-route', + queryParams: { + if (queryParameter != 'defaultValue') + 'query-parameter': queryParameter, + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +RouteBase get $personRouteWithExtra => GoRouteData.$route( + path: '/person', + factory: $PersonRouteWithExtraExtension._fromState, + ); + +extension $PersonRouteWithExtraExtension on PersonRouteWithExtra { + static PersonRouteWithExtra _fromState(GoRouterState state) => + PersonRouteWithExtra( + state.extra as Person?, + ); + + String get location => GoRouteData.$location( + '/person', + ); + + void go(BuildContext context) => context.go(location, extra: $extra); + + Future push(BuildContext context) => + context.push(location, extra: $extra); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: $extra); + + void replace(BuildContext context) => + context.replace(location, extra: $extra); +} + +RouteBase get $hotdogRouteWithEverything => GoRouteData.$route( + path: '/:ketchup', + factory: $HotdogRouteWithEverythingExtension._fromState, + ); + +extension $HotdogRouteWithEverythingExtension on HotdogRouteWithEverything { + static HotdogRouteWithEverything _fromState(GoRouterState state) => + HotdogRouteWithEverything( + _$boolConverter(state.pathParameters['ketchup']!), + state.uri.queryParameters['mustard'], + state.extra as Sauce, + ); + + String get location => GoRouteData.$location( + '/${Uri.encodeComponent(ketchup.toString())}', + queryParams: { + if (mustard != null) 'mustard': mustard, + }, + ); + + void go(BuildContext context) => context.go(location, extra: $extra); + + Future push(BuildContext context) => + context.push(location, extra: $extra); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: $extra); + + void replace(BuildContext context) => + context.replace(location, extra: $extra); +} + +bool _$boolConverter(String value) { + switch (value) { + case 'true': + return true; + case 'false': + return false; + default: + throw UnsupportedError('Cannot convert "$value" into a bool.'); + } +} diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 0e6e10b6b3d..6fba2f8dc9f 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,7 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 2.7.0 +version: 2.7.1 repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22 diff --git a/script/configs/temp_exclude_excerpt.yaml b/script/configs/temp_exclude_excerpt.yaml index dbaa5d71535..25580a780f9 100644 --- a/script/configs/temp_exclude_excerpt.yaml +++ b/script/configs/temp_exclude_excerpt.yaml @@ -6,7 +6,6 @@ # TODO(stuartmorgan): Remove everything from this list. See # https://github.com/flutter/flutter/issues/102679 - espresso -- go_router_builder - in_app_purchase/in_app_purchase - palette_generator - pointer_interceptor