[go_router] Fixes "Bad state: Future already completed" when a route with onExit is popped twice - #12500
Conversation
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
There was a problem hiding this comment.
Code Review
This pull request updates the go_router package to version 17.5.1. It addresses a StateError by ensuring that ImperativeRouteMatch.complete returns early if its underlying completer is already completed, preventing multiple completions when a route is popped twice before the navigator rebuilds. A corresponding regression test has been added to on_exit_test.dart, and the CHANGELOG.md has been updated. There are no review comments, so no additional feedback is provided.
2cc1e89 to
7cecaf4
Compare
|
@googlebot I signed it! |
…th onExit is popped twice
7cecaf4 to
5276a8a
Compare
When a route with an
onExitcallback is popped,GoRouterDelegate._handlePopPageWithRouteMatchvetoes the synchronous pop (returnsfalse) and defers completion to ascheduleMicrotask. Because the page is still on the navigator until the configuration change is applied, a second back event arriving in that window (e.g. a user tapping the back button twice in quick succession) reaches the delegate with the sameRouteMatchBaseand schedules a second microtask. Both microtasks call_completeRouteMatch→ImperativeRouteMatch.complete()on the same completer, and the second call throws an unhandledStateError: Bad state: Future already completed.This affects far more apps than explicit
onExitusers: any app using go_router_builder typed routes generated before thehasOverriddenOnExitparameter existed gets an implicitonExitwired byGoRouteData.$route(it treats a nullhasOverriddenOnExitastruefor backward compatibility), so every typed route takes the deferred pop path. We hit this in production from users double-tapping the AppBar back button.This PR makes
ImperativeRouteMatch.complete()ignore completions after the first, so the first pop's result is reported and the redundant second pop becomes a no-op (RouteMatchList.removealready returns the list unchanged when the match is gone). A regression test reproducing the double-pop is included; it fails with aStateErrorwithout the fix.Fixes flutter/flutter#191280
Pre-Review Checklist
[shared_preferences]///).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