Repository navigation
Closed
flutter/packages
#12674Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)Accessibility, e.g. VoiceOver or TalkBack. (aka a11y)found in release: 3.32Found to occur in 3.32Found to occur in 3.32found in release: 3.33Found to occur in 3.33Found to occur in 3.33has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packageThe go_router packagepackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.platform-iosiOS applications specificallyiOS applications specificallyteam-accessibilityOwned by Framework Accessibility team (i.e. responsible for accessibility code in flutter/flutter)Owned by Framework Accessibility team (i.e. responsible for accessibility code in flutter/flutter)triaged-accessibilityTriaged by Framework Accessibility teamTriaged by Framework Accessibility team
Description
What package does this bug report belong to?
go_router
What target platforms are you seeing this bug on?
iOS
Have you already upgraded your packages?
Yes
Dependency versions
go_router: ^15.1.2
Steps to reproduce
- Create an empty Flutter project
- Paste the sample code provided in the main.dart file
- enable voice over in iOS
- run the app
Expected results
When a new tab is selected the voice over should announce selected tab:
Selected My Movies, Tab 1 of 3
20250515_161330.mp4
Actual results
Nothing announced.
20250515_161121.mp4
Code sample
ShellRoute Code sample (which drops announcing selection)
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
final GlobalKey<NavigatorState> _rootNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'root');
final GlobalKey<NavigatorState> _shellNavigatorKey =
GlobalKey<NavigatorState>(debugLabel: 'shell');
// This scenario demonstrates how to set up nested navigation using ShellRoute,
// which is a pattern where an additional Navigator is placed in the widget tree
// to be used instead of the root navigator. This allows deep-links to display
// pages along with other UI components such as a BottomNavigationBar.
//
// This example demonstrates how to display a route within a ShellRoute and also
// push a screen using a different navigator (such as the root Navigator) by
// providing a `parentNavigatorKey`.
void main() {
runApp(ShellRouteExampleApp());
}
/// An example demonstrating how to use [ShellRoute]
class ShellRouteExampleApp extends StatelessWidget {
/// Creates a [ShellRouteExampleApp]
ShellRouteExampleApp({super.key});
final GoRouter _router = GoRouter(
navigatorKey: _rootNavigatorKey,
initialLocation: '/a',
debugLogDiagnostics: true,
routes: <RouteBase>[
/// Application shell
ShellRoute(
navigatorKey: _shellNavigatorKey,
builder: (BuildContext context, GoRouterState state, Widget child) {
return ScaffoldWithNavBar(child: child);
},
routes: <RouteBase>[
/// The first screen to display in the bottom navigation bar.
GoRoute(
path: '/a',
builder: (BuildContext context, GoRouterState state) {
return const ScreenA();
},
routes: <RouteBase>[
// The details screen to display stacked on the inner Navigator.
// This will cover screen A but not the application shell.
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen(label: 'A');
},
),
],
),
/// Displayed when the second item in the the bottom navigation bar is
/// selected.
GoRoute(
path: '/b',
builder: (BuildContext context, GoRouterState state) {
return const ScreenB();
},
routes: <RouteBase>[
/// Same as "/a/details", but displayed on the root Navigator by
/// specifying [parentNavigatorKey]. This will cover both screen B
/// and the application shell.
GoRoute(
path: 'details',
parentNavigatorKey: _rootNavigatorKey,
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen(label: 'B');
},
),
],
),
/// The third screen to display in the bottom navigation bar.
GoRoute(
path: '/c',
builder: (BuildContext context, GoRouterState state) {
return const ScreenC();
},
routes: <RouteBase>[
// The details screen to display stacked on the inner Navigator.
// This will cover screen C but not the application shell.
GoRoute(
path: 'details',
builder: (BuildContext context, GoRouterState state) {
return const DetailsScreen(label: 'C');
},
),
],
),
],
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routerConfig: _router,
);
}
}
/// Builds the "shell" for the app by building a Scaffold with a
/// BottomNavigationBar, where [child] is placed in the body of the Scaffold.
class ScaffoldWithNavBar extends StatelessWidget {
/// Constructs an [ScaffoldWithNavBar].
const ScaffoldWithNavBar({
required this.child,
super.key,
});
/// The widget to display in the body of the Scaffold.
/// In this sample, it is a Navigator.
final Widget child;
@override
Widget build(BuildContext context) {
return Scaffold(
body: child,
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'A Screen',
),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'B Screen',
),
BottomNavigationBarItem(
icon: Icon(Icons.notification_important_rounded),
label: 'C Screen',
),
],
currentIndex: _calculateSelectedIndex(context),
onTap: (int idx) => _onItemTapped(idx, context),
),
);
}
static int _calculateSelectedIndex(BuildContext context) {
final String location = GoRouterState.of(context).uri.path;
if (location.startsWith('/a')) {
return 0;
}
if (location.startsWith('/b')) {
return 1;
}
if (location.startsWith('/c')) {
return 2;
}
return 0;
}
void _onItemTapped(int index, BuildContext context) {
switch (index) {
case 0:
GoRouter.of(context).go('/a');
case 1:
GoRouter.of(context).go('/b');
case 2:
GoRouter.of(context).go('/c');
}
}
}
/// The first screen in the bottom navigation bar.
class ScreenA extends StatelessWidget {
/// Constructs a [ScreenA] widget.
const ScreenA({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Screen A'),
TextButton(
onPressed: () {
GoRouter.of(context).go('/a/details');
},
child: const Text('View A details'),
),
],
),
),
);
}
}
/// The second screen in the bottom navigation bar.
class ScreenB extends StatelessWidget {
/// Constructs a [ScreenB] widget.
const ScreenB({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Screen B'),
TextButton(
onPressed: () {
GoRouter.of(context).go('/b/details');
},
child: const Text('View B details'),
),
],
),
),
);
}
}
/// The third screen in the bottom navigation bar.
class ScreenC extends StatelessWidget {
/// Constructs a [ScreenC] widget.
const ScreenC({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
const Text('Screen C'),
TextButton(
onPressed: () {
GoRouter.of(context).go('/c/details');
},
child: const Text('View C details'),
),
],
),
),
);
}
}
/// The details screen for either the A, B or C screen.
class DetailsScreen extends StatelessWidget {
/// Constructs a [DetailsScreen].
const DetailsScreen({
required this.label,
super.key,
});
/// The label to display in the center of the screen.
final String label;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Details Screen'),
),
body: Center(
child: Text(
'Details for $label',
style: Theme.of(context).textTheme.headlineMedium,
),
),
);
}
}
BottomNavigationBar Code sample without using ShellRoute (which announces selection )
import 'package:flutter/material.dart';
/// Flutter code sample for [BottomNavigationBar].
void main() => runApp(const BottomNavigationBarExampleApp());
class BottomNavigationBarExampleApp extends StatelessWidget {
const BottomNavigationBarExampleApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: BottomNavigationBarExample());
}
}
class BottomNavigationBarExample extends StatefulWidget {
const BottomNavigationBarExample({super.key});
@override
State<BottomNavigationBarExample> createState() =>
_BottomNavigationBarExampleState();
}
class _BottomNavigationBarExampleState
extends State<BottomNavigationBarExample> {
int _selectedIndex = 0;
static const TextStyle optionStyle = TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
);
static const List<Widget> _widgetOptions = <Widget>[
Text('Index 0: Home', style: optionStyle),
Text('Index 1: Business', style: optionStyle),
Text('Index 2: School', style: optionStyle),
];
void _onItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('BottomNavigationBar Sample')),
body: Center(child: _widgetOptions.elementAt(_selectedIndex)),
bottomNavigationBar: BottomNavigationBar(
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(icon: Icon(Icons.home), label: 'Home'),
BottomNavigationBarItem(
icon: Icon(Icons.business),
label: 'Business',
),
BottomNavigationBarItem(icon: Icon(Icons.school), label: 'School'),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
}
Screenshots or Videos
Screenshots / Video demonstration
[Upload media here]
Logs
No response
Flutter Doctor output
Doctor output
[!] Flutter (Channel [user-branch], 3.27.3, on macOS 15.4.1 24E263 darwin-arm64, locale en-GB)
! Flutter version 3.27.3 on channel [user-branch] at /Users/has763/Desktop/Projects/github/flutter
Currently on an unknown channel. Run `flutter channel` to switch to an official channel.
If that doesn't fix the issue, reinstall Flutter by following instructions at https://flutter.dev/setup.
! Upstream repository unknown source is not a standard remote.
Set environment variable "FLUTTER_GIT_URL" to unknown source to dismiss this error.
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 16.3)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.2)
[✓] VS Code (version 1.100.0)
[✓] Connected device (6 available)
! Device R5CN80XF0WB is not authorized.
You might need to check your device for an authorization dialog.
[✓] Network resources
! Doctor found issues in 1 category.Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work lista: accessibilityAccessibility, e.g. VoiceOver or TalkBack. (aka a11y)Accessibility, e.g. VoiceOver or TalkBack. (aka a11y)found in release: 3.32Found to occur in 3.32Found to occur in 3.32found in release: 3.33Found to occur in 3.33Found to occur in 3.33has reproducible stepsThe issue has been confirmed reproducible and is ready to work onThe issue has been confirmed reproducible and is ready to work onp: go_routerThe go_router packageThe go_router packagepackageflutter/packages repository. See also p: labels.flutter/packages repository. See also p: labels.platform-iosiOS applications specificallyiOS applications specificallyteam-accessibilityOwned by Framework Accessibility team (i.e. responsible for accessibility code in flutter/flutter)Owned by Framework Accessibility team (i.e. responsible for accessibility code in flutter/flutter)triaged-accessibilityTriaged by Framework Accessibility teamTriaged by Framework Accessibility team
Activity
darshankawar commentedon May 16, 2025
@haashem
Can you check this issue to see if it resembles / relates to your case ?
Also, check if using Semantics helps further in your case or not.
haashem commentedon May 19, 2025
@darshankawar Thanks for quick reply, I tried the workaround but it didn't help.
The issue here I have the semantics are visible but on iOS, the Voice Over doesn't announce the selected tab when it becomes selected!. on Android it works as expected.
darshankawar commentedon May 20, 2025
Thanks for the update. Please check this issue which is fixed recently, so I suggest you to switch to latest master and re-run your scenario to check if reported behavior still persist or not.
haashem commentedon May 20, 2025
I tried channel master but the issue still persists!
darshankawar commentedon May 21, 2025
Thanks for the update. I was able to replicate the reported behavior as described and in latest sdk versions.
chunhtai commentedon May 21, 2025
will need to check with native behavior.
[material_ui][cupertino_ui] allow page routes to opt out of route sem…
[material_ui][cupertino_ui] allow page routes to opt out of route sem…
github-actions commentedon Sep 23, 2026
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of
flutter doctor -vand a minimal reproduction of the issue.