nested_navigation_examples
nested_navigation_examples copied to clipboard
Cupertino navigation fails with "The key [<StatefulNavigationShellState>] was used by multiple widgets"
I know there was a bug fix that was related to this, the workaround for that was making gorouter a global variable. However, I'm not using MaterialApp, but cuportino app. After switching everything over, when I navigate between tabs, I the screen crashes and I get this error.
The key [LabeledGlobalKey<StatefulNavigationShellState>#3d108] was used by multiple widgets. The parents of those widgets were:
- Semantics(container: false, properties: SemanticsProperties, renderObject: RenderSemanticsAnnotations#f9f71)
- Semantics(container: false, properties: SemanticsProperties, renderObject: RenderSemanticsAnnotations#cc9b7)
A GlobalKey can only be specified on one widget at a time in the widget tree.
When the exception was thrown, this was the stack:
#0 BuildOwner._debugVerifyGlobalKeyReservation.<anonymous closure>.<anonymous closure>.<anonymous closure> (package:flutter/src/widgets/framework.dart:3113:13)
#1 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:633:13)
#2 BuildOwner._debugVerifyGlobalKeyReservation.<anonymous closure>.<anonymous closure> (package:flutter/src/widgets/framework.dart:3057:20)
#3 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:633:13)
#4 BuildOwner._debugVerifyGlobalKeyReservation.<anonymous closure> (package:flutter/src/widgets/framework.dart:3052:36)
#5 BuildOwner._debugVerifyGlobalKeyReservation (package:flutter/src/widgets/framework.dart:3121:6)
#6 BuildOwner.finalizeTree.<anonymous closure> (package:flutter/src/widgets/framework.dart:3178:11)
#7 BuildOwner.finalizeTree (package:flutter/src/widgets/framework.dart:3260:8)
this is the same example in Cuportino form
import 'package:flutter/cupertino.dart';
import 'package:go_router/go_router.dart';
// ignore: depend_on_referenced_packages
import 'package:flutter_web_plugins/url_strategy.dart';
// private navigators
final _rootNavigatorKey = GlobalKey<NavigatorState>();
final _shellNavigatorAKey = GlobalKey<NavigatorState>(debugLabel: 'shellA');
final _shellNavigatorBKey = GlobalKey<NavigatorState>(debugLabel: 'shellB');
final goRouter = GoRouter(
initialLocation: '/a',
// * Passing a navigatorKey causes an issue on hot reload:
// * https://github.com/flutter/flutter/issues/113757#issuecomment-1518421380
// * However it's still necessary otherwise the navigator pops back to
// * root on hot reload
navigatorKey: _rootNavigatorKey,
debugLogDiagnostics: true,
routes: [
// Stateful navigation based on:
// https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart
StatefulShellRoute.indexedStack(
builder: (context, state, navigationShell) {
return ScaffoldWithNestedNavigation(navigationShell: navigationShell);
},
branches: [
StatefulShellBranch(
navigatorKey: _shellNavigatorAKey,
routes: [
GoRoute(
path: '/a',
pageBuilder: (context, state) => const NoTransitionPage(
child: RootScreen(label: 'A', detailsPath: '/a/details'),
),
routes: [
GoRoute(
path: 'details',
builder: (context, state) => const DetailsScreen(label: 'A'),
),
],
),
],
),
StatefulShellBranch(
navigatorKey: _shellNavigatorBKey,
routes: [
// Shopping Cart
GoRoute(
path: '/b',
pageBuilder: (context, state) => const NoTransitionPage(
child: RootScreen(label: 'B', detailsPath: '/b/details'),
),
routes: [
GoRoute(
path: 'details',
builder: (context, state) => const DetailsScreen(label: 'B'),
),
],
),
],
),
],
),
],
);
void main() {
// turn off the # in the URLs on the web
usePathUrlStrategy();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return CupertinoApp.router(
routerConfig: goRouter,
debugShowCheckedModeBanner: false,
theme: const CupertinoThemeData(),
);
}
}
// Stateful navigation based on:
// https://github.com/flutter/packages/blob/main/packages/go_router/example/lib/stateful_shell_route.dart
class ScaffoldWithNestedNavigation extends StatelessWidget {
const ScaffoldWithNestedNavigation({
Key? key,
required this.navigationShell,
}) : super(
key: key ?? const ValueKey<String>('ScaffoldWithNestedNavigation'));
final StatefulNavigationShell navigationShell;
void _goBranch(int index) {
navigationShell.goBranch(
index,
// A common pattern when using bottom navigation bars is to support
// navigating to the initial location when tapping the item that is
// already active. This example demonstrates how to support this behavior,
// using the initialLocation parameter of goBranch.
initialLocation: index == navigationShell.currentIndex,
);
}
@override
Widget build(BuildContext context) {
return CupertinoTabScaffold(
tabBar: CupertinoTabBar(
currentIndex: navigationShell.currentIndex,
items: const [
BottomNavigationBarItem(label: 'Section A', icon: Icon(CupertinoIcons.home)),
BottomNavigationBarItem(label: 'Section B', icon: Icon(CupertinoIcons.settings)),
],
onTap: _goBranch,
),
tabBuilder: (context, index) {
return CupertinoTabView(
builder: (context) {
return navigationShell; // Assuming navigationShell is a widget that can handle navigation.
},
);
},
);
}
}
/// Widget for the root/initial pages in the bottom navigation bar.
class RootScreen extends StatelessWidget {
/// Creates a RootScreen
const RootScreen({required this.label, required this.detailsPath, Key? key})
: super(key: key);
/// The label
final String label;
/// The path to the detail page
final String detailsPath;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Tab root - $label'),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Screen $label',
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle),
const Padding(padding: EdgeInsets.all(4)),
CupertinoButton(
onPressed: () => context.go(detailsPath),
child: const Text('View details'),
),
],
),
),
);
}
}
/// The details screen for either the A or B screen.
class DetailsScreen extends StatefulWidget {
/// Constructs a [DetailsScreen].
const DetailsScreen({
required this.label,
Key? key,
}) : super(key: key);
/// The label to display in the center of the screen.
final String label;
@override
State<StatefulWidget> createState() => DetailsScreenState();
}
/// The state for DetailsScreen
class DetailsScreenState extends State<DetailsScreen> {
int _counter = 0;
@override
Widget build(BuildContext context) {
return CupertinoPageScaffold(
navigationBar: CupertinoNavigationBar(
middle: Text('Details Screen - ${widget.label}'),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Details for ${widget.label} - Counter: $_counter',
style: CupertinoTheme.of(context).textTheme.navLargeTitleTextStyle),
const Padding(padding: EdgeInsets.all(4)),
CupertinoButton(
onPressed: () {
setState(() {
_counter++;
});
},
child: const Text('Increment counter'),
),
],
),
),
);
}
}
any idea as to why this works on material but not on cuportino?