Skip to content

[go_router_builder] Add ShellRoute support to go_router_builder #3269

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions packages/go_router_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 1.2.0

* Adds Support for ShellRoute

## 1.1.1

* Support for the generation of the pushReplacement method has been added.
Expand Down
4 changes: 2 additions & 2 deletions packages/go_router_builder/example/lib/all_types.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

64 changes: 50 additions & 14 deletions packages/go_router_builder/example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class App extends StatelessWidget {
late final GoRouter _router = GoRouter(
debugLogDiagnostics: true,
routes: $appRoutes,
navigatorKey: key,

// redirect to the login page if the user is not logged in
redirect: (BuildContext context, GoRouterState state) {
Expand Down Expand Up @@ -63,20 +64,26 @@ class App extends StatelessWidget {
);
}

const GlobalObjectKey<NavigatorState> key = GlobalObjectKey('navigator_key');

@TypedGoRoute<HomeRoute>(
path: '/',
routes: <TypedGoRoute<GoRouteData>>[
TypedGoRoute<FamilyRoute>(
path: 'family/:fid',
routes: <TypedShellRoute<FamilyRoute>>[
TypedShellRoute<FamilyRoute>(
routes: <TypedGoRoute<GoRouteData>>[
TypedGoRoute<PersonRoute>(
path: 'person/:pid',
routes: <TypedGoRoute<GoRouteData>>[
TypedGoRoute<PersonDetailsRoute>(path: 'details/:details'),
TypedGoRoute<FamilyIdRoute>(
path: 'family/:fid',
routes: <TypedGoRoute<PersonRoute>>[
TypedGoRoute<PersonRoute>(
path: 'person/:pid',
routes: <TypedGoRoute<GoRouteData>>[
TypedGoRoute<PersonDetailsRoute>(path: 'details/:details'),
],
),
],
),
],
)
),
],
)
class HomeRoute extends GoRouteData {
Expand All @@ -86,6 +93,17 @@ class HomeRoute extends GoRouteData {
Widget build(BuildContext context, GoRouterState state) => const HomeScreen();
}

class FamilyRoute extends ShellRouteData {
const FamilyRoute();

static final GlobalKey<NavigatorState> $navigatorKey = GlobalKey();

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

@TypedGoRoute<LoginRoute>(
path: '/login',
)
Expand All @@ -99,14 +117,15 @@ class LoginRoute extends GoRouteData {
LoginScreen(from: fromPage);
}

class FamilyRoute extends GoRouteData {
const FamilyRoute(this.fid);
class FamilyIdRoute extends GoRouteData {
const FamilyIdRoute(this.fid);

final String fid;

@override
Widget build(BuildContext context, GoRouterState state) =>
FamilyScreen(family: familyById(fid));
Widget build(BuildContext context, GoRouterState state) => FamilyIdScreen(
family: familyById(fid),
);
}

class PersonRoute extends GoRouteData {
Expand Down Expand Up @@ -177,7 +196,7 @@ class HomeScreen extends StatelessWidget {
for (final Family f in familyData)
ListTile(
title: Text(f.name),
onTap: () => FamilyRoute(f.id).go(context),
onTap: () => FamilyIdRoute(f.id).go(context),
)
],
),
Expand All @@ -186,7 +205,23 @@ class HomeScreen extends StatelessWidget {
}

class FamilyScreen extends StatelessWidget {
const FamilyScreen({required this.family, super.key});
const FamilyScreen({
required this.child,
super.key,
});

final Widget child;

@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.all(20),
child: child,
);
}

class FamilyIdScreen extends StatelessWidget {
const FamilyIdScreen({required this.family, super.key});

final Family family;

@override
Expand Down Expand Up @@ -279,6 +314,7 @@ class PersonDetailsPage extends StatelessWidget {

class LoginScreen extends StatelessWidget {
const LoginScreen({this.from, super.key});

final String? from;

@override
Expand Down
30 changes: 20 additions & 10 deletions packages/go_router_builder/example/lib/main.g.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/go_router_builder/example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ dependencies:
provider: ^6.0.0

dev_dependencies:
build_runner: ^2.0.0
build_runner: ^2.3.0
build_verify: ^3.1.0
flutter_test:
sdk: flutter
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class GoRouterGenerator extends GeneratorForAnnotation<void> {

return <String>[
'''
List<GoRoute> get \$appRoutes => [
List<RouteBase> get \$appRoutes => [
${getters.map((String e) => "$e,").join('\n')}
];
''',
Expand Down
96 changes: 82 additions & 14 deletions packages/go_router_builder/lib/src/route_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ class RouteConfig {
this._path,
this._routeDataClass,
this._parent,
this._key,
this._isShellRoute,
);

/// Creates a new [RouteConfig] represented the annotation data in [reader].
Expand Down Expand Up @@ -66,19 +68,26 @@ class RouteConfig {
RouteConfig? parent,
) {
assert(!reader.isNull, 'reader should not be null');
final ConstantReader pathValue = reader.read('path');
if (pathValue.isNull) {
throw InvalidGenerationSourceError(
'Missing `path` value on annotation.',
element: element,
);
}
final InterfaceType type = reader.objectValue.type! as InterfaceType;
// Ignore the deprected `element2` so that the "downgraded_analyze" CI step
// passes.
//ignore: deprecated_member_use
final bool isShellRoute = type.element2.name == 'TypedShellRoute';

final String path = pathValue.stringValue;
String? path;

final InterfaceType type = reader.objectValue.type! as InterfaceType;
final DartType typeParamType = type.typeArguments.single;
if (!isShellRoute) {
final ConstantReader pathValue = reader.read('path');
if (pathValue.isNull) {
throw InvalidGenerationSourceError(
'Missing `path` value on annotation.',
element: element,
);
}
path = pathValue.stringValue;
}

final DartType typeParamType = type.typeArguments.single;
if (typeParamType is! InterfaceType) {
throw InvalidGenerationSourceError(
'The type parameter on one of the @TypedGoRoute declarations could not '
Expand All @@ -93,7 +102,13 @@ class RouteConfig {
// ignore: deprecated_member_use
final InterfaceElement classElement = typeParamType.element;

final RouteConfig value = RouteConfig._(path, classElement, parent);
final RouteConfig value = RouteConfig._(
path ?? '',
classElement,
parent,
_decodeKey(classElement),
isShellRoute,
);

value._children.addAll(reader.read('routes').listValue.map((DartObject e) =>
RouteConfig._fromAnnotation(ConstantReader(e), element, value)));
Expand All @@ -105,6 +120,39 @@ class RouteConfig {
final String _path;
final InterfaceElement _routeDataClass;
final RouteConfig? _parent;
final String? _key;
final bool _isShellRoute;

static String? _decodeKey(InterfaceElement classElement) {
bool whereStatic(FieldElement element) => element.isStatic;
bool whereKeyName(FieldElement element) => element.name == r'$navigatorKey';
final String? fieldDisplayName = classElement.fields
.where(whereStatic)
.where(whereKeyName)
.where((FieldElement element) {
final DartType type = element.type;
if (type is! ParameterizedType) {
return false;
}
final List<DartType> typeArguments = type.typeArguments;
if (typeArguments.length != 1) {
return false;
}
final DartType typeArgument = typeArguments.single;
if (typeArgument.getDisplayString(withNullability: false) ==
'NavigatorState') {
return true;
}
return false;
})
.map<String>((FieldElement e) => e.displayName)
.firstOrNull;

if (fieldDisplayName == null) {
return null;
}
return '${classElement.name}.$fieldDisplayName';
}

/// Generates all of the members that correspond to `this`.
InfoIterable generateMembers() => InfoIterable._(
Expand Down Expand Up @@ -136,7 +184,15 @@ class RouteConfig {
}

/// Returns `extension` code.
String _extensionDefinition() => '''
String _extensionDefinition() {
if (_isShellRoute) {
return '''
extension $_extensionName on $_className {
static $_className _fromState(GoRouterState state) $_newFromState
}
''';
}
return '''
extension $_extensionName on $_className {
static $_className _fromState(GoRouterState state) $_newFromState

Expand All @@ -150,6 +206,7 @@ extension $_extensionName on $_className {
context.pushReplacement(location, extra: this);
}
''';
}

/// Returns this [RouteConfig] and all child [RouteConfig] instances.
Iterable<RouteConfig> _flatten() sync* {
Expand All @@ -164,7 +221,7 @@ extension $_extensionName on $_className {

/// Returns the `GoRoute` code for the annotated class.
String _rootDefinition() => '''
GoRoute get $_routeGetterName => ${_routeDefinition()};
RouteBase get $_routeGetterName => ${_routeDefinition()};
''';

/// Returns code representing the constant maps that contain the `enum` to
Expand Down Expand Up @@ -257,11 +314,22 @@ GoRoute get $_routeGetterName => ${_routeDefinition()};
: '''
routes: [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}],
''';

final String navigatorKey =
_key == null || _key!.isEmpty ? '' : 'navigatorKey: $_key,';
if (_isShellRoute) {
return '''
ShellRouteData.\$route(
factory: $_extensionName._fromState,
$navigatorKey
$routesBit
)
''';
}
return '''
GoRouteData.\$route(
path: ${escapeDartString(_path)},
factory: $_extensionName._fromState,
$navigatorKey
$routesBit
)
''';
Expand Down
Loading