[go_router]: Cannot pop back after calling `goBranch` on a pushed ShellRoute
- Dominant language
- Dart
- Stars
- 179k
- Forks
- 31.1k
- PR merge metrics
- PR metrics pending
Description
### Steps to reproduce
1. Push onto a `StatefulShellBranch`
2. Navigate inside another `StatefulShellBranch` using `goBranch`
3. Try to call `context.pop()` to back to the original location
### Expected results
Being able to exit a pushed `StatefulShellRoute` that has been navigated with `goBranch`.
### Actual results
If the user navigates to a different branch than the original, they will not be able to pop the current screen.
### Code sample
Code sample
```dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MainApp());
}
abstract class Routes {
static const tab1 = "/tab-1";
static const tab2 = "/tab-2";
static const tab3 = "/tab-3";
static const tab4 = "/tab-4";
static const tab5 = "/tab-5";
static const test = "/test";
static const menuTab1 = "/menu/tab-1";
static const menuTab2 = "/menu/tab-2";
static const menuTab3 = "/menu/tab-3";
}
abstract class AppRouter {
static final parentNavigatorKey = GlobalKey();
static final tab1NavigatorKey = GlobalKey();
static final tab2NavigatorKey = GlobalKey();
static final tab3NavigatorKey = GlobalKey();
static final tab4NavigatorKey = GlobalKey();
static final tab5NavigatorKey = GlobalKey();
static final menuTab1NavigatorKey = GlobalKey();
static final menuTab2NavigatorKey = GlobalKey();
static final menuTab3NavigatorKey = GlobalKey();
static Page getPage({
required Widget child,
required GoRouterState state,
}) {
return MaterialPage(
key: state.pageKey,
child: child,
);
}
static GoRouter router = GoRouter(
navigatorKey: parentNavigatorKey,
initialLocation: Routes.tab1,
routes: [
StatefulShellRoute.indexedStack(
parentNavigatorKey: parentNavigatorKey,
pageBuilder: (
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) {
return getPage(
child: RootNavigation(
navigationShell: navigationShell,
),
state: state,
);
},
branches: [
StatefulShellBranch(
navigatorKey: tab1NavigatorKey,
routes: [
GoRoute(
path: Routes.tab1,
pageBuilder: (context, GoRouterState state) {
return getPage(
child: const Center(
child: Text("Tab 1 content"),
),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: tab2NavigatorKey,
routes: [
GoRoute(
path: Routes.tab2,
pageBuilder: (context, state) {
return getPage(
child: const Center(
child: Text("Tab 2 content"),
),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: tab3NavigatorKey,
routes: [
GoRoute(
path: Routes.tab3,
pageBuilder: (context, state) {
return getPage(
child: const Center(
child: Text("Tab 3 content"),
),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: tab4NavigatorKey,
routes: [
GoRoute(
path: Routes.tab4,
pageBuilder: (context, state) {
return getPage(
child: const Center(
child: Text("Tab 4 content"),
),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: tab5NavigatorKey,
routes: [
GoRoute(
path: Routes.tab5,
pageBuilder: (context, state) {
return getPage(
child: const Center(
child: Text("Tab 5 content"),
),
state: state,
);
},
),
],
),
],
),
StatefulShellRoute.indexedStack(
parentNavigatorKey: parentNavigatorKey,
pageBuilder: (
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) {
return getPage(
child: Menu(
shell: navigationShell,
),
state: state,
);
},
branches: [
StatefulShellBranch(
navigatorKey: menuTab1NavigatorKey,
routes: [
GoRoute(
path: Routes.menuTab1,
pageBuilder: (context, GoRouterState state) {
return getPage(
child: Center(
child: const Text("Menu tab 1"),
),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: menuTab2NavigatorKey,
routes: [
GoRoute(
path: Routes.menuTab2,
pageBuilder: (context, state) {
return getPage(
child: Center(
child: const Text("Menu tab 2"),
),
state: state,
);
},
),
],
),
StatefulShellBranch(
navigatorKey: menuTab3NavigatorKey,
routes: [
GoRoute(
path: Routes.menuTab3,
pageBuilder: (context, state) {
return getPage(
child: Center(
child: const Text("Menu tab 3"),
),
state: state,
);
},
),
],
),
],
),
GoRoute(
parentNavigatorKey: parentNavigatorKey,
path: Routes.test,
builder: (context, state) {
return Test();
},
),
],
);
}
class Test extends StatelessWidget {
const Test({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
),
body: Center(
child: Text("TEST!"),
),
);
}
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: AppRouter.router,
debugShowCheckedModeBanner: false,
);
}
}
class RootNavigation extends StatelessWidget {
const RootNavigation({
required this.navigationShell,
});
final StatefulNavigationShell navigationShell;
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
navigationShell,
Container(
// color: Colors.lightGreen,
child: Column(
children: [
Text(
"URL Based Navigation",
style: Theme.of(context).textTheme.headlineLarge,
),
ElevatedButton(
onPressed: () {
context.go(Routes.tab1);
},
child: Text("Navigate to: Tab 1"),
),
ElevatedButton(
onPressed: () {
context.go(Routes.tab2);
},
child: Text("Navigate to: Tab 2"),
),
ElevatedButton(
onPressed: () {
context.go(Routes.tab3);
},
child: Text("Navigate to: Tab 3"),
),
ElevatedButton(
onPressed: () {
context.go(Routes.tab4);
},
child: Text("Navigate to: Tab 4"),
),
ElevatedButton(
onPressed: () {
context.go(Routes.tab5);
},
child: Text("Navigate to: Tab 5"),
),
ElevatedButton(
onPressed: () {
context.push(Routes.menuTab1);
},
child: Text("Navigate to: Menu 1"),
),
ElevatedButton(
onPressed: () {
context.push(Routes.menuTab2);
},
child: Text("Navigate to: Menu 2"),
),
ElevatedButton(
onPressed: () {
context.push(Routes.menuTab3);
},
child: Text("Navigate to: Menu 3"),
),
ElevatedButton(
onPressed: () {
context.push(Routes.test);
},
child: Text("Navigate to: Test"),
),
],
),
),
],
),
),
),
bottomNavigationBar: AppNavigationBar(
child: navigationShell,
),
);
}
}
class AppNavigationBar extends StatefulWidget {
const AppNavigationBar({
super.key,
required this.child,
});
final StatefulNavigationShell child;
@override
State createState() => _AppNavigationBarState();
}
class _AppNavigationBarState extends State {
@override
Widget build(BuildContext context) {
return NavigationBar(
selectedIndex: widget.child.currentIndex,
onDestinationSelected: (index) {
widget.child.goBranch(
index,
initialLocation: index == widget.child.currentIndex,
);
setState(() {});
},
destinations: const [
NavigationDestination(
icon: Icon(Icons.hub_outlined),
selectedIcon: Icon(Icons.hub),
label: "Tab 1",
),
NavigationDestination(
icon: Icon(Icons.view_carousel_outlined),
selectedIcon: Icon(Icons.view_carousel),
label: "Tab 2",
),
NavigationDestination(
icon: Icon(Icons.view_carousel_outlined),
selectedIcon: Icon(Icons.view_carousel),
label: "Tab 3",
),
NavigationDestination(
icon: Icon(Icons.view_carousel_outlined),
selectedIcon: Icon(Icons.view_carousel),
label: "Tab 4",
),
NavigationDestination(
icon: Icon(Icons.view_carousel_outlined),
selectedIcon: Icon(Icons.view_carousel),
label: "Tab 5",
),
],
);
}
}
enum MenuSegment { MENU1, MENU2, MENU3 }
class Menu extends StatefulWidget {
Menu({
required this.shell,
});
final StatefulNavigationShell shell;
@override
State createState() => _MenuState();
}
class _MenuState extends State {
void initState() {
currentlySelectedMenuSegment =
MenuSegment.values[widget.shell.currentIndex];
super.initState();
}
late MenuSegment currentlySelectedMenuSegment;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
context.pop();
},
icon: Icon(Icons.arrow_back),
),
actions: [
IconButton(
onPressed: () {
context.pop();
},
icon: Icon(Icons.close),
),
],
backgroundColor: Theme.of(context).colorScheme.background,
elevation: 0,
),
body: SafeArea(
bottom: false,
child: SingleChildScrollView(
child: Column(
children: [
Column(
children: [
const SizedBox(height: 16),
const Text(
"MENU WELCOME",
),
const SizedBox(height: 16),
SegmentedButton(
style: const ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
visualDensity: VisualDensity(
horizontal: -1,
vertical: -1,
),
),
onSelectionChanged: (e) {
// onMenuSegmentSelected(e.first);
int index = MenuSegment.values.indexOf(e.first);
widget.shell.goBranch(
index,
initialLocation: index == widget.shell.currentIndex,
);
setState(() {
currentlySelectedMenuSegment = e.first;
});
},
segments: const [
ButtonSegment(
value: MenuSegment.MENU1,
label: Text("Menu 1"),
icon: Icon(Icons.account_circle_outlined),
),
ButtonSegment(
value: MenuSegment.MENU2,
label: Text("Menu 2"),
icon: Icon(Icons.settings_outlined),
),
ButtonSegment(
value: MenuSegment.MENU3,
label: Text("Menu 3"),
icon: Icon(Icons.credit_card_outlined),
),
],
selected: {currentlySelectedMenuSegment},
showSelectedIcon: false,
),
],
),
widget.shell,
],
),
),
),
);
}
}
```
### Screenshots or Video
Screenshots / Video demonstration
https://github.com/flutter/flutter/assets/70296188/99cbea8e-1bf0-4a12-9ea8-e82fbe9716b4
### Logs
Logs
```console
[Paste your logs here]
```
### Flutter Doctor output
Doctor output
```console
[Paste your output here]
```
Contributor guide
Assessment
This issue has not been assessed yet.