Repository navigation
Open
flutter/packages
#11910Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listfound in release: 3.44Found to occur in 3.44Found to occur in 3.44has 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.team-androidOwned by Android platform teamOwned by Android platform teamtriaged-androidTriaged by Android platform teamTriaged by Android platform teamwaiting for PR to land (fixed)A fix is in flightA fix is in flight
Description
Steps to reproduce
- Make sure predictive back is enabled (either Android 16 SDK 36 or set
android:enableOnBackInvokedCallback="true"inmain/AndroidManifest.xml). - Run the sample code below on Android 16.
- On the initial "Stack A" screen, push the button
Push one screentwice to push 2 screens into branch A. - Use the bottom navigation bar to navigate to
C. Push the button once to push 1 screen into branch C. - Use the bottom navigation bar to navigate to
B. - Do the predictive back gesture twice (swipe from left edge of the screen to the middle).
- Use the bottom navigation bar to navigate to
A.
Expected results
Screens from inactive branch A and C should not pop off.
Actual results
Each predictive back pops one screen off each of the 3 branches if there is something to pop:
- First predictive back pops screen A3 and C2.
- Second predictive back pops screen A2.
It does not call the screen dispose function after the screens are popped off. If we navigate to branch A, the dispose function for both screen A2 and A3 are called but in the wrong order. When popping off screens normally, A3 dispose should be called first, then follow by A2. In this case, it's A2 first, then A3.
Code sample
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(const MyApp());
}
final GoRouter _router = GoRouter(
initialLocation: '/A1',
routes: [
StatefulShellRoute.indexedStack(
builder:
(
BuildContext context,
GoRouterState state,
StatefulNavigationShell navigationShell,
) {
return BottomNavWrapper(navigationShell: navigationShell);
},
branches: <StatefulShellBranch>[
StatefulShellBranch(
initialLocation: '/A1',
observers: [LoggingObserver('/A')],
routes: <RouteBase>[
GoRoute(
path: '/A1',
builder: (BuildContext context, GoRouterState state) =>
MyTestPage(
title: 'Stack A - 1',
nextRoutePath: '/A1/A2',
canPop: false,
),
routes: [
GoRoute(
path: '/A2',
builder: (BuildContext context, GoRouterState state) =>
MyTestPage(
title: 'Stack A - 2',
nextRoutePath: '/A1/A2/A3',
),
routes: [
GoRoute(
path: '/A3',
builder: (BuildContext context, GoRouterState state) =>
MyTestPage(title: 'Stack A - 3'),
),
],
),
],
),
],
),
StatefulShellBranch(
initialLocation: '/B1',
observers: [LoggingObserver('/B')],
routes: <RouteBase>[
GoRoute(
path: '/B1',
builder: (BuildContext context, GoRouterState state) =>
MyTestPage(title: 'Stack B - 1', canPop: false),
),
],
),
StatefulShellBranch(
initialLocation: '/C1',
observers: [LoggingObserver('/C')],
routes: <RouteBase>[
GoRoute(
path: '/C1',
builder: (BuildContext context, GoRouterState state) =>
MyTestPage(
title: 'Stack C - 1',
nextRoutePath: '/C1/C2',
canPop: false,
),
routes: [
GoRoute(
path: '/C2',
builder: (BuildContext context, GoRouterState state) =>
MyTestPage(title: 'Stack C - 2'),
),
],
),
],
),
],
),
],
);
class BottomNavWrapper extends StatelessWidget {
final StatefulNavigationShell navigationShell;
const BottomNavWrapper({super.key, required this.navigationShell});
@override
Widget build(BuildContext context) {
return Scaffold(
body: navigationShell,
bottomNavigationBar: NavigationBar(
onDestinationSelected: (int index) {
navigationShell.goBranch(index, initialLocation: false);
},
selectedIndex: navigationShell.currentIndex,
destinations: <Widget>[
NavigationDestination(
icon: Icon(Icons.navigation_outlined),
selectedIcon: Icon(Icons.navigation),
label: 'A',
),
NavigationDestination(
icon: Icon(Icons.navigation_outlined),
selectedIcon: Icon(Icons.navigation),
label: 'B',
),
NavigationDestination(
icon: Icon(Icons.navigation_outlined),
selectedIcon: Icon(Icons.navigation),
label: 'C',
),
],
),
);
}
}
class LoggingObserver extends NavigatorObserver {
final String name;
LoggingObserver(this.name);
@override
void didPop(Route route, Route? previousRoute) {
debugPrint(
'$name didPop ${route.settings.name} -> ${previousRoute?.settings.name}',
);
}
@override
void didPush(Route route, Route? previousRoute) {
debugPrint('$name didPush ${route.settings.name}');
}
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
void initState() {
super.initState();
_router.routerDelegate.addListener(() {
debugPrint(
'Location: '
'${_router.routerDelegate.currentConfiguration.last.matchedLocation}',
);
});
}
@override
Widget build(BuildContext context) {
return MaterialApp.router(title: 'Go Router Bug', routerConfig: _router);
}
}
class MyTestPage extends StatefulWidget {
const MyTestPage({
super.key,
required this.title,
this.nextRoutePath,
this.canPop = true,
});
final String title;
final String? nextRoutePath;
final bool canPop;
@override
State<MyTestPage> createState() => _MyTestPageState();
}
class _MyTestPageState extends State<MyTestPage> {
@override
void dispose() {
debugPrint('${widget.title} dispose');
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: widget.canPop,
onPopInvokedWithResult: (bool didPop, Object? result) {
debugPrint('${widget.title} onPopInvokedWithResult didPop=$didPop');
},
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
leading: widget.canPop
? IconButton(
icon: const Icon(Icons.chevron_left),
onPressed: () => Navigator.of(context).maybePop(),
)
: null,
),
body: Center(
child: Column(
mainAxisAlignment: .center,
children: [
Text(
widget.title,
style: Theme.of(context).textTheme.headlineMedium,
),
if (widget.nextRoutePath != null)
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: ElevatedButton(
onPressed: () => context.push(widget.nextRoutePath!),
child: Text('Push one screen'),
),
),
],
),
),
),
);
}
}Screenshots or Video
Screenshots / Video demonstration
Screen_recording_20260615_233149.webm
Logs
Logs
I/flutter (11158): Location: /A1
I/flutter (11158): /A didPush /A1
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Location: /A1/A2
I/flutter (11158): /A didPush /A2
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Location: /A1/A2/A3
I/flutter (11158): /A didPush /A3
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Location: /C1
I/flutter (11158): /C didPush /C1
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Location: /C1/C2
I/flutter (11158): /C didPush /C2
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Location: /B1
I/flutter (11158): /B didPush /B1
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Location: /B1
I/flutter (11158): Stack A - 3 onPopInvokedWithResult didPop=true
I/flutter (11158): /A didPop /A3 -> /A2
I/flutter (11158): Location: /B1
I/flutter (11158): Stack C - 2 onPopInvokedWithResult didPop=true
I/flutter (11158): /C didPop /C2 -> /C1
I/flutter (11158): Location: /B1
I/flutter (11158): Stack A - 2 onPopInvokedWithResult didPop=true
I/flutter (11158): /A didPop /A2 -> /A1
I/flutter (11158): Location: /A1/A2/A3
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Stack A - 3 dispose
I/flutter (11158): Stack A - 2 dispose
I/flutter (11158): Location: /C1/C2
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@812848b
D/WindowOnBackDispatcher(11158): setTopOnBackInvokedCallback (unwrapped): io.flutter.embedding.android.FlutterActivity$1@71f9c47
I/flutter (11158): Stack C - 2 disposeFlutter Doctor output
Doctor output
[✓] Flutter (Channel stable, 3.44.0, on macOS 26.5.1 25F80 darwin-arm64, locale en-AU) [329ms]
• Flutter version 3.44.0 on channel stable at /Users/dev/development/flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision 559ffa3f75 (4 weeks ago), 2026-05-15 14:13:13 -0700
• Engine revision 4c525dac5e
• Dart version 3.12.0
• DevTools version 2.57.0
• 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) [978ms]
• Android SDK at /Users/dev/Library/Android/sdk
• Emulator version 36.5.11.0 (build_id 15261927) (CL:N/A)
• Platform android-36.1, 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.5) [694ms]
• Xcode at /Applications/Xcode.app/Contents/Developer
• Build 17F42
• CocoaPods version 1.16.2
[✓] Chrome - develop for the web [4ms]
• Chrome at /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[✓] Connected device (5 available) [5.9s]
• sdk gphone64 arm64 (mobile) • emulator-5554 • android-arm64 • Android 16 (API 36)
(emulator)
• Dev’s iPad (wireless) (mobile) • 00008020-000E3D682228402E • ios • iOS 26.5 23F77
• iPhone 17 Pro Max (mobile) • E44E154F-423F-4325-B615-4C4F9EB67251 • ios •
com.apple.CoreSimulator.SimRuntime.iOS-26-5 (simulator)
• macOS (desktop) • macos • darwin-arm64 • macOS 26.5.1 25F80
darwin-arm64
• Chrome (web) • chrome • web-javascript • Google Chrome
149.0.7827.104
[✓] Network resources [438ms]
• All expected network resources are available.
• No issues found!Edit: add go_router version
go_router version
version: 17.3.0
Metadata
Metadata
Assignees
Labels
P2Important issues not at the top of the work listImportant issues not at the top of the work listfound in release: 3.44Found to occur in 3.44Found to occur in 3.44has 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.team-androidOwned by Android platform teamOwned by Android platform teamtriaged-androidTriaged by Android platform teamTriaged by Android platform teamwaiting for PR to land (fixed)A fix is in flightA fix is in flight
Activity
danyalahmed1995 commentedon Jun 15, 2026
I was able to reproduce this on current
flutter/packages.While the active location stays
/B1, system/predictive back still pops inactive branch routes:So the issue appears to be inactive
StatefulShellBranchnavigators being included in system back handling.danyalahmed1995 commentedon Jun 15, 2026
Opened PR at flutter/packages#11910
The test covers the reproduced case where system/predictive back runs while branch B is active and verifies inactive A/C branch navigators are not popped.
saurabh-mirajkar commentedon Jun 16, 2026
@jasaw Thanks for the detailed report!
I was able to reproduce this behavior with the provided sample on the latest stable channel using an Android 16 emulator with predictive back enabled.
Based on the reproduction:
Labeling this for
team-androidand addingfyi-ecosystem, as this directly affects the first-partygo_routerpackage.test: guard predictive back with hidden tab stacks (flutter/flutter#1…