[go_router] Fix chained top-level redirects not being fully resolved (#11108)

Fixes https://github.com/flutter/flutter/issues/178984

The `onEnter` refactor (commit `9ec29b6d23`, PR #8339) split the unified `redirect()` method into `applyTopLegacyRedirect()` (top-level, runs once) and `redirect()` (route-level only). This introduced two regressions:

1. **Top-level redirect chains broken**: `applyTopLegacyRedirect()` returned after one hop — a chain like `/ → /a → /b` stopped at `/a` instead of resolving to `/b`.
2. **Route-level → top-level chains broken**: `processRouteLevelRedirect()` recursed into `redirect()` without re-evaluating the top-level redirect on the new location.

### Fix

Unify top-level and route-level redirects back into `redirect()` as the single entry point for all redirect processing, while keeping the separation of concerns via `applyTopLegacyRedirect()` as an internal helper.

> **Note:** An earlier revision of this PR (v1) kept the split but added recursive calls in both methods. Based on [reviewer feedback](https://github.com/flutter/packages/pull/11108#discussion_r2943354003), the approach was refactored to move `applyTopLegacyRedirect()` inside `redirect()` so the caller (`parseRouteInformationWithDependencies`) doesn't need to know about the two-phase process.

#### Before (broken)

```mermaid
sequenceDiagram
    participant Parser as parseRouteInformationWithDependencies
    participant TopRedir as applyTopLegacyRedirect
    participant Redir as redirect (route-level only)

    Parser->>TopRedir: / (one hop only)
    TopRedir-->>Parser: /a (stops here ✗)
    Parser->>Redir: /a
    Redir->>Redir: route-level redirects
    Note right of Redir: No top-level re-evaluation
    Redir-->>Parser: result
```

#### After (fix)

```mermaid
sequenceDiagram
    participant Parser as parseRouteInformationWithDependencies
    participant Redir as redirect (unified)
    participant TopRedir as applyTopLegacyRedirect

    Parser->>Redir: / (initial matches)
    Redir->>TopRedir: / → top-level redirect chain
    TopRedir->>TopRedir: / → /a → /b (self-recursive)
    TopRedir-->>Redir: /b (fully resolved ✓)
    Redir->>Redir: route-level redirects on /b
    Note right of Redir: If route-level changes location,<br/>recurse → top-level re-evaluated
    Redir-->>Parser: final result
```

#### Key changes

- **`configuration.dart` — `redirect()`**: Now calls `applyTopLegacyRedirect()` first at every cycle, then processes route-level redirects on the post-top-level result. Route-level `_processRouteLevelRedirects` extracted as a helper.
- **`configuration.dart` — `applyTopLegacyRedirect()`**: Self-recursive to fully resolve top-level chains. No functional change from v1.
- **`parser.dart` — `parseRouteInformationWithDependencies()`**: Simplified — no longer calls `applyTopLegacyRedirect` separately. Just passes initial matches to `_navigate()`.
- **`parser.dart` — `_navigate()`**: Removed `preSharedHistory` parameter. Added `context.mounted` guard in the result `.then()` to protect the relocated async boundary.

Both fixes share the existing `redirectHistory` for loop detection and respect `redirectLimit`. The `onEnter` system is completely unaffected — it runs before redirects in the pipeline.

### Tests

- **19 redirect chain tests** (`redirect_chain_test.dart`): top-level chains, async chains, loop detection (including loop-to-initial), route→top cross-type chains, **async cross-type chains** (async top→route, async route→sync top, sync route→async top), **context disposal** during async top-level and route-level redirects, redirect limit boundary (exact limit succeeds, limit+1 fails), shared limit across redirect types.
- **3 onEnter interaction tests** (`on_enter_test.dart`): onEnter called once when chains resolve, onEnter block prevents redirect evaluation.
- **Full suite**: 418 tests pass, 0 regressions.

## Pre-Review Checklist

[^1]: This PR uses `pending_changelogs/` for versioning and changelog, following the go_router batch release process.
5 files changed
tree: 07898dd3b4af338d149140196f81e52985728159
  1. .agents/
  2. .ci/
  3. .claude/
  4. .gemini/
  5. .github/
  6. agent-artifacts/
  7. packages/
  8. script/
  9. third_party/
  10. .ci.yaml
  11. .clang-format
  12. .direnv
  13. .gitattributes
  14. .gitignore
  15. .metadata
  16. AGENTS.md
  17. analysis_options.yaml
  18. AUTHORS
  19. CONTRIBUTING.md
  20. customer_testing.bat
  21. customer_testing.sh
  22. LICENSE
  23. README.md
  24. SUGGESTED_REVIEWERS.md
README.md

Flutter Packages

Release Status Flutter CI Status

This repo is a companion repo to the main flutter repo. It contains the source code for Flutter's first-party packages (i.e., packages developed by the core Flutter team). Check the packages directory to see all packages.

These packages are also available on pub.

Issues

Please file any issues, bugs, or feature requests in the main flutter repo. Issues pertaining to this repository are labeled “package”.

Contributing

If you wish to contribute a new package to the Flutter ecosystem, please see the documentation for developing packages. You can store your package source code in any GitHub repository (the present repo is only intended for packages developed by the core Flutter team). Once your package is ready you can publish to the pub repository.

If you wish to contribute a change to any of the existing packages in this repo, please review our contribution guide, and send a pull request.

Packages

These are the packages hosted in this repository:

PackagePubPointsUsageIssuesPull requests
animationspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
camerapub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
cross_filepub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
cupertino_iconspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
cupertino_uipub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
espressopub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
extension_google_sign_in_as_googleapis_authpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
file_selectorpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
flutter_lintspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
flutter_plugin_android_lifecyclepub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
flutter_svgpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
flutter_svg_testpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
flutter_template_imagespub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
go_routerpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
go_router_builderpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
google_adsensepub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
google_fontspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
google_maps_flutterpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
google_sign_inpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
image_pickerpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
interactive_media_adspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
in_app_purchasepub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
local_authpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
material_uipub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
metrics_centerpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
multicast_dnspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
path_parsingpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
path_providerpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
pigeonpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
pointer_interceptorpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
plugin_platform_interfacepub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
quick_actionspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
google_identity_services_webpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
mustache_templatepub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
rfwpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
shared_preferencespub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
standard_message_codecpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
two_dimensional_scrollablespub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
url_launcherpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
vector_graphicspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
vector_graphics_codecpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
vector_graphics_compilerpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
video_playerpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
web_benchmarkspub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
webview_flutterpub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label
xdg_directoriespub packagepub pointsdownloadsGitHub issues by-labelGitHub pull requests by-label