mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Fix DropdownMenuFormField does not clear text field content on reset … (#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](https://github.com/flutter/flutter/issues/174578) ## Tests Adds 2 tests.
This commit is contained in:
parent
937f2b9ad4
commit
6ae27959de
@ -96,7 +96,7 @@ class DropdownMenuFormField<T> extends FormField<T> {
|
||||
textAlign: textAlign,
|
||||
inputDecorationTheme: inputDecorationTheme,
|
||||
menuStyle: menuStyle,
|
||||
controller: controller,
|
||||
controller: state.textFieldController,
|
||||
initialSelection: state.value,
|
||||
onSelected: field.didChange,
|
||||
focusNode: focusNode,
|
||||
@ -139,8 +139,14 @@ class DropdownMenuFormField<T> extends FormField<T> {
|
||||
class _DropdownMenuFormFieldState<T> extends FormFieldState<T> {
|
||||
DropdownMenuFormField<T> get _dropdownMenuFormField => widget as DropdownMenuFormField<T>;
|
||||
|
||||
// The controller used to restore the selected item.
|
||||
RestorableTextEditingController? _restorableController;
|
||||
|
||||
// The controller used to reset the content of the DropdownMenu inner TextField.
|
||||
TextEditingController? _localTextFieldController;
|
||||
TextEditingController get textFieldController =>
|
||||
_dropdownMenuFormField.controller ?? (_localTextFieldController ??= TextEditingController());
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -163,11 +169,16 @@ class _DropdownMenuFormFieldState<T> extends FormFieldState<T> {
|
||||
if (oldWidget.initialValue != widget.initialValue && !hasInteractedByUser) {
|
||||
setValue(widget.initialValue);
|
||||
}
|
||||
if (oldWidget.controller != _dropdownMenuFormField.controller) {
|
||||
_localTextFieldController?.dispose();
|
||||
_localTextFieldController = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_restorableController?.dispose();
|
||||
_localTextFieldController?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -183,6 +194,9 @@ class _DropdownMenuFormFieldState<T> extends FormFieldState<T> {
|
||||
super.reset();
|
||||
_dropdownMenuFormField.onSelected?.call(value);
|
||||
_updateRestorableController(widget.initialValue);
|
||||
if (widget.initialValue == null) {
|
||||
textFieldController.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateRestorableController(T? value) {
|
||||
|
||||
@ -506,13 +506,14 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
// Check default value.
|
||||
DropdownMenu<MenuItem> dropdownMenu = tester.widget(find.byType(DropdownMenu<MenuItem>));
|
||||
expect(dropdownMenu.controller, null);
|
||||
|
||||
final TextEditingController controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
// Check default value.
|
||||
DropdownMenu<MenuItem> dropdownMenu = tester.widget(find.byType(DropdownMenu<MenuItem>));
|
||||
expect(dropdownMenu.controller, isNotNull); // A default controller is created.
|
||||
expect(dropdownMenu.controller, isNot(controller));
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
@ -967,6 +968,92 @@ void main() {
|
||||
expect(fieldKey.currentState!.value, MenuItem.menuItem0);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/174578.
|
||||
testWidgets(
|
||||
'Inner text field is cleared on reset when initialSelection is null - Default controller',
|
||||
(WidgetTester tester) async {
|
||||
final GlobalKey<FormFieldState<MenuItem>> fieldKey = GlobalKey<FormFieldState<MenuItem>>();
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: DropdownMenuFormField<MenuItem>(key: fieldKey, dropdownMenuEntries: menuEntries),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final TextField textField = tester.widget(find.byType(TextField));
|
||||
|
||||
// Select menuItem1.
|
||||
await tester.tap(find.byType(DropdownMenu<MenuItem>));
|
||||
await tester.pump();
|
||||
await tester.tap(findMenuItem(MenuItem.menuItem1));
|
||||
await tester.pump();
|
||||
expect(fieldKey.currentState!.value, MenuItem.menuItem1);
|
||||
expect(
|
||||
textField.controller?.value,
|
||||
const TextEditingValue(text: 'Item 1', selection: TextSelection.collapsed(offset: 6)),
|
||||
);
|
||||
|
||||
// After reset the text field content is cleared.
|
||||
fieldKey.currentState!.reset();
|
||||
await tester.pump();
|
||||
|
||||
expect(fieldKey.currentState!.value, null);
|
||||
expect(
|
||||
textField.controller?.value,
|
||||
const TextEditingValue(selection: TextSelection.collapsed(offset: 0)),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/174578.
|
||||
testWidgets(
|
||||
'Inner text field is cleared on reset when initialSelection is null - Custom controller',
|
||||
(WidgetTester tester) async {
|
||||
final GlobalKey<FormFieldState<MenuItem>> fieldKey = GlobalKey<FormFieldState<MenuItem>>();
|
||||
final TextEditingController controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: DropdownMenuFormField<MenuItem>(
|
||||
key: fieldKey,
|
||||
controller: controller,
|
||||
dropdownMenuEntries: menuEntries,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Custom controller is correctly passed to the inner TextField.
|
||||
final TextField textField = tester.widget(find.byType(TextField));
|
||||
expect(textField.controller, controller);
|
||||
|
||||
// Select menuItem1.
|
||||
await tester.tap(find.byType(DropdownMenu<MenuItem>));
|
||||
await tester.pump();
|
||||
await tester.tap(findMenuItem(MenuItem.menuItem1));
|
||||
await tester.pump();
|
||||
expect(fieldKey.currentState!.value, MenuItem.menuItem1);
|
||||
expect(
|
||||
textField.controller?.value,
|
||||
const TextEditingValue(text: 'Item 1', selection: TextSelection.collapsed(offset: 6)),
|
||||
);
|
||||
|
||||
// After reset the text field content is cleared.
|
||||
fieldKey.currentState!.reset();
|
||||
await tester.pump();
|
||||
|
||||
expect(fieldKey.currentState!.value, null);
|
||||
expect(
|
||||
controller.value,
|
||||
const TextEditingValue(selection: TextSelection.collapsed(offset: 0)),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('isValid and hasError results are correct', (WidgetTester tester) async {
|
||||
final GlobalKey<FormFieldState<MenuItem>> fieldKey = GlobalKey<FormFieldState<MenuItem>>();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user