Skip to content

[go_router_builder] add support to go_router_builder for initializing a ShellRoute with observers #5563

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

Merged
merged 12 commits into from
Dec 7, 2023
Merged
3 changes: 2 additions & 1 deletion packages/go_router_builder/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## NEXT
## 2.4.0

* Adds support for passing observers to the ShellRoute for the nested Navigator.
* Updates minimum supported SDK version to Flutter 3.10/Dart 3.0.

## 2.3.4
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// 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

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

part 'shell_route_with_observers_example.g.dart';

void main() => runApp(App());

class App extends StatelessWidget {
App({super.key});

@override
Widget build(BuildContext context) => MaterialApp.router(
routerConfig: _router,
);

final GoRouter _router = GoRouter(
routes: $appRoutes,
initialLocation: '/home',
);
}

@TypedShellRoute<MyShellRouteData>(
routes: <TypedRoute<RouteData>>[
TypedGoRoute<HomeRouteData>(path: '/home'),
TypedGoRoute<UsersRouteData>(
path: '/users',
routes: <TypedGoRoute<UserRouteData>>[
TypedGoRoute<UserRouteData>(path: ':id'),
],
),
],
)
class MyShellRouteData extends ShellRouteData {
const MyShellRouteData();

static final List<NavigatorObserver> $observers = <NavigatorObserver>[
MyNavigatorObserver()
];

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

class MyNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {}
}

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

final Widget child;

int getCurrentIndex(BuildContext context) {
final String location = GoRouterState.of(context).uri.toString();
if (location.startsWith('/users')) {
return 1;
}
return 0;
}

@override
Widget build(BuildContext context) {
final int selectedIndex = getCurrentIndex(context);

return Scaffold(
body: Row(
children: <Widget>[
NavigationRail(
destinations: const <NavigationRailDestination>[
NavigationRailDestination(
icon: Icon(Icons.home),
label: Text('Home'),
),
NavigationRailDestination(
icon: Icon(Icons.group),
label: Text('Users'),
),
],
selectedIndex: selectedIndex,
onDestinationSelected: (int index) {
switch (index) {
case 0:
const HomeRouteData().go(context);
break;
case 1:
const UsersRouteData().go(context);
break;
}
},
),
const VerticalDivider(thickness: 1, width: 1),
Expanded(child: child),
],
),
);
}
}

class HomeRouteData extends GoRouteData {
const HomeRouteData();

@override
Widget build(BuildContext context, GoRouterState state) {
return const Center(child: Text('The home page'));
}
}

class UsersRouteData extends GoRouteData {
const UsersRouteData();

@override
Widget build(BuildContext context, GoRouterState state) {
return ListView(
children: <Widget>[
for (int userID = 1; userID <= 3; userID++)
ListTile(
title: Text('User $userID'),
onTap: () => UserRouteData(id: userID).go(context),
),
],
);
}
}

class DialogPage extends Page<void> {
/// A page to display a dialog.
const DialogPage({required this.child, super.key});

/// The widget to be displayed which is usually a [Dialog] widget.
final Widget child;

@override
Route<void> createRoute(BuildContext context) {
return DialogRoute<void>(
context: context,
settings: this,
builder: (BuildContext context) => child,
);
}
}

class UserRouteData extends GoRouteData {
const UserRouteData({required this.id});

// Without this static key, the dialog will not cover the navigation rail.
final int id;

@override
Page<void> buildPage(BuildContext context, GoRouterState state) {
return DialogPage(
key: state.pageKey,
child: Center(
child: SizedBox(
width: 300,
height: 300,
child: Card(child: Center(child: Text('User $id'))),
),
),
);
}
}

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

16 changes: 13 additions & 3 deletions packages/go_router_builder/lib/src/route_config.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class ShellRouteConfig extends RouteBaseConfig {
required this.navigatorKey,
required this.parentNavigatorKey,
required super.routeDataClass,
required this.observers,
required super.parent,
}) : super._();

Expand All @@ -49,6 +50,9 @@ class ShellRouteConfig extends RouteBaseConfig {
/// The parent navigator key.
final String? parentNavigatorKey;

/// The navigator observers.
final String? observers;

@override
Iterable<String> classDeclarations() {
if (routeDataClass.unnamedConstructor == null) {
Expand All @@ -72,7 +76,8 @@ class ShellRouteConfig extends RouteBaseConfig {
@override
String get routeConstructorParameters =>
'${navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'}'
'${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}';
'${parentNavigatorKey == null ? '' : 'parentNavigatorKey: $parentNavigatorKey,'}'
'${observers == null ? '' : 'observers: $observers,'}';

@override
String get factorConstructorParameters =>
Expand Down Expand Up @@ -475,6 +480,10 @@ abstract class RouteBaseConfig {
classElement,
parameterName: r'$parentNavigatorKey',
),
observers: _generateParameterGetterCode(
classElement,
parameterName: r'$observers',
),
);
break;
case 'TypedStatefulShellRoute':
Expand Down Expand Up @@ -568,7 +577,9 @@ abstract class RouteBaseConfig {
if (!element.isStatic || element.name != parameterName) {
return false;
}
if (parameterName.toLowerCase().contains('navigatorkey')) {
if (parameterName
.toLowerCase()
.contains(RegExp('navigatorKey | observers'))) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this no longer match against navigatorKey due to the toLowerCase() call? It also would require a space after navigatorKey or before observers in order to actually match

final DartType type = element.type;
if (type is! ParameterizedType) {
return false;
Expand All @@ -591,7 +602,6 @@ abstract class RouteBaseConfig {
if (fieldDisplayName != null) {
return '${classElement.name}.$fieldDisplayName';
}

final String? methodDisplayName = classElement.methods
.where((MethodElement element) {
return element.isStatic && element.name == parameterName;
Expand Down
2 changes: 1 addition & 1 deletion packages/go_router_builder/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: go_router_builder
description: >-
A builder that supports generated strongly-typed route helpers for
package:go_router
version: 2.3.4
version: 2.4.0
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

Expand Down