Skip to content

Commit 1a8e57f

Browse files
authored
Use decoration hint text as the default value for dropdown button hints (flutter#152474)
## Description This PR makes `DropdownButtonFormField` hint defaults to the provided inputDecoration hintText. Because `DropDownButtonFormField` accepts both a `hint` parameter and a `decoration`parameter, one can expect `InputDecoration.hintText` to be valid. Before this PR, when `InputDecoration.hintText` was specified, it is shown but the vertical position is wrong. After this PR, when `InputDecoration.hintText` is specified, it is used as the default value for `DropDownButtonFormField.hint` and `DropDownButtonFormField.disabledHint`. | Before | After | |--------|--------| | ![image](https://github.com/user-attachments/assets/a08ff75c-edd4-4e16-9cfa-98ddb349d860) | ![image](https://github.com/user-attachments/assets/55f08bee-8f88-4125-8fae-68e2be724955) | ## Related Issue Fixes flutter#111958. ## Tests Adds 5 tests.
1 parent 0c6b600 commit 1a8e57f

File tree

2 files changed

+199
-131
lines changed

2 files changed

+199
-131
lines changed

packages/flutter/lib/src/material/dropdown.dart

Lines changed: 77 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,45 +1671,43 @@ class DropdownButtonFormField<T> extends FormField<T> {
16711671
EdgeInsetsGeometry? padding,
16721672
// When adding new arguments, consider adding similar arguments to
16731673
// DropdownButton.
1674-
}) : assert(items == null || items.isEmpty || value == null ||
1675-
items.where((DropdownMenuItem<T> item) {
1676-
return item.value == value;
1677-
}).length == 1,
1678-
"There should be exactly one item with [DropdownButton]'s value: "
1679-
'$value. \n'
1680-
'Either zero or 2 or more [DropdownMenuItem]s were detected '
1681-
'with the same value',
1682-
),
1683-
assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
1684-
decoration = decoration ?? InputDecoration(focusColor: focusColor),
1685-
super(
1686-
initialValue: value,
1687-
autovalidateMode: autovalidateMode ?? AutovalidateMode.disabled,
1688-
builder: (FormFieldState<T> field) {
1689-
final _DropdownButtonFormFieldState<T> state = field as _DropdownButtonFormFieldState<T>;
1690-
final InputDecoration decorationArg = decoration ?? InputDecoration(focusColor: focusColor);
1691-
final InputDecoration effectiveDecoration = decorationArg.applyDefaults(
1692-
Theme.of(field.context).inputDecorationTheme,
1693-
);
1694-
1695-
final bool showSelectedItem = items != null && items.where((DropdownMenuItem<T> item) => item.value == state.value).isNotEmpty;
1696-
bool isHintOrDisabledHintAvailable() {
1697-
final bool isDropdownDisabled = onChanged == null || (items == null || items.isEmpty);
1698-
if (isDropdownDisabled) {
1699-
return hint != null || disabledHint != null;
1700-
} else {
1701-
return hint != null;
1702-
}
1703-
}
1704-
final bool isEmpty = !showSelectedItem && !isHintOrDisabledHintAvailable();
1705-
final bool hasError = effectiveDecoration.errorText != null;
1706-
1707-
// An unfocusable Focus widget so that this widget can detect if its
1708-
// descendants have focus or not.
1709-
return Focus(
1710-
canRequestFocus: false,
1711-
skipTraversal: true,
1712-
child: Builder(builder: (BuildContext context) {
1674+
}) : assert(items == null || items.isEmpty || value == null ||
1675+
items.where((DropdownMenuItem<T> item) => item.value == value).length == 1,
1676+
"There should be exactly one item with [DropdownButton]'s value: "
1677+
'$value. \n'
1678+
'Either zero or 2 or more [DropdownMenuItem]s were detected '
1679+
'with the same value',
1680+
),
1681+
assert(itemHeight == null || itemHeight >= kMinInteractiveDimension),
1682+
decoration = decoration ?? InputDecoration(focusColor: focusColor),
1683+
super(
1684+
initialValue: value,
1685+
autovalidateMode: autovalidateMode ?? AutovalidateMode.disabled,
1686+
builder: (FormFieldState<T> field) {
1687+
final _DropdownButtonFormFieldState<T> state = field as _DropdownButtonFormFieldState<T>;
1688+
final InputDecoration decorationArg = decoration ?? InputDecoration(focusColor: focusColor);
1689+
final InputDecoration effectiveDecoration = decorationArg.applyDefaults(
1690+
Theme.of(field.context).inputDecorationTheme,
1691+
);
1692+
1693+
final bool showSelectedItem = items != null && items.where((DropdownMenuItem<T> item) => item.value == state.value).isNotEmpty;
1694+
final bool isDropdownEnabled = onChanged != null && items != null && items.isNotEmpty;
1695+
// If decoration hintText is provided, use it as the default value for both hint and disabledHint.
1696+
final Widget? decorationHint = effectiveDecoration.hintText != null ? Text(effectiveDecoration.hintText!) : null;
1697+
final Widget? effectiveHint = hint ?? decorationHint;
1698+
final Widget? effectiveDisabledHint = disabledHint ?? effectiveHint;
1699+
final bool isHintOrDisabledHintAvailable = isDropdownEnabled
1700+
? effectiveHint != null
1701+
: effectiveHint != null || effectiveDisabledHint != null;
1702+
final bool isEmpty = !showSelectedItem && !isHintOrDisabledHintAvailable;
1703+
final bool hasError = effectiveDecoration.errorText != null;
1704+
1705+
// An unfocusable Focus widget so that this widget can detect if its
1706+
// descendants have focus or not.
1707+
return Focus(
1708+
canRequestFocus: false,
1709+
skipTraversal: true,
1710+
child: Builder(builder: (BuildContext context) {
17131711
final bool isFocused = Focus.of(context).hasFocus;
17141712
InputBorder? resolveInputBorder() {
17151713
if (hasError) {
@@ -1737,42 +1735,46 @@ class DropdownButtonFormField<T> extends FormField<T> {
17371735
return null;
17381736
}
17391737

1740-
return DropdownButtonHideUnderline(
1741-
child: DropdownButton<T>._formField(
1742-
items: items,
1743-
selectedItemBuilder: selectedItemBuilder,
1744-
value: state.value,
1745-
hint: hint,
1746-
disabledHint: disabledHint,
1747-
onChanged: onChanged == null ? null : state.didChange,
1748-
onTap: onTap,
1749-
elevation: elevation,
1750-
style: style,
1751-
icon: icon,
1752-
iconDisabledColor: iconDisabledColor,
1753-
iconEnabledColor: iconEnabledColor,
1754-
iconSize: iconSize,
1755-
isDense: isDense,
1756-
isExpanded: isExpanded,
1757-
itemHeight: itemHeight,
1758-
focusColor: focusColor,
1759-
focusNode: focusNode,
1760-
autofocus: autofocus,
1761-
dropdownColor: dropdownColor,
1762-
menuMaxHeight: menuMaxHeight,
1763-
enableFeedback: enableFeedback,
1764-
alignment: alignment,
1765-
borderRadius: borderRadius ?? effectiveBorderRadius(),
1766-
inputDecoration: effectiveDecoration.copyWith(errorText: field.errorText),
1767-
isEmpty: isEmpty,
1768-
isFocused: isFocused,
1769-
padding: padding,
1770-
),
1771-
);
1772-
}),
1773-
);
1774-
},
1775-
);
1738+
return DropdownButtonHideUnderline(
1739+
child: DropdownButton<T>._formField(
1740+
items: items,
1741+
selectedItemBuilder: selectedItemBuilder,
1742+
value: state.value,
1743+
hint: effectiveHint,
1744+
disabledHint: effectiveDisabledHint,
1745+
onChanged: onChanged == null ? null : state.didChange,
1746+
onTap: onTap,
1747+
elevation: elevation,
1748+
style: style,
1749+
icon: icon,
1750+
iconDisabledColor: iconDisabledColor,
1751+
iconEnabledColor: iconEnabledColor,
1752+
iconSize: iconSize,
1753+
isDense: isDense,
1754+
isExpanded: isExpanded,
1755+
itemHeight: itemHeight,
1756+
focusColor: focusColor,
1757+
focusNode: focusNode,
1758+
autofocus: autofocus,
1759+
dropdownColor: dropdownColor,
1760+
menuMaxHeight: menuMaxHeight,
1761+
enableFeedback: enableFeedback,
1762+
alignment: alignment,
1763+
borderRadius: borderRadius ?? effectiveBorderRadius(),
1764+
// Clear the decoration hintText because DropdownButton has its own hint logic.
1765+
inputDecoration: effectiveDecoration.copyWith(
1766+
errorText: field.errorText,
1767+
hintText: effectiveDecoration.hintText != null ? '' : null,
1768+
),
1769+
isEmpty: isEmpty,
1770+
isFocused: isFocused,
1771+
padding: padding,
1772+
),
1773+
);
1774+
}),
1775+
);
1776+
},
1777+
);
17761778

17771779
/// {@macro flutter.material.dropdownButton.onChanged}
17781780
final ValueChanged<T?>? onChanged;

0 commit comments

Comments
 (0)