Skip to content

Commit 6ae2795

Browse files
authored
Fix DropdownMenuFormField does not clear text field content on reset … (flutter#174937)
## Description This PR fixes `DropdownMenuFormField` not clearing the text field value when the form is reset and DropdownMenuFormField.initialSelection is null. ## Related Issue Fixes [DropdownMenuFormField does not reset when calling FormState.reset](flutter#174578) ## Tests Adds 2 tests.
1 parent 937f2b9 commit 6ae2795

File tree

2 files changed

+106
-5
lines changed

2 files changed

+106
-5
lines changed

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ class DropdownMenuFormField<T> extends FormField<T> {
9696
textAlign: textAlign,
9797
inputDecorationTheme: inputDecorationTheme,
9898
menuStyle: menuStyle,
99-
controller: controller,
99+
controller: state.textFieldController,
100100
initialSelection: state.value,
101101
onSelected: field.didChange,
102102
focusNode: focusNode,
@@ -139,8 +139,14 @@ class DropdownMenuFormField<T> extends FormField<T> {
139139
class _DropdownMenuFormFieldState<T> extends FormFieldState<T> {
140140
DropdownMenuFormField<T> get _dropdownMenuFormField => widget as DropdownMenuFormField<T>;
141141

142+
// The controller used to restore the selected item.
142143
RestorableTextEditingController? _restorableController;
143144

145+
// The controller used to reset the content of the DropdownMenu inner TextField.
146+
TextEditingController? _localTextFieldController;
147+
TextEditingController get textFieldController =>
148+
_dropdownMenuFormField.controller ?? (_localTextFieldController ??= TextEditingController());
149+
144150
@override
145151
void initState() {
146152
super.initState();
@@ -163,11 +169,16 @@ class _DropdownMenuFormFieldState<T> extends FormFieldState<T> {
163169
if (oldWidget.initialValue != widget.initialValue && !hasInteractedByUser) {
164170
setValue(widget.initialValue);
165171
}
172+
if (oldWidget.controller != _dropdownMenuFormField.controller) {
173+
_localTextFieldController?.dispose();
174+
_localTextFieldController = null;
175+
}
166176
}
167177

168178
@override
169179
void dispose() {
170180
_restorableController?.dispose();
181+
_localTextFieldController?.dispose();
171182
super.dispose();
172183
}
173184

@@ -183,6 +194,9 @@ class _DropdownMenuFormFieldState<T> extends FormFieldState<T> {
183194
super.reset();
184195
_dropdownMenuFormField.onSelected?.call(value);
185196
_updateRestorableController(widget.initialValue);
197+
if (widget.initialValue == null) {
198+
textFieldController.clear();
199+
}
186200
}
187201

188202
void _updateRestorableController(T? value) {

packages/flutter/test/material/dropdown_menu_form_field_test.dart

Lines changed: 91 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -506,13 +506,14 @@ void main() {
506506
),
507507
);
508508

509-
// Check default value.
510-
DropdownMenu<MenuItem> dropdownMenu = tester.widget(find.byType(DropdownMenu<MenuItem>));
511-
expect(dropdownMenu.controller, null);
512-
513509
final TextEditingController controller = TextEditingController();
514510
addTearDown(controller.dispose);
515511

512+
// Check default value.
513+
DropdownMenu<MenuItem> dropdownMenu = tester.widget(find.byType(DropdownMenu<MenuItem>));
514+
expect(dropdownMenu.controller, isNotNull); // A default controller is created.
515+
expect(dropdownMenu.controller, isNot(controller));
516+
516517
await tester.pumpWidget(
517518
MaterialApp(
518519
home: Scaffold(
@@ -967,6 +968,92 @@ void main() {
967968
expect(fieldKey.currentState!.value, MenuItem.menuItem0);
968969
});
969970

971+
// Regression test for https://github.com/flutter/flutter/issues/174578.
972+
testWidgets(
973+
'Inner text field is cleared on reset when initialSelection is null - Default controller',
974+
(WidgetTester tester) async {
975+
final GlobalKey<FormFieldState<MenuItem>> fieldKey = GlobalKey<FormFieldState<MenuItem>>();
976+
977+
await tester.pumpWidget(
978+
MaterialApp(
979+
home: Scaffold(
980+
body: DropdownMenuFormField<MenuItem>(key: fieldKey, dropdownMenuEntries: menuEntries),
981+
),
982+
),
983+
);
984+
985+
final TextField textField = tester.widget(find.byType(TextField));
986+
987+
// Select menuItem1.
988+
await tester.tap(find.byType(DropdownMenu<MenuItem>));
989+
await tester.pump();
990+
await tester.tap(findMenuItem(MenuItem.menuItem1));
991+
await tester.pump();
992+
expect(fieldKey.currentState!.value, MenuItem.menuItem1);
993+
expect(
994+
textField.controller?.value,
995+
const TextEditingValue(text: 'Item 1', selection: TextSelection.collapsed(offset: 6)),
996+
);
997+
998+
// After reset the text field content is cleared.
999+
fieldKey.currentState!.reset();
1000+
await tester.pump();
1001+
1002+
expect(fieldKey.currentState!.value, null);
1003+
expect(
1004+
textField.controller?.value,
1005+
const TextEditingValue(selection: TextSelection.collapsed(offset: 0)),
1006+
);
1007+
},
1008+
);
1009+
1010+
// Regression test for https://github.com/flutter/flutter/issues/174578.
1011+
testWidgets(
1012+
'Inner text field is cleared on reset when initialSelection is null - Custom controller',
1013+
(WidgetTester tester) async {
1014+
final GlobalKey<FormFieldState<MenuItem>> fieldKey = GlobalKey<FormFieldState<MenuItem>>();
1015+
final TextEditingController controller = TextEditingController();
1016+
addTearDown(controller.dispose);
1017+
1018+
await tester.pumpWidget(
1019+
MaterialApp(
1020+
home: Scaffold(
1021+
body: DropdownMenuFormField<MenuItem>(
1022+
key: fieldKey,
1023+
controller: controller,
1024+
dropdownMenuEntries: menuEntries,
1025+
),
1026+
),
1027+
),
1028+
);
1029+
1030+
// Custom controller is correctly passed to the inner TextField.
1031+
final TextField textField = tester.widget(find.byType(TextField));
1032+
expect(textField.controller, controller);
1033+
1034+
// Select menuItem1.
1035+
await tester.tap(find.byType(DropdownMenu<MenuItem>));
1036+
await tester.pump();
1037+
await tester.tap(findMenuItem(MenuItem.menuItem1));
1038+
await tester.pump();
1039+
expect(fieldKey.currentState!.value, MenuItem.menuItem1);
1040+
expect(
1041+
textField.controller?.value,
1042+
const TextEditingValue(text: 'Item 1', selection: TextSelection.collapsed(offset: 6)),
1043+
);
1044+
1045+
// After reset the text field content is cleared.
1046+
fieldKey.currentState!.reset();
1047+
await tester.pump();
1048+
1049+
expect(fieldKey.currentState!.value, null);
1050+
expect(
1051+
controller.value,
1052+
const TextEditingValue(selection: TextSelection.collapsed(offset: 0)),
1053+
);
1054+
},
1055+
);
1056+
9701057
testWidgets('isValid and hasError results are correct', (WidgetTester tester) async {
9711058
final GlobalKey<FormFieldState<MenuItem>> fieldKey = GlobalKey<FormFieldState<MenuItem>>();
9721059

0 commit comments

Comments
 (0)