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;
|
TextEditingController? _localTextEditingController;
|
||||||
final FocusNode _internalFocudeNode = FocusNode();
|
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
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
@ -545,8 +533,15 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
|||||||
filteredEntries = widget.dropdownMenuEntries;
|
filteredEntries = widget.dropdownMenuEntries;
|
||||||
buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey());
|
buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey());
|
||||||
_menuHasEnabledItem = filteredEntries.any((DropdownMenuEntry<T> entry) => entry.enabled);
|
_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();
|
refreshLeadingPadding();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -585,19 +580,20 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
|
|||||||
filteredEntries = widget.dropdownMenuEntries;
|
filteredEntries = widget.dropdownMenuEntries;
|
||||||
buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey());
|
buttonItemKeys = List<GlobalKey>.generate(filteredEntries.length, (int index) => GlobalKey());
|
||||||
_menuHasEnabledItem = filteredEntries.any((DropdownMenuEntry<T> entry) => entry.enabled);
|
_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) {
|
if (oldWidget.leadingIcon != widget.leadingIcon) {
|
||||||
refreshLeadingPadding();
|
refreshLeadingPadding();
|
||||||
}
|
}
|
||||||
if (oldWidget.initialSelection != widget.initialSelection) {
|
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);
|
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(
|
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 {
|
(WidgetTester tester) async {
|
||||||
final TextEditingController controller = TextEditingController();
|
final TextEditingController controller = TextEditingController(text: 'Flutter');
|
||||||
addTearDown(controller.dispose);
|
addTearDown(controller.dispose);
|
||||||
|
|
||||||
Widget boilerplate(List<DropdownMenuEntry<TestMenu>> entries) {
|
await tester.pumpWidget(
|
||||||
return MaterialApp(
|
MaterialApp(
|
||||||
home: StatefulBuilder(
|
home: StatefulBuilder(
|
||||||
builder: (BuildContext context, StateSetter setState) {
|
builder: (BuildContext context, StateSetter setState) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: DropdownMenu<TestMenu>(
|
body: DropdownMenu<TestMenu>(
|
||||||
initialSelection: TestMenu.mainMenu3,
|
initialSelection: TestMenu.mainMenu3,
|
||||||
dropdownMenuEntries: entries,
|
// Use a menu entries which does not contain TestMenu.mainMenu3.
|
||||||
|
dropdownMenuEntries: menuChildren.getRange(0, 1).toList(),
|
||||||
controller: controller,
|
controller: controller,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
|
|
||||||
await tester.pumpWidget(boilerplate(menuChildren));
|
expect(controller.text, 'Flutter');
|
||||||
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);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user