Repository navigation
[go_router] Stop inactive StatefulShellRoute branches from vetoing pops - #12972
auto-submit[bot] merged 9 commits into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces ActiveBranchScope, an InheritedWidget that tracks whether a subtree belongs to the currently active branch of a StatefulShellRoute. It updates _CustomNavigatorState and _BranchNavigatorProxy to utilize this scope, ensuring that only the active branch's PopScope can veto pops, which prevents inactive branches with active sub-routes from blocking system back navigation. Additionally, regression tests and a changelog entry are included. There are no review comments, so no further feedback is provided.
|
Thanks for the contribution! You haven’t completed all of the items in the PR checklist above, so I’m assuming this is a work in progress and am marking it as a Draft. Please review the checklist, updating the PR as appropriate, and when that’s complete please feel free to mark the PR as ready for review. |
The Navigator of every StatefulShellRoute branch is wrapped in a PopScope that keeps the shell itself from being popped, for example by an iOS back gesture, while the branch has an active sub-route. Inactive branches stay mounted, so their PopScope stays registered as a pop entry of the same enclosing route as the active one, and ModalRoute only allows a pop if every registered entry allows it. Switching away from a branch that still had a sub-route therefore suppressed the system back button for the whole app: popRoute() reported the pop as handled without popping anything, so Android never closed the activity. The guard now only applies to the active branch. StatefulNavigationShell publishes which of its branches is active through an ActiveBranchScope inherited widget, and the PopScope depends on it, so canPop is also kept up to date when the user switches branches without the route matches of a branch changing. Behavior is unchanged for a plain ShellRoute, which has no enclosing ActiveBranchScope. Fixes flutter/flutter#193098
8693ac6 to
01aa7fc
Compare
There was a problem hiding this comment.
Code Review
This pull request introduces ActiveBranchScope, an InheritedWidget that tracks whether a subtree belongs to the active branch of a StatefulShellRoute. This allows the PopScope of shell route navigators to only veto pops when their branch is active, resolving an issue where the system back button was ignored after switching branches. Regression tests and a changelog entry are also added. The review feedback recommends removing the generic type argument from the PopScope cast in the test file to prevent compilation errors on older Flutter versions.
7e79adf to
f01624e
Compare
|
Hi Flutter team, I’m following up on this PR. I have addressed the review feedback, updated the test accordingly, and synced the branch with main. The PR is currently waiting for the CICD label and tests to run. I would appreciate it if you could review the latest implementation whenever convenient. Thank you for your time. |
|
can you guide me @Piinks what is the issue in this PR so that i can make sure to fix it. |
|
This pull request is not mergeable in its current state, likely because of a merge conflict. Pre-submit CI jobs were not triggered. Pushing a new commit to this branch that resolves the issue will result in pre-submit jobs being scheduled. |
|
Hi @IbrarIlyas, please refrain from tagging maintainers directly. This will be reviewed shortly. |
Piinks
left a comment
There was a problem hiding this comment.
Can you also add this test in order to ensure coverage for preload: true?
Details
testWidgets(
'Preloaded branch (preload: true) with deep initialLocation does not veto pop while inactive, and guards shell once activated',
(WidgetTester tester) async {
final router = GoRouter(
initialLocation: '/home',
routes: <RouteBase>[
StatefulShellRoute.indexedStack(
builder:
(
BuildContext context,
GoRouterState state,
StatefulNavigationShell shell,
) => Scaffold(
body: shell,
bottomNavigationBar: Row(
children: <Widget>[
FilledButton(
key: const Key('toHome'),
onPressed: () => shell.goBranch(0),
child: const Text('Home'),
),
FilledButton(
key: const Key('toSettings'),
onPressed: () => shell.goBranch(1),
child: const Text('Settings'),
),
],
),
),
branches: <StatefulShellBranch>[
StatefulShellBranch(
routes: <RouteBase>[
GoRoute(
path: '/home',
builder: (BuildContext context, GoRouterState state) =>
const Scaffold(body: Text('Home Screen')),
),
],
),
StatefulShellBranch(
preload: true,
initialLocation: '/settings/general',
routes: <RouteBase>[
GoRoute(
path: '/settings',
builder: (BuildContext context, GoRouterState state) =>
const Scaffold(body: Text('Settings Screen')),
routes: <RouteBase>[
GoRoute(
path: 'general',
builder: (BuildContext context, GoRouterState state) =>
const Scaffold(body: Text('General Screen')),
),
],
),
],
),
],
),
],
);
addTearDown(router.dispose);
await tester.pumpWidget(MaterialApp.router(routerConfig: router));
await tester.pumpAndSettle();
expect(_branchCanPopValues(tester), <bool>[true, true]);
expect(await tester.binding.handlePopRoute(), isFalse);
await tester.tap(find.byKey(const Key('toSettings')));
await tester.pumpAndSettle();
expect(find.text('General Screen'), findsOneWidget);
expect(_branchCanPopValues(tester), <bool>[true, false]);
expect(await tester.binding.handlePopRoute(), isTrue);
await tester.pumpAndSettle();
expect(find.text('Settings Screen'), findsOneWidget);
expect(_branchCanPopValues(tester), <bool>[true, true]);
},
);- Annotate ActiveBranchScope with @internal. - Use a typed widgetList in the system back test helper. - Add a test covering a preloaded branch with a deep initialLocation.
c2011df to
cf4aaef
Compare
|
Thanks for the review! I've updated the PR:
|
|
Can you address the merge conflicts? |
…-branch-popscope # Conflicts: # packages/go_router/lib/src/builder.dart # packages/go_router/lib/src/route.dart
|
Resolved the merge conflicts with latest |
|
(Note: Updated the branch to get #13544, which should fix the failures). |
…er#196047) flutter/packages@670b30e...0478162 2026-11-05 mkmt@google.com [material_ui] Document RefreshIndicator mouse behavior on desktop and web (flutter/packages#13286) 2026-11-05 45147003+samar70@users.noreply.github.com [material_ui] Make SearchAnchor.bar respect SearchBarThemeData padding (flutter/packages#13360) 2026-11-04 kevmoo@users.noreply.github.com [pointer_interceptor] Use dart.library.js_interop in example export (flutter/packages#13558) 2026-11-04 louisehsu@google.com [in_app_purchase_storekit] Use purchase(confirmIn:) for StoreKit 2 purchases (flutter/packages#13410) 2026-11-04 41366638+kirya355@users.noreply.github.com [material_ui] Fix dismissal near minimum extent when shouldCloseOnMinExtent (flutter/packages#12545) 2026-11-04 36861262+QuncCccccc@users.noreply.github.com [material_ui] Add Material 3 Expressive `ElevatedButton` (flutter/packages#13084) 2026-11-04 108947675+IbrarIlyas@users.noreply.github.com [go_router] Stop inactive StatefulShellRoute branches from vetoing pops (flutter/packages#12972) 2026-11-04 engine-flutter-autoroll@skia.org Manual roll Flutter (stable) from abaf9c5 to c197ca5 (14 revisions) (flutter/packages#13542) 2026-11-04 stuart.morgan@gmail.com [in_app_purchase] Convert example app to Swift (flutter/packages#13556) 2026-11-04 fluttergithubbot@gmail.com Sync release-go_router-18.1.3 to main (flutter/packages#13559) 2026-11-04 fluttergithubbot@gmail.com Sync release-material_ui-1.10.0 to main (flutter/packages#13546) 2026-11-04 43054281+camsim99@users.noreply.github.com [camera_android_camerax] Add magic number guideline to `TESTING.md` (flutter/packages#13081) 2026-11-04 fluttergithubbot@gmail.com Sync release-cupertino_ui-1.1.6 to main (flutter/packages#13547) 2026-11-04 stuartmorgan@google.com [file_selector] Convert unit tests to Kotlin (flutter/packages#13102) 2026-11-04 engine-flutter-autoroll@skia.org Roll Flutter from 9347202 to 3623d2d (25 revisions) (flutter/packages#13555) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC flutter-ecosystem@google.com on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
The Navigator of every StatefulShellRoute branch is wrapped in a PopScope that
keeps the shell itself from being popped, for example by an iOS back gesture,
while the branch has an active sub-route.
Inactive branches stay mounted, so their PopScope stays registered as a pop
entry of the same enclosing route as the active one, and ModalRoute only allows
a pop if every registered entry allows it. Switching away from a branch that
still had a sub-route therefore suppressed the system back button for the whole
app: popRoute() reported the pop as handled without popping anything, so Android
never closed the activity.
The guard now only applies to the active branch. StatefulNavigationShell
publishes which of its branches is active through an ActiveBranchScope inherited
widget, and the PopScope depends on it, so canPop is also kept up to date when
the user switches branches without the route matches of a branch changing.
Behavior is unchanged for a plain ShellRoute, which has no enclosing
ActiveBranchScope.
Tests added to
test/stateful_shell_route_system_back_test.dart, next to theexisting issue 120353 gesture tests: the issue's repro, back still popping the
sub-route once the branch is active again, and a per-branch
canPopcheckconfirming the active branch still guards the shell. Also verified manually on
an Android 15 device.
Fixes flutter/flutter#193098
Replace this paragraph with a description of what this PR is changing or adding, and why. Consider including before/after screenshots.
List which issues are fixed by this PR. You must list at least one issue.
Pre-Review Checklist
[shared_preferences]///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.Footnotes
Regular contributors who have demonstrated familiarity with the repository guidelines only need to comment if the PR is not auto-exempted by repo tooling. ↩ ↩2