Skip to content

Commit c3a5fb9

Browse files
authored
[go_router_builder] Add go_router StatefulShellRoute support to go_router_builder (#4238)
fixes: flutter/flutter#127371
1 parent 11b79b5 commit c3a5fb9

10 files changed

+608
-73
lines changed

packages/go_router_builder/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.3.0
2+
3+
* Adds Support for StatefulShellRoute
4+
15
## 2.2.5
26

37
* Fixes a bug where shell routes without const constructor were not generated correctly.

packages/go_router_builder/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ To use `go_router_builder`, you need to have the following dependencies in
88
```yaml
99
dependencies:
1010
# ...along with your other dependencies
11-
go_router: ^7.0.0
11+
go_router: ^9.0.3
1212

1313
dev_dependencies:
1414
# ...along with your other dev-dependencies
1515
build_runner: ^2.0.0
16-
go_router_builder: ^2.0.0
16+
go_router_builder: ^2.3.0
1717
```
1818
1919
### Source code

packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 266 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,266 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// ignore_for_file: public_member_api_docs
6+
7+
import 'package:collection/collection.dart';
8+
import 'package:flutter/material.dart';
9+
import 'package:go_router/go_router.dart';
10+
11+
part 'stateful_shell_route_example.g.dart';
12+
13+
final GlobalKey<NavigatorState> _sectionANavigatorKey =
14+
GlobalKey<NavigatorState>(debugLabel: 'sectionANav');
15+
void main() => runApp(App());
16+
17+
class App extends StatelessWidget {
18+
App({super.key});
19+
20+
@override
21+
Widget build(BuildContext context) => MaterialApp.router(
22+
routerConfig: _router,
23+
);
24+
25+
final GoRouter _router = GoRouter(
26+
routes: $appRoutes,
27+
initialLocation: '/detailsA',
28+
);
29+
}
30+
31+
class HomeScreen extends StatelessWidget {
32+
const HomeScreen({super.key});
33+
34+
@override
35+
Widget build(BuildContext context) => Scaffold(
36+
appBar: AppBar(title: const Text('foo')),
37+
);
38+
}
39+
40+
@TypedStatefulShellRoute<MyShellRouteData>(
41+
branches: <TypedStatefulShellBranch<StatefulShellBranchData>>[
42+
TypedStatefulShellBranch<BranchAData>(
43+
routes: <TypedRoute<RouteData>>[
44+
TypedGoRoute<DetailsARouteData>(path: '/detailsA'),
45+
],
46+
),
47+
TypedStatefulShellBranch<BranchBData>(
48+
routes: <TypedRoute<RouteData>>[
49+
TypedGoRoute<DetailsBRouteData>(path: '/detailsB'),
50+
],
51+
),
52+
],
53+
)
54+
class MyShellRouteData extends StatefulShellRouteData {
55+
const MyShellRouteData();
56+
57+
@override
58+
Widget builder(
59+
BuildContext context,
60+
GoRouterState state,
61+
StatefulNavigationShell navigationShell,
62+
) {
63+
return navigationShell;
64+
}
65+
66+
static const String $restorationScopeId = 'restorationScopeId';
67+
68+
static Widget $navigatorContainerBuilder(BuildContext context,
69+
StatefulNavigationShell navigationShell, List<Widget> children) {
70+
return ScaffoldWithNavBar(
71+
navigationShell: navigationShell,
72+
children: children,
73+
);
74+
}
75+
}
76+
77+
class BranchAData extends StatefulShellBranchData {
78+
const BranchAData();
79+
}
80+
81+
class BranchBData extends StatefulShellBranchData {
82+
const BranchBData();
83+
84+
static final GlobalKey<NavigatorState> $navigatorKey = _sectionANavigatorKey;
85+
static const String $restorationScopeId = 'restorationScopeId';
86+
}
87+
88+
class DetailsARouteData extends GoRouteData {
89+
const DetailsARouteData();
90+
91+
@override
92+
Widget build(BuildContext context, GoRouterState state) {
93+
return const DetailsScreen(label: 'A');
94+
}
95+
}
96+
97+
class DetailsBRouteData extends GoRouteData {
98+
const DetailsBRouteData();
99+
100+
@override
101+
Widget build(BuildContext context, GoRouterState state) {
102+
return const DetailsScreen(label: 'B');
103+
}
104+
}
105+
106+
/// Builds the "shell" for the app by building a Scaffold with a
107+
/// BottomNavigationBar, where [child] is placed in the body of the Scaffold.
108+
class ScaffoldWithNavBar extends StatelessWidget {
109+
/// Constructs an [ScaffoldWithNavBar].
110+
const ScaffoldWithNavBar({
111+
required this.navigationShell,
112+
required this.children,
113+
Key? key,
114+
}) : super(key: key ?? const ValueKey<String>('ScaffoldWithNavBar'));
115+
116+
/// The navigation shell and container for the branch Navigators.
117+
final StatefulNavigationShell navigationShell;
118+
119+
/// The children (branch Navigators) to display in a custom container
120+
/// ([AnimatedBranchContainer]).
121+
final List<Widget> children;
122+
123+
@override
124+
Widget build(BuildContext context) {
125+
return Scaffold(
126+
body: AnimatedBranchContainer(
127+
currentIndex: navigationShell.currentIndex,
128+
children: children,
129+
),
130+
bottomNavigationBar: BottomNavigationBar(
131+
// Here, the items of BottomNavigationBar are hard coded. In a real
132+
// world scenario, the items would most likely be generated from the
133+
// branches of the shell route, which can be fetched using
134+
// `navigationShell.route.branches`.
135+
items: const <BottomNavigationBarItem>[
136+
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Section A'),
137+
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Section B'),
138+
],
139+
currentIndex: navigationShell.currentIndex,
140+
onTap: (int index) => _onTap(context, index),
141+
),
142+
);
143+
}
144+
145+
/// Navigate to the current location of the branch at the provided index when
146+
/// tapping an item in the BottomNavigationBar.
147+
void _onTap(BuildContext context, int index) {
148+
// When navigating to a new branch, it's recommended to use the goBranch
149+
// method, as doing so makes sure the last navigation state of the
150+
// Navigator for the branch is restored.
151+
navigationShell.goBranch(
152+
index,
153+
// A common pattern when using bottom navigation bars is to support
154+
// navigating to the initial location when tapping the item that is
155+
// already active. This example demonstrates how to support this behavior,
156+
// using the initialLocation parameter of goBranch.
157+
initialLocation: index == navigationShell.currentIndex,
158+
);
159+
}
160+
}
161+
162+
/// Custom branch Navigator container that provides animated transitions
163+
/// when switching branches.
164+
class AnimatedBranchContainer extends StatelessWidget {
165+
/// Creates a AnimatedBranchContainer
166+
const AnimatedBranchContainer(
167+
{super.key, required this.currentIndex, required this.children});
168+
169+
/// The index (in [children]) of the branch Navigator to display.
170+
final int currentIndex;
171+
172+
/// The children (branch Navigators) to display in this container.
173+
final List<Widget> children;
174+
175+
@override
176+
Widget build(BuildContext context) {
177+
return Stack(
178+
children: children.mapIndexed(
179+
(int index, Widget navigator) {
180+
return AnimatedScale(
181+
scale: index == currentIndex ? 1 : 1.5,
182+
duration: const Duration(milliseconds: 400),
183+
child: AnimatedOpacity(
184+
opacity: index == currentIndex ? 1 : 0,
185+
duration: const Duration(milliseconds: 400),
186+
child: _branchNavigatorWrapper(index, navigator),
187+
),
188+
);
189+
},
190+
).toList());
191+
}
192+
193+
Widget _branchNavigatorWrapper(int index, Widget navigator) => IgnorePointer(
194+
ignoring: index != currentIndex,
195+
child: TickerMode(
196+
enabled: index == currentIndex,
197+
child: navigator,
198+
),
199+
);
200+
}
201+
202+
/// The details screen for either the A or B screen.
203+
class DetailsScreen extends StatefulWidget {
204+
/// Constructs a [DetailsScreen].
205+
const DetailsScreen({
206+
required this.label,
207+
this.param,
208+
this.extra,
209+
super.key,
210+
});
211+
212+
/// The label to display in the center of the screen.
213+
final String label;
214+
215+
/// Optional param
216+
final String? param;
217+
218+
/// Optional extra object
219+
final Object? extra;
220+
@override
221+
State<StatefulWidget> createState() => DetailsScreenState();
222+
}
223+
224+
/// The state for DetailsScreen
225+
class DetailsScreenState extends State<DetailsScreen> {
226+
int _counter = 0;
227+
228+
@override
229+
Widget build(BuildContext context) {
230+
return Scaffold(
231+
appBar: AppBar(
232+
title: Text('Details Screen - ${widget.label}'),
233+
),
234+
body: _build(context),
235+
);
236+
}
237+
238+
Widget _build(BuildContext context) {
239+
return Center(
240+
child: Column(
241+
mainAxisSize: MainAxisSize.min,
242+
children: <Widget>[
243+
Text('Details for ${widget.label} - Counter: $_counter',
244+
style: Theme.of(context).textTheme.titleLarge),
245+
const Padding(padding: EdgeInsets.all(4)),
246+
TextButton(
247+
onPressed: () {
248+
setState(() {
249+
_counter++;
250+
});
251+
},
252+
child: const Text('Increment counter'),
253+
),
254+
const Padding(padding: EdgeInsets.all(8)),
255+
if (widget.param != null)
256+
Text('Parameter: ${widget.param!}',
257+
style: Theme.of(context).textTheme.titleMedium),
258+
const Padding(padding: EdgeInsets.all(8)),
259+
if (widget.extra != null)
260+
Text('Extra: ${widget.extra!}',
261+
style: Theme.of(context).textTheme.titleMedium),
262+
],
263+
),
264+
);
265+
}
266+
}

packages/go_router_builder/example/lib/stateful_shell_route_example.g.dart

Lines changed: 80 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/go_router_builder/example/pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ environment:
66
sdk: ">=2.18.0 <4.0.0"
77

88
dependencies:
9+
collection: ^1.15.0
910
flutter:
1011
sdk: flutter
1112
go_router: ^10.0.0

0 commit comments

Comments
 (0)