diff --git a/packages/go_router_builder/example/lib/shell_route_with_observers_example.dart b/packages/go_router_builder/example/lib/shell_route_with_observers_example.dart new file mode 100644 index 00000000000..b644ee3bb02 --- /dev/null +++ b/packages/go_router_builder/example/lib/shell_route_with_observers_example.dart @@ -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( + routes: >[ + TypedGoRoute(path: '/home'), + TypedGoRoute( + path: '/users', + routes: >[ + TypedGoRoute(path: ':id'), + ], + ), + ], +) +class MyShellRouteData extends ShellRouteData { + const MyShellRouteData(); + + static final List $observers = [ + MyNavigatorObserver() + ]; + + @override + Widget builder(BuildContext context, GoRouterState state, Widget navigator) { + return MyShellRouteScreen(child: navigator); + } +} + +class MyNavigatorObserver extends NavigatorObserver { + @override + void didPush(Route route, Route? 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: [ + NavigationRail( + destinations: const [ + 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: [ + for (int userID = 1; userID <= 3; userID++) + ListTile( + title: Text('User $userID'), + onTap: () => UserRouteData(id: userID).go(context), + ), + ], + ); + } +} + +class DialogPage extends Page { + /// 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 createRoute(BuildContext context) { + return DialogRoute( + 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 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'))), + ), + ), + ); + } +} diff --git a/packages/go_router_builder/example/lib/shell_route_with_observers_example.g.dart b/packages/go_router_builder/example/lib/shell_route_with_observers_example.g.dart new file mode 100644 index 00000000000..5737eb472a3 --- /dev/null +++ b/packages/go_router_builder/example/lib/shell_route_with_observers_example.g.dart @@ -0,0 +1,93 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +// ignore_for_file: always_specify_types, public_member_api_docs + +part of 'shell_route_with_observers_example.dart'; + +// ************************************************************************** +// GoRouterGenerator +// ************************************************************************** + +List get $appRoutes => [ + $myShellRouteData, + ]; + +RouteBase get $myShellRouteData => ShellRouteData.$route( + observers: MyShellRouteData.$observers, + factory: $MyShellRouteDataExtension._fromState, + routes: [ + GoRouteData.$route( + path: '/home', + factory: $HomeRouteDataExtension._fromState, + ), + GoRouteData.$route( + path: '/users', + factory: $UsersRouteDataExtension._fromState, + routes: [ + GoRouteData.$route( + path: ':id', + factory: $UserRouteDataExtension._fromState, + ), + ], + ), + ], + ); + +extension $MyShellRouteDataExtension on MyShellRouteData { + static MyShellRouteData _fromState(GoRouterState state) => + const MyShellRouteData(); +} + +extension $HomeRouteDataExtension on HomeRouteData { + static HomeRouteData _fromState(GoRouterState state) => const HomeRouteData(); + + String get location => GoRouteData.$location( + '/home', + ); + + 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 $UsersRouteDataExtension on UsersRouteData { + static UsersRouteData _fromState(GoRouterState state) => + const UsersRouteData(); + + String get location => GoRouteData.$location( + '/users', + ); + + 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 $UserRouteDataExtension on UserRouteData { + static UserRouteData _fromState(GoRouterState state) => UserRouteData( + id: int.parse(state.pathParameters['id']!), + ); + + String get location => GoRouteData.$location( + '/users/${Uri.encodeComponent(id.toString())}', + ); + + 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); +} diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index d2ff611961e..b358d7809d1 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -40,6 +40,7 @@ class ShellRouteConfig extends RouteBaseConfig { required this.navigatorKey, required this.parentNavigatorKey, required super.routeDataClass, + required this.observers, required super.parent, }) : super._(); @@ -49,6 +50,9 @@ class ShellRouteConfig extends RouteBaseConfig { /// The parent navigator key. final String? parentNavigatorKey; + /// The navigator observers. + final String? observers; + @override Iterable classDeclarations() { if (routeDataClass.unnamedConstructor == null) { @@ -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 => @@ -475,6 +480,10 @@ abstract class RouteBaseConfig { classElement, parameterName: r'$parentNavigatorKey', ), + observers: _generateParameterGetterCode( + classElement, + parameterName: r'$observers', + ), ); break; case 'TypedStatefulShellRoute': @@ -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'))) { final DartType type = element.type; if (type is! ParameterizedType) { return false; @@ -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;