-
Notifications
You must be signed in to change notification settings - Fork 3.6k
[go_router] Nested stateful navigation with ShellRoute #2650
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
Changes from 1 commit
fa4c542
76b17ae
7612a55
62574bd
850f41a
1c04588
1db6bba
6028c90
5f2e995
461efd9
0315e1a
510ec34
e82647a
e38fa32
f38b9bf
89f1dd7
3a00a54
b0a6264
b729d64
3b3909f
f0b2a1b
eb6d4d3
016be76
4ae26d7
4a9889a
1f3f01a
3b38641
d701ab5
bb6240d
245d47b
bda571b
da1ea0b
59c19e7
723de76
4c0a91d
7556965
ee9bde9
e9a7029
59a6b05
06ab1e9
1b877c8
e542a05
2215a51
b180653
8fdfb82
9240ea4
93bce8e
20dc0c6
376e80f
2b2ff91
5c9fe04
81e1296
59e3b66
8636bf8
1c509f1
42c7b7d
62e7fc1
9a7069a
ae48ede
141fdc1
b6b289f
4d05d99
d4edd47
7b9de47
703815c
9f88928
5ca533d
f5f0ecb
4a2eac3
fc7bd54
a65f9df
816acb2
ee2a845
89b82c5
90b9d62
7392264
c0253f6
2c4afa1
845c052
193a267
37c4969
c562482
38b5772
db22bac
d8d1641
4c4b7b0
f08f548
6f1b047
07ee030
9ffff5c
e9d40ee
0ea48cb
4abcaa9
ce23558
351ceb2
4cb0f1e
e6a4f71
873bf34
5b668f8
565c3cd
5ee1a8f
358551f
f9a2608
87211aa
257a272
08148e5
c18941f
b1ce762
75e43d0
9f54b4e
1226f44
881be86
6bbbd29
fd5412f
a5234a6
b9428cb
ee047c8
ddc71f0
5041bea
01cce04
9315373
89341ef
2c298cd
4c22b53
a7d419f
7285788
9983887
ad43837
3a83007
ec6722c
9bb0da9
a1c7a8f
477b47c
7f25f0c
6f35636
bb5c08f
16c095c
a18a5a5
abbd01b
0f36623
316cf8c
79a0c63
3696448
763b136
2f48831
709ee7a
bb7fd9e
9eaf1af
527a806
86a72e1
57b761f
85fa0b6
d75c5e2
bd1c018
c13cd71
c7b1295
0f842ee
80c396f
04b549a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -619,7 +619,7 @@ class ShellRoute extends ShellRouteBase { | |
| /// when switching active branch. | ||
| /// | ||
| /// For a default implementation of [navigatorContainerBuilder], consider using | ||
| /// [StackedShellRoute]. | ||
| /// [StatefulShellRoute]. | ||
| /// | ||
| /// Below is a simple example of how a router configuration with | ||
| /// StatefulShellRoute could be achieved. In this example, a | ||
|
|
@@ -692,7 +692,7 @@ class ShellRoute extends ShellRouteBase { | |
| /// ``` | ||
| /// | ||
| /// See [Stateful Nested Navigation](https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stacked_shell_route.dart) | ||
| /// for a complete runnable example using StatefulShellRoute and StackedShellRoute. | ||
| /// for a complete runnable example using StatefulShellRoute and StatefulShellRoute. | ||
| class StatefulShellRoute extends ShellRouteBase { | ||
tolo marked this conversation as resolved.
Show resolved
Hide resolved
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why does hot reloading reset the state?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just tried running the sample code in macOS and iOS, and hot reload seemed to work as expected. Which environment did you test on?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tested with this app on iOS and Desktop: import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(MyApp());
}
class Foo extends StatelessWidget {
const Foo({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}
///
class MyApp extends StatelessWidget {
///
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: GoRouter(
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
branches: [
StatefulShellBranch(
routes: [
GoRoute(
path: '/',
pageBuilder: (BuildContext context, GoRouterState state) {
return FadeTransitionPage(key: state.pageKey, child: HomeScreen());
},
),
],
),
StatefulShellBranch(
routes: [
GoRoute(
path: '/work',
pageBuilder: (BuildContext context, GoRouterState state) {
return FadeTransitionPage(key: state.pageKey, child: WorkScreen());
},
),
],
),
],
builder: (BuildContext context, GoRouterState state,
StatefulNavigationShell navigationShell) {
return AppScaffold(
shell: navigationShell,
);
},
),
],
),
);
}
}
///
class AppScaffold extends StatelessWidget {
final StatefulNavigationShell shell;
///
const AppScaffold({super.key, required this.shell});
@override
Widget build(BuildContext context) {
return Scaffold(
body: shell,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(icon: Icon(Icons.work), label: 'Work'),
],
currentIndex: shell.currentIndex,
onTap: (int index) => _onTap(context, index),
),
);
}
void _onTap(BuildContext context, int index) {
shell.goBranch(index);
}
}
///
class HomeScreen extends StatefulWidget {
///
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text('Home'),
),
);
}
}
///
class WorkScreen extends StatefulWidget {
///
const WorkScreen({super.key});
@override
State<WorkScreen> createState() => _WorkScreenState();
}
class _WorkScreenState extends State<WorkScreen> {
int count = 0;
final Stream<dynamic> _stream = Stream.periodic(Duration(seconds: 1));
late final StreamSubscription<dynamic> _subscription;
///
void initState() {
super.initState();
_subscription = _stream.listen((_) {
setState(() {
count++;
});
});
}
///
void dispose() {
_subscription.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('Count!!! $count'),
),
);
}
}
/// A page that fades in an out.
class FadeTransitionPage extends CustomTransitionPage<void> {
/// Creates a [FadeTransitionPage].
FadeTransitionPage({
required LocalKey super.key,
required super.child,
}) : super(
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child) =>
FadeTransition(
opacity: animation.drive(_curveTween),
child: child,
));
static final CurveTween _curveTween = CurveTween(curve: Curves.easeIn);
}
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, I'll have a look at your code and see what's going on.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, so the main issue with this code I guess is that the GoRouter is recreated every time MyApp is rebuilt. Among other things this means that new and unique Navigators will be created on each hot reload, thus effectively resetting state. As I've understood it, the intended way to use GoRouter is by creating a single instance only once, and then passing that instance to MaterialApp.router, like the code below (and like is done in all the sample code and documentation). final _router = GoRouter(
routes: [
...
],
);
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I suspect this is because StatefulNavigationShell doesn't use a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree that possibly this shouldn't be a requirement, but I'm wondering if go_router is built to support this right now? Anyway, to make this example work, you would need to use fixed navigatorKeys for both GoRouter and each of the branches. And you're probably (partially) right about the key for StatefulNavigationShell - a GlobalKey is actually used for that Widget though, the problem is that a new key is created each time StatefulShellRoute is instantiated (the shell route holds the key). I tried making it possible to pass this key in the constructor to StatefulShellRoute, but I ended up with new problems with "GlobalKey used by multiple widgets" errors. |
||
| /// Constructs a [StatefulShellRoute] from a list of [StatefulShellBranch]es, | ||
| /// each representing a separate nested navigation tree (branch). | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.