Skip to content

[go_router] Fixes StatefulShellRoute PopScope behavior to respect back button#11432

Draft
chunhtai wants to merge 1 commit intoflutter:mainfrom
chunhtai:fix-pop-scope-stateful-shell
Draft

[go_router] Fixes StatefulShellRoute PopScope behavior to respect back button#11432
chunhtai wants to merge 1 commit intoflutter:mainfrom
chunhtai:fix-pop-scope-stateful-shell

Conversation

@chunhtai
Copy link
Copy Markdown
Contributor

@chunhtai chunhtai commented Apr 3, 2026

Fixes issue #181945.

Root Cause

StatefulShellRoute manages multiple branches, each with its own parallel navigation stack. When the system back button is pressed on a non-root branch (index > 0), the framework does not inherently know that it should oscillate back to the root branch (index 0) when the current branch's navigator cannot pop further. This caused system back events to exit the app or behave inconsistently.

Fix

This PR adds a PopScope wrapper around the StatefulNavigationShell container:

  1. Conditional Blocking: It sets canPop: widget.currentIndex == 0. This allows natural app exiting only from the root branch.
  2. Branch Oscillation: When a pop is requested on a non-root branch, it is blocked, and the onPopInvokedWithResult handler calls goBranch(0) to switch back to the main tab.
  3. Compatibility: Inner PopScope widgets placed within specific branches will still function for pops within their respective nested navigators.

@github-actions github-actions bot added p: go_router triage-framework Should be looked at in framework triage labels Apr 3, 2026
gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

@chunhtai chunhtai force-pushed the fix-pop-scope-stateful-shell branch from 0135340 to 293106d Compare April 3, 2026 22:55
@chunhtai chunhtai changed the title Fix StatefulShellRoute PopScope behavior to respect back button [go_router] Fixes StatefulShellRoute PopScope behavior to respect back button Apr 3, 2026
@chunhtai
Copy link
Copy Markdown
Contributor Author

chunhtai commented Apr 3, 2026

@gemini-code-assist review

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a PopScope to StatefulNavigationShellState to handle back button navigation by switching to the root branch when the current branch cannot pop. A review comment identifies a potential issue where this logic could interfere with nested PopScope widgets and suggests checking the navigator's state before switching branches.

Comment on lines +1572 to +1580
return PopScope(
canPop: widget.currentIndex == 0,
onPopInvokedWithResult: (bool didPop, Object? result) {
if (!didPop && widget.currentIndex != 0) {
goBranch(0);
}
},
child: widget.containerBuilder(context, widget, children),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation of PopScope might interfere with nested PopScope widgets within a branch.

If a child route on a non-root branch (e.g., Tab B) uses a PopScope to block a pop (for instance, to show a "Discard changes?" confirmation dialog), the didPop value in this shell-level PopScope will be false. Consequently, goBranch(0) will be triggered, switching the user to the root branch even though the intention was to stay on the current branch and handle the pop locally.

To fix this, you should check if the current branch's navigator can actually pop before deciding to switch branches. If navigator.canPop() is true but didPop is false, it implies a nested PopScope blocked the pop, and the shell should not intervene.

    return PopScope(
      canPop: widget.currentIndex == 0,
      onPopInvokedWithResult: (bool didPop, Object? result) {
        if (!didPop && widget.currentIndex != 0) {
          final NavigatorState? navigator =
              widget.shellRouteContext.navigatorKey.currentState;
          // Only switch to the root branch if the current branch navigator
          // cannot pop further. If it can pop but didn't, a nested PopScope
          // likely blocked it.
          if (navigator != null && !navigator.canPop()) {
            goBranch(0);
          }
        }
      },
      child: widget.containerBuilder(context, widget, children),
    );

@chunhtai chunhtai marked this pull request as draft April 3, 2026 23:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

p: go_router triage-framework Should be looked at in framework triage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant