Fix DropdownButton crash on orientation change while menu is open - #187366
Fix DropdownButton crash on orientation change while menu is open#187366ishivamg wants to merge 2 commits into
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 defers the dismissal of an open dropdown menu during an orientation change by using a post-frame callback, preventing Navigator mutations during the build phase. It also adds a regression test to verify that a dropdown menu opened inside a dialog is dismissed without throwing an exception when the orientation changes. There are no review comments, and I have no feedback to provide.
When a DropdownButton's menu is open and the device orientation changes, _DropdownButtonState.build() dismissed the open route synchronously by calling _removeDropdownRoute() -> Navigator.removeRoute(). Mutating the Navigator during build triggers a "setState() or markNeedsBuild() called during build" assertion (surfaced via the route's animation listeners), which is especially reproducible when the dropdown lives inside a dialog. Defer the route dismissal to a post-frame callback so the Navigator is mutated after the current frame, preserving the existing behavior of closing the menu on orientation change without throwing. Fixes flutter#171011
1a5f765 to
14e4430
Compare
|
The This change is a crash fix for #171011: opening a Since I can't apply the |
navaronbracke
left a comment
There was a problem hiding this comment.
Just one remark about the post frame callback
| // current frame. | ||
| // See https://github.com/flutter/flutter/issues/171011 | ||
| if (_dropdownRoute != null) { | ||
| WidgetsBinding.instance.addPostFrameCallback((Duration timeStamp) { |
There was a problem hiding this comment.
I do recall that there's a little trick to check what scheduler phase we are in, before posting a post frame callback: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/raw_menu_anchor.dart#L565-L578
Per the process for decoupling, I would mark this PR as a draft (so we can cross reference it), and then open a new PR in material_ui once contributions there are allowed. |
Apply the scheduler-phase check (matching raw_menu_anchor.dart) so the dropdown route is removed synchronously unless we are mid-build, in which case the removal is deferred to a post-frame callback. Addresses review feedback on flutter#187366 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Thanks for the review @navaronbracke ! I've applied the scheduler-phase check — the dropdown route is now removed synchronously unless we're in SchedulerPhase.persistentCallbacks (mid-build), in which case the removal is deferred to a post-frame callback, matching the raw_menu_anchor.dart pattern. flutter test packages/flutter/test/material/dropdown_test.dart passes, including the #171011 regression test. Per your suggestion, I'm marking this PR as a draft for cross-reference, and I'll port the change to material_ui in flutter/packages once contributions there are open. Thanks! |
|
I've marked this PR as not ready to port to flutter/packages yet. |
|
This pull request contains changes to Material or Cupertino, which are currently frozen in this repository. Changes should be made in Please refer to #188444 for instructions. |
|
This PR is now ready to port over to flutter/packages! 🥳 |
|
Ported to flutter/packages: flutter/packages#12821 |
Description
When a
DropdownButton's menu is open and the device orientation changes, the framework throws:_DropdownButtonState.build()detects the orientation change and dismisses the open menu by calling_removeDropdownRoute()→_DropdownRoute._dismiss()→Navigator.removeRoute(). Mutating theNavigatorsynchronously during build marks route-transition listeners (ListenableBuilder) dirty mid-build, which trips the assertion. It is especially reproducible when the dropdown is hosted inside a dialog (the dialog route's transition builder is merged with the dropdown's secondary animation), as in the linked issue.Fix
Defer the dismissal to a post-frame callback so the
Navigatoris mutated after the current frame completes. This preserves the existing behavior — the menu still closes on orientation change — but no longer mutates theNavigatorduring build. Amountedguard avoids touching a disposedState, and the callback is only scheduled when a route is actually open.Tests
Dropdown menu opened inside a dialog is dismissed on orientation change without throwing) that opens aDropdownButtonmenu inside anAlertDialog, rotates the view, and asserts no exception is thrown and the menu is dismissed. It fails onmasterand passes with this change.Dropdown menus are dismissed on screen orientation changes, but not on keyboard hidecontinues to pass unmodified.Related Issues
Fixes #171011
Pre-launch Checklist
///).