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:
Bruno Leroux 2024-12-21 06:16:49 +01:00 committed by GitHub
parent a17c647ccc
commit 2a3a19c189
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 27 additions and 84 deletions

View File

@ -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),
);
}
}
}

View File

@ -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');
},
);