Repository navigation
[material_ui] Add enabled parameter to DropdownButtonFormField - #12610
Conversation
…n and DropdownButtonFormField
…for fallback behavior
# Conflicts: # packages/material_ui/lib/src/dropdown.dart
There was a problem hiding this comment.
Code Review
This pull request makes the onChanged parameter optional and introduces an enabled property to both DropdownButton and DropdownButtonFormField to control their interactivity, along with corresponding updates to documentation, migration fixes, and tests. Feedback on the changes highlights a critical issue in DropdownButtonFormField where providing enabled: true with a null onChanged callback prevents the form field's value from updating when an item is selected. To resolve this, it is recommended to pass state.didChange to the underlying DropdownButton whenever the form field is enabled, and to add a test case verifying this behavior.
| barrierDismissible: barrierDismissible, | ||
| mouseCursor: mouseCursor, | ||
| dropdownMenuItemMouseCursor: dropdownMenuItemMouseCursor, | ||
| enabled: isDropdownEnabled, |
There was a problem hiding this comment.
There is a critical bug here when DropdownButtonFormField is used with onChanged: null (or not provided) and enabled: true.\n\nIn the DropdownButtonFormField builder, the onChanged argument passed to DropdownButton is likely still defined as:\ndart\nonChanged: onChanged == null ? null : state.didChange,\n\nIf onChanged is null, this evaluates to null, so DropdownButton receives onChanged: null. Even though enabled: isDropdownEnabled (which is true) is passed to DropdownButton, making it interactive, selecting an item will do nothing because its onChanged callback is null. As a result, state.didChange is never called, and the form field's value is never updated.\n\nTo fix this, the onChanged argument passed to DropdownButton inside the builder should be updated to:\ndart\nonChanged: field.widget.enabled ? state.didChange : null,\n\nThis ensures that state.didChange is always called when the dropdown is enabled, which in turn updates the form field's value and safely calls the optional widget.onChanged callback if it is provided.
| ); | ||
| }).toList(), | ||
| alignment: buttonAlignment, | ||
| enabled: enabled, |
There was a problem hiding this comment.
Please add a test case to verify that DropdownButtonFormField works correctly when onChanged is null but enabled is true.\n\nSpecifically, the test should assert that:\n1. The dropdown is enabled and interactive.\n2. Selecting an item successfully updates the form field's value (i.e., formFieldKey.currentState.value changes to the selected item).\n\nThis will help prevent regressions and ensure that the new feature (making onChanged optional for read-only/disabled form field usage) works as intended.
|
[from triage] Could you help fix the conflict:)? |
# Conflicts: # packages/material_ui/lib/src/dropdown.dart
dkwingsmt
left a comment
There was a problem hiding this comment.
Additionally, I suggest adding a constructor assertion assert(enabled != true || onChanged != null), because in such cases the DropdownButton can be opened but any selection is dropped (since the button doesn't save its own values).
| this.isVerticallyExpanded = false, | ||
| required this._inputDecoration, | ||
| required this._isEmpty, | ||
| bool? enabled, |
There was a problem hiding this comment.
Why is this new parameter not used in the constructor's only invocation at L1966?
IMO DropdownButtonFormField should pass down enabled and set onChanged as isDropdownEnabled ? state.didChange : null.
| DropdownButtonFormField(onChanged: (String? v) {}); | ||
|
|
||
| // Changes made in https://github.com/flutter/flutter/pull/182419. | ||
| DropdownButton(onChanged: null, enabled: true); |
There was a problem hiding this comment.
These look like strange cases here because enabled did not even exist before this PR.
There was a problem hiding this comment.
In fact, at the current changes I suspect that this PR is no longer breaking and should not need a dartfix.
| // When adding new arguments, consider adding similar arguments to | ||
| // DropdownButtonFormField. | ||
| }) : assert( | ||
| }) : enabled = enabled ?? onChanged != null, |
There was a problem hiding this comment.
I suggest keeping enabled as bool?. While technically Dart allows constructor's arguments to be independent of the getters, it has been a convention of Flutter that they're identical and making them different like this will be confusing. Considering that getters of widgets are rarely used I think it's ok to just keep it the simple way.
| /// If this property is null, [WidgetStateMouseCursor.adaptiveClickable] will be used. | ||
| final MouseCursor? dropdownMenuItemMouseCursor; | ||
|
|
||
| /// Whether the [DropdownButton] is enabled. |
There was a problem hiding this comment.
This doc should also be updated to reflect the functionality of the bool? enabled parameter, not the calculated bool enabled effective value.
|
Update: after thinking about this more, this comment replaces my earlier inline comments about I think most of the issues here come from mixing up two layers that handle "enabled" differently. It might help to look at each one separately. Layer 1:
An
The issue (flutter/flutter#57953) also only asks about the form field. So I'd suggest reverting the Layer 2: The form field does store the value, in The form field can get there without any help from super(
// FormField.enabled also controls validation. Only an explicit
// `enabled: false` should turn validation off, so existing
// `onChanged: null` fields keep validating like they do today.
enabled: enabled ?? true,
builder: (FormFieldState<T> field) {
final bool isDropdownEnabled =
(enabled ?? onChanged != null) && items != null && items.isNotEmpty;
// ...
DropdownButton<T>._formField(
onChanged: isDropdownEnabled ? state.didChange : null,
// ...
);
},
);Compared to this, the current PR has three problems:
About the constructor parameter and the getter: What this means for the rest of the PR With
|
dkwingsmt
left a comment
There was a problem hiding this comment.
(Forgot to post my comments regarding the changelog)
Just a reminder that this hasn't been addressed. Otherwise the code LGTM. |
…638061467.yaml Co-authored-by: Tong Mu <[email protected]>
Co-authored-by: Qun Cheng <[email protected]>
Co-authored-by: Qun Cheng <[email protected]>
Roll Packages from a1ed23c to da40eaf (33 revisions) flutter/packages@a1ed23c...da40eaf 2026-10-15 [email protected] [pigeon]add support for sealed classes in native interop (flutter/packages#13175) 2026-10-15 [email protected] [pigeon] Replace full JNI bindings with stubs for API classes that aren't needed (flutter/packages#13173) 2026-10-14 [email protected] [material_ui] Fix self-comparison and assert bugs in semantics_tester (flutter/packages#12722) 2026-10-14 [email protected] [camera_avfoundation] Replace the deprecated 'videoOrientation' with 'videoRotationAngle' on iOS 17+ (flutter/packages#13247) 2026-10-14 [email protected] [material_ui] Fix GridTile crash when the incoming constraints are unbounded (flutter/packages#12586) 2026-10-14 [email protected] [in_app_purchase_storekit] Remove unused and deprecated "SKDownload" hosted content (flutter/packages#13222) 2026-10-14 [email protected] [material_ui] Add supportingTextPadding to InputDecorator (flutter/packages#12769) 2026-10-14 [email protected] [vector_graphics_codec] Add support for encoding and decoding paint blur commands (flutter/packages#13148) 2026-10-14 [email protected] [go_router] Fix redirect zone async error handling (flutter/packages#12836) 2026-10-14 [email protected] Sync release-go_router-18.1.0 to main (flutter/packages#13260) 2026-10-14 [email protected] Sync release-cupertino_ui-1.1.3 to main (flutter/packages#13258) 2026-10-14 [email protected] [material_ui] add enableInlinePrediction parameter to TextFormField (flutter/packages#12793) 2026-10-14 [email protected] Sync release-material_ui-1.7.0 to main (flutter/packages#13259) 2026-10-14 [email protected] [material_ui] Add the Material shapes. (flutter/packages#12771) 2026-10-14 [email protected] [material_ui] Add tertiary tap callbacks to InkResponse and InkWell (flutter/packages#12676) 2026-10-14 [email protected] Roll Flutter from 4249ccb to 426be96 (44 revisions) (flutter/packages#13256) 2026-10-14 [email protected] [material_ui] fixes expanded state in the a11y_assessment home page dropdown (flutter/packages#13219) 2026-10-14 [email protected] [VPAT] Fix semantics in a11y_assessments drawer, radio, and rail demos (flutter/packages#13159) 2026-10-14 [email protected] [google_maps_flutter] Add onPointOfInterestTap (flutter/packages#11872) 2026-10-14 [email protected] [go_router] Fix shell route key collisions and dynamic branch updates (flutter/packages#12549) 2026-10-14 [email protected] Manual roll Flutter from d03768e to 4249ccb (158 revisions) (flutter/packages#13220) 2026-10-14 [email protected] [image_picker_ios] Require Flutter 3.47 / iOS 15 and drop deprecated kUTType code paths (flutter/packages#13224) 2026-10-13 [email protected] [in_app_purchase_storekit] Fixes Storekit 2 flaky tests (flutter/packages#13213) 2026-10-13 [email protected] [local_auth] Convert Android to new Pigeon suspend support (flutter/packages#12894) 2026-10-13 [email protected] [material_ui] Add enabled parameter to DropdownButtonFormField (flutter/packages#12610) 2026-10-13 [email protected] [go_router] Link ShellRoute docs to StatefulShellRoute (flutter/packages#13120) 2026-10-13 [email protected] FadeForwards page transition performance (flutter/packages#13161) 2026-10-13 [email protected] [in_app_purchase_android] Guard async billing callbacks against duplicate listener responses (flutter/packages#12640) 2026-10-13 [email protected] [google_sign_in] Remove the Objective-C exception catcher (flutter/packages#13156) 2026-10-13 [email protected] [camera_avfoundation] Remove availability checks below the package minimum iOS 13 (flutter/packages#13214) 2026-10-13 [email protected] [google_maps_flutter_ios] Remove iOS 14 availability check from example UI tests (flutter/packages#13218) 2026-10-13 [email protected] [google_sign_in] Convert implementation to Kotlin (flutter/packages#13005) 2026-10-13 [email protected] [image_picker_ios] PR 2/5 Add a PHImageManager image-data seam and migrate plugin native tests to Swift Testing (flutter/packages#13079) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: ...
Roll Packages from a1ed23c to da40eaf (33 revisions) flutter/packages@a1ed23c...da40eaf 2026-10-15 [email protected] [pigeon]add support for sealed classes in native interop (flutter/packages#13175) 2026-10-15 [email protected] [pigeon] Replace full JNI bindings with stubs for API classes that aren't needed (flutter/packages#13173) 2026-10-14 [email protected] [material_ui] Fix self-comparison and assert bugs in semantics_tester (flutter/packages#12722) 2026-10-14 [email protected] [camera_avfoundation] Replace the deprecated 'videoOrientation' with 'videoRotationAngle' on iOS 17+ (flutter/packages#13247) 2026-10-14 [email protected] [material_ui] Fix GridTile crash when the incoming constraints are unbounded (flutter/packages#12586) 2026-10-14 [email protected] [in_app_purchase_storekit] Remove unused and deprecated "SKDownload" hosted content (flutter/packages#13222) 2026-10-14 [email protected] [material_ui] Add supportingTextPadding to InputDecorator (flutter/packages#12769) 2026-10-14 [email protected] [vector_graphics_codec] Add support for encoding and decoding paint blur commands (flutter/packages#13148) 2026-10-14 [email protected] [go_router] Fix redirect zone async error handling (flutter/packages#12836) 2026-10-14 [email protected] Sync release-go_router-18.1.0 to main (flutter/packages#13260) 2026-10-14 [email protected] Sync release-cupertino_ui-1.1.3 to main (flutter/packages#13258) 2026-10-14 [email protected] [material_ui] add enableInlinePrediction parameter to TextFormField (flutter/packages#12793) 2026-10-14 [email protected] Sync release-material_ui-1.7.0 to main (flutter/packages#13259) 2026-10-14 [email protected] [material_ui] Add the Material shapes. (flutter/packages#12771) 2026-10-14 [email protected] [material_ui] Add tertiary tap callbacks to InkResponse and InkWell (flutter/packages#12676) 2026-10-14 [email protected] Roll Flutter from 4249ccb to 426be96 (44 revisions) (flutter/packages#13256) 2026-10-14 [email protected] [material_ui] fixes expanded state in the a11y_assessment home page dropdown (flutter/packages#13219) 2026-10-14 [email protected] [VPAT] Fix semantics in a11y_assessments drawer, radio, and rail demos (flutter/packages#13159) 2026-10-14 [email protected] [google_maps_flutter] Add onPointOfInterestTap (flutter/packages#11872) 2026-10-14 [email protected] [go_router] Fix shell route key collisions and dynamic branch updates (flutter/packages#12549) 2026-10-14 [email protected] Manual roll Flutter from d03768e to 4249ccb (158 revisions) (flutter/packages#13220) 2026-10-14 [email protected] [image_picker_ios] Require Flutter 3.47 / iOS 15 and drop deprecated kUTType code paths (flutter/packages#13224) 2026-10-13 [email protected] [in_app_purchase_storekit] Fixes Storekit 2 flaky tests (flutter/packages#13213) 2026-10-13 [email protected] [local_auth] Convert Android to new Pigeon suspend support (flutter/packages#12894) 2026-10-13 [email protected] [material_ui] Add enabled parameter to DropdownButtonFormField (flutter/packages#12610) 2026-10-13 [email protected] [go_router] Link ShellRoute docs to StatefulShellRoute (flutter/packages#13120) 2026-10-13 [email protected] FadeForwards page transition performance (flutter/packages#13161) 2026-10-13 [email protected] [in_app_purchase_android] Guard async billing callbacks against duplicate listener responses (flutter/packages#12640) 2026-10-13 [email protected] [google_sign_in] Remove the Objective-C exception catcher (flutter/packages#13156) 2026-10-13 [email protected] [camera_avfoundation] Remove availability checks below the package minimum iOS 13 (flutter/packages#13214) 2026-10-13 [email protected] [google_maps_flutter_ios] Remove iOS 14 availability check from example UI tests (flutter/packages#13218) 2026-10-13 [email protected] [google_sign_in] Convert implementation to Kotlin (flutter/packages#13005) 2026-10-13 [email protected] [image_picker_ios] PR 2/5 Add a PHImageManager image-data seam and migrate plugin native tests to Swift Testing (flutter/packages#13079) If this roll has caused a breakage, revert this CL and set the roller to dry run mode using the controls here: https://autoroll.skia.org/r/flutter-packages-flutter-autoroll Please CC [email protected] on the revert to ensure that a human is aware of the problem. To file a bug in Flutter: https://github.com/flutter/flutter/issues/new/choose To report a problem with the AutoRoller itself, please file a bug: https://issues.skia.org/issues/new?component=1389291&template=1850622 Documentation for the AutoRoller is here: ...
Introduced the
enabledparameter inDropdownButtonFormField, allowing proper disabled/read-only usage without needing to pass dummy callbacks or remove existing ones.Previously, the only way to disable a
DropdownButtonFormFieldwas to setonChanged: null. With this change, developers can explicitly control the interactive state via theenabledproperty. For example, you can setenabled: falsewhile keepingonChangeddefined, or setenabled: trueto have an interactive field even ifonChangedis omitted.This change is fully backward compatible. If the
enabledproperty is omitted (left asnull), the field will fall back to the legacy behavior: it will be enabled if and only ifonChangedis non-null.Closes flutter/flutter#57953.
Port of flutter/flutter#182419.
Pre-Review Checklist
[shared_preferences]///).If you need help, consider asking for advice on the #hackers-new channel on Discord.
Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the
gemini-code-assistbot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.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