flutter / flutter/flutter

[iOS] DropdownMenu selection tears down the entire iOS accessibility tree when the app uses the Router API (go_router / MaterialApp.router)

Open
#187,611 6 comments 1 reaction 1 assignee Claimed by @QuncCccccc View on GitHub
a: accessibility engine has reproducible steps P2 platform-ios team-ios triaged-ios
Dominant language
Dart
Stars
179k
Forks
31.1k
PR merge metrics
PR metrics pending

Description

### Steps to reproduce

Note: as you can tell, I had a lot of help from Claude on this one. It's very similar to #186582, but is different enough (specific to a Gorouter setup) that I'm entering a separate bug - feel free to close as a duplicate if that's the path forward. It's not clear to me whether or not the PR to fix the existing issue will fix this one too.

Clonable repro: **https://github.com/elitree/dropdown_gorouter_repro**

1. Run on an iOS simulator or device (`flutter run`) with `useGoRouter = true`.
2. Open **Xcode → Open Developer Tool → Accessibility Inspector** and target the simulator/device.
3. Inspect the page — the marker `Text`, the button, and the dropdown are all present in the accessibility hierarchy.
4. Tap the `DropdownMenu` and **select any entry**.
5. Re-inspect: the accessibility tree is now **empty** — the marker `Text` and the button are gone too, not just the menu. It **stays collapsed** for the rest of the session.

### Isolation

Bisected from a large production app down to this minimal repro, running the same Accessibility Inspector before/after check at each step.

**The single differing variable is the routing API:**

| Host | Identical page | Result after selection |
|---|---|---|
| `MaterialApp.router` + `go_router` | ✅ | Tree **collapses** |
| plain `MaterialApp` (Navigator 1.0) | ✅ | Tree **survives** |

Ruled out (collapse still reproduces without them, or they make no difference):
Google Maps / `UiKitView` platform views, `showModalBottomSheet`, `SemanticsBinding.ensureSemantics`, custom `FlutterViewController` subclasses, and various plugins. The reproduction requires the **combination** of the Router API and `DropdownMenu`/`MenuAnchor` — neither alone is sufficient.

### Relationship to #186582 / #187167

This produces the same end state as #186582 (the iOS accessibility bridge is torn down), but I want to flag a distinction rather than assume it:

- **It is not addressed by #187167.** That PR fixes the bridge teardown that happens through a *new `FlutterViewController` reattach*. I instrumented the VC lifecycle during a reproduced collapse and saw **zero** lifecycle transitions and a single, constant owner-VC instance — the reattach path the PR patches never fires in this scenario.
- **It may still share a root cause with #186582.** I could not distinguish, with app/VC-level instrumentation alone, between `SetOwnerViewController(self)` being re-invoked with the *same* instance (which still runs `accessibility_bridge_.reset()`) and a genuinely separate reset path. Confirming that would need engine-level logging in `platform_view_ios.mm`.

So this is filed as a distinct, minimal repro with a reliable trigger that the in-flight fix won't cover. If maintainers determine it's the same underlying cause as #186582, please treat this as that issue's iOS-Router reproduction (and an indication that #187167's scope may need to expand).

### Workaround

Replacing the `DropdownMenu` with a `showModalBottomSheet` list picker keeps the accessibility tree intact under the same test.

### Note for anyone reproducing app-side

You can't observe this from app code by reading `view.accessibilityElements`: without an attached assistive-technology consumer, iOS only exposes a shallow placeholder (count ~2) and materializes the full tree lazily for VoiceOver / Accessibility Inspector / XCUITest. Observe the tree with the **Accessibility Inspector** or an **XCUITest** assertion, not in-app element counting.

### Expected results

Selecting a dropdown entry updates the selection and leaves the accessibility tree
intact — exactly what happens when `useGoRouter = false` (plain `MaterialApp`).

### Actual results

With `useGoRouter = true`, selecting any entry wipes the **entire** iOS accessibility
tree and it is never rebuilt. This breaks VoiceOver and any UIKit-level automation
(XCUITest, Accessibility Inspector, etc.).

### Code sample

Code sample

Requires go_router as a dependency. This is available as a repro project here: https://github.com/elitree/dropdown_gorouter_repro

```dart
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

/// true -> MaterialApp.router + go_router (a11y tree COLLAPSES after a selection)
/// false -> plain MaterialApp (tree survives)
const bool useGoRouter = true;

void main() => runApp(const MyApp());

final _router = GoRouter(
routes: [GoRoute(path: '/', builder: (context, state) => const DropdownPage())],
);

class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
if (useGoRouter) {
return MaterialApp.router(title: 'repro', routerConfig: _router);
}
return const MaterialApp(title: 'repro', home: DropdownPage());
}
}

class DropdownPage extends StatefulWidget {
const DropdownPage({super.key});
@override
State createState() => _DropdownPageState();
}

class _DropdownPageState extends State {
String _selected = 'Owner';
static const _entries = ['Owner', 'Owner Last', 'Parcel ID', 'None'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('repro')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text('MARKER ELEMENT ONE'),
const SizedBox(height: 24),
ElevatedButton(onPressed: () {}, child: const Text('MARKER BUTTON TWO')),
const SizedBox(height: 48),
DropdownMenu(
requestFocusOnTap: false,
initialSelection: _selected,
onSelected: (v) { if (v != null) setState(() => _selected = v); },
dropdownMenuEntries: _entries
.map((e) => DropdownMenuEntry(value: e, label: e))
.toList(),
),
const SizedBox(height: 24),
Text('Selected: $_selected'),
],
),
),
);
}
}
```

### Screenshots or Video

Screenshots / Video demonstration

https://github.com/user-attachments/assets/0299cae2-566d-43fc-af0c-543abc7a5b76

### Logs

Logs

```console
fvm flutter run -d 3B4AC0E0-9B84-47D2-969B-0F010184BF4E
Resolving dependencies...
Downloading packages...
matcher 0.12.19 (0.12.20 available)
meta 1.17.0 (1.18.3 available)
test_api 0.7.10 (0.7.12 available)
vector_math 2.2.0 (2.4.0 available)
Got dependencies!
4 packages have newer versions incompatible with dependency constraints.
Try `flutter pub outdated` for more information.
Launching lib/main.dart on iPhone 16e in debug mode...
Running Xcode build...
Xcode build done. 13.0s
Syncing files to device iPhone 16e... 35ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h List all available interactive commands.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).

A Dart VM Service on iPhone 16e is available at: http://127.0.0.1:51576/Lt6rZAFG35A=/
The Flutter DevTools debugger and profiler on iPhone 16e is available at:
http://127.0.0.1:51576/Lt6rZAFG35A=/devtools/?uri=ws://127.0.0.1:51576/Lt6rZAFG35A=/ws

Application finished.
```

### Flutter Doctor output

Doctor output

```console
fvm flutter doctor -v
[✓] Flutter (Channel stable, 3.41.9, on macOS 26.5.1 25F80 darwin-arm64, locale en-US) [392ms]
• Flutter version 3.41.9 on channel stable at /Users/eligeller/fvm/versions/3.41.9
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 00b0c91f06 (5 weeks ago), 2026-04-29 10:03:19 -0700
• Engine revision 42d3d75a56
• Dart version 3.11.5
• DevTools version 2.54.2
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios,
cli-animations, enable-native-assets, enable-swift-package-manager, omit-legacy-version-file, enable-lldb-debugging,
enable-uiscene-migration

[✓] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [1,752ms]
• Android SDK at /Users/eligeller/Library/Android/sdk
• Emulator version 36.5.10.0 (build_id 15081367) (CL:N/A)
• Platform android-37.0, build-tools 36.1.0
• Java binary at: /Applications/Android Studio.app/Contents/jbr/Contents/Home/bin/java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.10+-117844308-b1163.108)
• All Android licenses accepted.

[✓] Xcode - develop for iOS and macOS (Xcode 26.3) [981ms]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 17C519
• CocoaPods version 1.16.2

[✓] Chrome - develop for the web [4ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome

[✓] Connected device (6 available) [6.1s]
• Eli iPhone 16 Pro (wireless) (mobile) • 00008140-000603510193001C • ios • iOS 26.5 23F77
• Eli iPhone 11 (wireless) (mobile) • 00008030-000E10513A38802E • ios • iOS 26.5 23F77
• Ashley’s iPhone 17 (wireless) (mobile) • 00008150-001571412186401C • ios • iOS 26.5.1 23F81
• iPhone 16e (mobile) • 3B4AC0E0-9B84-47D2-969B-0F010184BF4E • ios •
com.apple.CoreSimulator.SimRuntime.iOS-26-2 (simulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 26.5.1 25F80
darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome
148.0.7778.216
! Error: Browsing on the local area network for Eli’s iPad Pro. Ensure the device is unlocked and attached with a cable or
associated with the same local area network as this Mac.
The device must be opted into Developer Mode to connect wirelessly. (code -27)

[✓] Network resources [302ms]
• All expected network resources are available.

• No issues found!
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.