[go_router] Fix chained top-level redirects not being fully resolved#11108
[go_router] Fix chained top-level redirects not being fully resolved#11108davidmigloz wants to merge 2 commits intoflutter:mainfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request addresses a regression where chained top-level redirects were not fully resolved and route-level redirects failed to trigger re-evaluation of top-level redirects. The changes modify the redirect and applyTopLegacyRedirect methods in RouteConfiguration to recursively re-evaluate top-level redirects when a new location is produced, ensuring all hops in a redirect chain are resolved and that route-level redirects correctly trigger top-level re-evaluation. The update also clarifies the documentation for applyTopLegacyRedirect regarding recursive evaluation, loop detection, and redirect limits. New tests were added to validate various chained redirect scenarios, including async redirects, loop detection, redirect limit enforcement, and interactions with onEnter hooks. The reviewer specifically noted that the addition of the isError check and the recursive call to applyTopLegacyRedirect correctly implements the fix, ensuring the router's correct functioning.
|
I remembered this behavior was intentional to preserve previous behavior before onEnter is introduced will take a closer look |
|
Hey @davidmigloz, thanks for the detailed write-up and for putting in the work on this fix. For context, as @chunhtai mentioned, the single-pass behavior in From the user side though, you are right - this is a regression. Chained top-level redirects worked before v16 and stopped after. Even if the internal change was intentional, this is still a behavior break without a deprecation path. Your fix looks right to me. I support landing this. Long term, |
|
@davidmigloz thank you for fixing the regression. when you have some time could you look into the review, v16+ is breaking redirection for us as well |
|
Yes! Sorry, it’s still on my TODO list. I need to find some time to do it. |
Address reviewer feedback by moving applyTopLegacyRedirect() inside redirect() so it's the single entry point for all redirect processing. - redirect() now calls applyTopLegacyRedirect() first, then route-level redirects, recursing naturally when either changes the location - Simplified parseRouteInformationWithDependencies — no longer calls applyTopLegacyRedirect separately - Removed preSharedHistory parameter from _navigate - Added context.mounted guard in _navigate result handler to protect the relocated async boundary - Added async cross-type redirect tests and context disposal tests
0f5f88f to
3a021e0
Compare
|
@chunhtai feedback addressed! |
Fixes flutter/flutter#178984
The
onEnterrefactor (commit9ec29b6d23, PR #8339) split the unifiedredirect()method intoapplyTopLegacyRedirect()(top-level, runs once) andredirect()(route-level only). This introduced two regressions:applyTopLegacyRedirect()returned after one hop — a chain like/ → /a → /bstopped at/ainstead of resolving to/b.processRouteLevelRedirect()recursed intoredirect()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 viaapplyTopLegacyRedirect()as an internal helper.Before (broken)
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: resultAfter (fix)
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 resultKey changes
configuration.dart—redirect(): Now callsapplyTopLegacyRedirect()first at every cycle, then processes route-level redirects on the post-top-level result. Route-level_processRouteLevelRedirectsextracted 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 callsapplyTopLegacyRedirectseparately. Just passes initial matches to_navigate().parser.dart—_navigate(): RemovedpreSharedHistoryparameter. Addedcontext.mountedguard in the result.then()to protect the relocated async boundary.Both fixes share the existing
redirectHistoryfor loop detection and respectredirectLimit. TheonEntersystem is completely unaffected — it runs before redirects in the pipeline.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.on_enter_test.dart): onEnter called once when chains resolve, onEnter block prevents redirect evaluation.Pre-Review Checklist
[shared_preferences]pubspec.yamlwith an appropriate new version according to the pub versioning philosophy, or I have commented below to indicate which version change exemption this PR falls under1.CHANGELOG.mdto add a description of the change, following repository CHANGELOG style, or I have commented below to indicate which CHANGELOG exemption this PR falls under1.///).Footnotes
This PR uses
pending_changelogs/for versioning and changelog, following the go_router batch release process. ↩ ↩2 ↩3