bizz84 / bizz84/nested_navigation_examples

Cupertino navigation fails with "The key [<StatefulNavigationShellState>] was used by multiple widgets"

Open
#11 2 comments 0 reactions 0 assignees View on GitHub
Dominant language
No language data
Stars
118
Forks
20
PR merge metrics
No merged PRs in 30d

Description

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#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... (package:flutter/src/widgets/framework.dart:3113:13)
#1 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:633:13)
#2 BuildOwner._debugVerifyGlobalKeyReservation.. (package:flutter/src/widgets/framework.dart:3057:20)
#3 _LinkedHashMapMixin.forEach (dart:collection-patch/compact_hash.dart:633:13)
#4 BuildOwner._debugVerifyGlobalKeyReservation. (package:flutter/src/widgets/framework.dart:3052:36)
#5 BuildOwner._debugVerifyGlobalKeyReservation (package:flutter/src/widgets/framework.dart:3121:6)
#6 BuildOwner.finalizeTree. (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

```dart
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();
final _shellNavigatorAKey = GlobalKey(debugLabel: 'shellA');
final _shellNavigatorBKey = GlobalKey(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('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: [
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 createState() => DetailsScreenState();
}

/// The state for DetailsScreen
class DetailsScreenState extends State {
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: [
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?

Contributor guide

No contributing guide indexed for this repository

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.