Revert rematching DropdownMenu.initialSelection (#160643)
## Description This PR reverts `DropdownMenu` changes from https://github.com/flutter/flutter/pull/155757. Automatically rematching the `initialSelection` breaks some use cases. It is more flexible to let users manipulate the text field content using the TextEditingController. ## Related Issue Fixes [Dropdown Menu Creates Infinite Build Loop](https://github.com/flutter/flutter/issues/160196) Fixes [Can no longer initialize non selectable value in DropdownMenu as of flutter version 3.27.1](https://github.com/flutter/flutter/issues/160555) ## Tests Removes 2 regression tests from https://github.com/flutter/flutter/pull/155757. Keeps 2 tests from the original PR (missing test for the initialSelection behavior). Adds 1 tests to avoid regressing this revert.
This commit is contained in:
parent
a17c647ccc
commit
2a3a19c189
@ -521,18 +521,6 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
TextEditingController? _localTextEditingController;
|
||||
final FocusNode _internalFocudeNode = FocusNode();
|
||||
|
||||
TextEditingValue get _initialTextEditingValue {
|
||||
for (final DropdownMenuEntry<T> entry in filteredEntries) {
|
||||
if (entry.value == widget.initialSelection) {
|
||||
return TextEditingValue(
|
||||
text: entry.label,
|
||||
selection: TextSelection.collapsed(offset: entry.label.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
return TextEditingValue.empty;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@ -545,8 +533,15 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
filteredEntries = widget.dropdownMenuEntries;
|
||||
buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey());
|
||||
_menuHasEnabledItem = filteredEntries.any((DropdownMenuEntry<T> entry) => entry.enabled);
|
||||
_localTextEditingController?.value = _initialTextEditingValue;
|
||||
|
||||
final int index = filteredEntries.indexWhere(
|
||||
(DropdownMenuEntry<T> entry) => entry.value == widget.initialSelection,
|
||||
);
|
||||
if (index != -1) {
|
||||
_localTextEditingController?.value = TextEditingValue(
|
||||
text: filteredEntries[index].label,
|
||||
selection: TextSelection.collapsed(offset: filteredEntries[index].label.length),
|
||||
);
|
||||
}
|
||||
refreshLeadingPadding();
|
||||
}
|
||||
|
||||
@ -585,19 +580,20 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
||||
filteredEntries = widget.dropdownMenuEntries;
|
||||
buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey());
|
||||
_menuHasEnabledItem = filteredEntries.any((DropdownMenuEntry<T> entry) => entry.enabled);
|
||||
// If the text field content matches one of the new entries do not rematch the initialSelection.
|
||||
final bool isCurrentSelectionValid = filteredEntries.any(
|
||||
(DropdownMenuEntry<T> entry) => entry.label == _localTextEditingController?.text,
|
||||
);
|
||||
if (!isCurrentSelectionValid) {
|
||||
_localTextEditingController?.value = _initialTextEditingValue;
|
||||
}
|
||||
}
|
||||
if (oldWidget.leadingIcon != widget.leadingIcon) {
|
||||
refreshLeadingPadding();
|
||||
}
|
||||
if (oldWidget.initialSelection != widget.initialSelection) {
|
||||
_localTextEditingController?.value = _initialTextEditingValue;
|
||||
final int index = filteredEntries.indexWhere(
|
||||
(DropdownMenuEntry<T> entry) => entry.value == widget.initialSelection,
|
||||
);
|
||||
if (index != -1) {
|
||||
_localTextEditingController?.value = TextEditingValue(
|
||||
text: filteredEntries[index].label,
|
||||
selection: TextSelection.collapsed(offset: filteredEntries[index].label.length),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2174,83 +2174,30 @@ void main() {
|
||||
expect(controller.text, isEmpty);
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/155660.
|
||||
testWidgets('Updating the menu entries refreshes the initial selection', (
|
||||
WidgetTester tester,
|
||||
) async {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
Widget boilerplate(List<DropdownMenuEntry<TestMenu>> entries) {
|
||||
return MaterialApp(
|
||||
home: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
initialSelection: TestMenu.mainMenu3,
|
||||
dropdownMenuEntries: entries,
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// The text field should be empty when the initial selection does not match
|
||||
// any menu items.
|
||||
await tester.pumpWidget(boilerplate(menuChildren.getRange(0, 1).toList()));
|
||||
expect(controller.text, '');
|
||||
|
||||
// When the menu entries is updated the initial selection should be rematched.
|
||||
await tester.pumpWidget(boilerplate(menuChildren));
|
||||
expect(controller.text, TestMenu.mainMenu3.label);
|
||||
|
||||
// Update the entries with none matching the initial selection.
|
||||
await tester.pumpWidget(boilerplate(menuChildren.getRange(0, 1).toList()));
|
||||
expect(controller.text, '');
|
||||
});
|
||||
|
||||
// Regression test for https://github.com/flutter/flutter/issues/155660.
|
||||
testWidgets(
|
||||
'Updating the menu entries refreshes the initial selection only if the current selection is no more valid',
|
||||
'Text field content is not cleared when the initial selection does not match any menu entries',
|
||||
(WidgetTester tester) async {
|
||||
final TextEditingController controller = TextEditingController();
|
||||
final TextEditingController controller = TextEditingController(text: 'Flutter');
|
||||
addTearDown(controller.dispose);
|
||||
|
||||
Widget boilerplate(List<DropdownMenuEntry<TestMenu>> entries) {
|
||||
return MaterialApp(
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: StatefulBuilder(
|
||||
builder: (BuildContext context, StateSetter setState) {
|
||||
return Scaffold(
|
||||
body: DropdownMenu<TestMenu>(
|
||||
initialSelection: TestMenu.mainMenu3,
|
||||
dropdownMenuEntries: entries,
|
||||
// Use a menu entries which does not contain TestMenu.mainMenu3.
|
||||
dropdownMenuEntries: menuChildren.getRange(0, 1).toList(),
|
||||
controller: controller,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpWidget(boilerplate(menuChildren));
|
||||
expect(controller.text, TestMenu.mainMenu3.label);
|
||||
|
||||
// Open the menu.
|
||||
await tester.tap(find.byType(DropdownMenu<TestMenu>));
|
||||
await tester.pump();
|
||||
|
||||
// Select another item.
|
||||
final Finder item2 = findMenuItemButton('Item 2');
|
||||
await tester.tap(item2);
|
||||
await tester.pumpAndSettle();
|
||||
expect(controller.text, TestMenu.mainMenu2.label);
|
||||
|
||||
// Update the menu entries with another instance of list containing the
|
||||
// same entries.
|
||||
await tester.pumpWidget(boilerplate(List<DropdownMenuEntry<TestMenu>>.from(menuChildren)));
|
||||
expect(controller.text, TestMenu.mainMenu2.label);
|
||||
expect(controller.text, 'Flutter');
|
||||
},
|
||||
);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user