-
Notifications
You must be signed in to change notification settings - Fork 28.9k
Description
Use case
Generating extensions on GoRouteData
classes is a clean design but it becomes impossible to extend. Sometimes we may want different route behaviours under the same path base on runtime states (like mocking a route in testing). Another drawback is difficult to read runtime states in a route class. The only way I know is to declare the states globally (correct me if it's not true).
Proposal
Probably we can create a class (E.g. AppRoutes
in the example) to help manage the routes and generate a route creator for each route, then we can extend AppRoutes
to override the route behaviours. I'm not sure if build runner can work like this way but all required params are given.
Codes we need to write:
class HomeRoute extends GoRouteData {
const HomeRoute(super.creator);
}
class CourseRoute extends GoRouteData {
const CourseRoute(super.creator, this.id, {this.$extra});
final String id;
final CourseCacheData? $extra;
}
class WelcomeRoute extends GoRouteData {
const WelcomeRoute(super.creator);
}
@TypedGoRoutes(routes: [
TypedGoRoute<HomeRoute>(
name: 'home',
path: '/',
routes: [
TypedGoRoute<CourseRoute>(
name: 'course',
path: 'course/:id',
),
],
),
TypedGoRoute<WelcomeRoute>(
name: 'welcome',
path: '/welcome',
),
])
class AppRoutes with _$AppRoutes {
AppRoutes(this.loginInfo);
final LoginInfo loginInfo;
@override
late final home = HomeRouteCreator(
builder: (context, data) => const HomePage(),
);
@override
late final course = CourseRouteCreator(
builder: (context, data) => CoursePage(id: data.id, cache: data.$extra),
redirect: (data) => loginInfo.isLoggedIn ? null : home().location,
);
@override
late final welcome = WelcomeRouteCreator(
builder: (context, data) => const WelcomePage(),
);
}
Part of base classes:
abstract class GoRouteData {
const GoRouteData(this.creator);
final GoRouteCreator? creator;
String get location => creator?.$location(this) ?? '';
Widget build(BuildContext context) => (creator?.builder ?? defaultBuilder)(context, this);
Page<void> pageBuilder(BuildContext context) => (creator?.pageBuilder ?? defaultPageBuilder)(context, this);
String? redirect() => (creator?.redirect ?? defaultRedirect)(this);
}
abstract class GoRouteCreator<T extends GoRouteData> {
const GoRouteCreator({this.builder, this.pageBuilder, this.redirect});
String $location(T data);
T $fromState(GoRouterState state);
GoRoute $route({required String path, List<GoRoute> routes = const []}) {
return GoRouteData.$route(
path: path,
factory: $fromState,
routes: routes,
);
}
final Widget Function(BuildContext context, T data)? builder;
final Page<void> Function(BuilderContext context, T data)? pageBuilder;
final String? Function(T data)? redirect;
}
Generated file:
abstract class _$AppRoutes {
List<GoRoute> get $routes => [
home.$route(
path: '/',
routes: [
course.$route(path: 'course/:id'),
],
),
welcome.$route(path: '/welcome'),
];
HomeRouteCreator get home;
CourseRouteCreator get course;
WelcomeRouteCreator get welcome;
}
class HomeRouteCreator extends GoRouteCreator<HomeRoute> {
const HomeRouteCreator({
super.builder,
super.pageBuilder,
super.redirect,
});
@override
String $location(HomeRoute data) => '/';
@override
HomeRoute $fromState(GoRouterState state) => HomeRoute(this);
HomeRoute call() => HomeRoute(this);
}
class CourseRouteCreator extends GoRouteCreator<CourseRoute> {
const CourseRouteCreator({
super.builder,
super.pageBuilder,
super.redirect,
});
@override
String $location(CourseRoute data) => '/course/${data.id}';
@override
CourseRoute $fromState(GoRouterState state) => CourseRoute(this, state..params['id']!, $extra: state.extra as CourseCacheData?);
CourseRoute call(String id, {CourseCacheData? $extra}) => CourseRoute(this, id, $extra: $extra);
}
class WelcomeRouteCreator extends GoRouteCreator<WelcomeRoute> {
const WelcomeRouteCreator({
super.builder,
super.pageBuilder,
super.redirect,
});
@override
String $location(WelcomeRoute data) => '/welcome';
@override
WelcomeRoute $fromState(GoRouterState state) => WelcomeRoute(this);
WelcomeRoute call() => WelcomeRoute(this);
}
Now we can get app routes like this:
/// Gets [loginInfo] from somewhere.
final appRoutes = AppRoutes(loginInfo);
/// Passes [appRoutes.$routes] to [GoRouter].
/// Redirects to course route with id 'COURESID'.
appRoutes.course('COURESID').go(context);
In the end, thank you so much for your amazing work!