Skip to content

Commit 04e1b17

Browse files
authored
fix: Dropdown menu trying to access highlight element which doesn't exist when search and filters both are enabled (flutter#151969)
DropdownMenu throws RangeError when both filter and search are enabled because when we search for elements, we have some highlighted element, but if there is no element to highlight it tries to access 0th element is the filtered entries, but entries are empty. Fixes flutter#151878
1 parent d4bfa2f commit 04e1b17

File tree

2 files changed

+46
-4
lines changed

2 files changed

+46
-4
lines changed

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

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -613,14 +613,30 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
613613
.toList();
614614
}
615615

616+
bool _shouldUpdateCurrentHighlight(List<DropdownMenuEntry<T>> entries) {
617+
final String searchText = _localTextEditingController!.value.text.toLowerCase();
618+
if (searchText.isEmpty) {
619+
return true;
620+
}
621+
622+
// When `entries` are filtered by filter algorithm, currentHighlight may exceed the valid range of `entries` and should be updated.
623+
if (currentHighlight == null || currentHighlight! >= entries.length) {
624+
return true;
625+
}
626+
627+
if (entries[currentHighlight!].label.toLowerCase().contains(searchText)) {
628+
return false;
629+
}
630+
631+
return true;
632+
}
633+
616634
int? search(List<DropdownMenuEntry<T>> entries, TextEditingController textEditingController) {
617635
final String searchText = textEditingController.value.text.toLowerCase();
618636
if (searchText.isEmpty) {
619637
return null;
620638
}
621-
if (currentHighlight != null && entries[currentHighlight!].label.toLowerCase().contains(searchText)) {
622-
return currentHighlight;
623-
}
639+
624640
final int index = entries.indexWhere((DropdownMenuEntry<T> entry) => entry.label.toLowerCase().contains(searchText));
625641

626642
return index != -1 ? index : null;
@@ -796,7 +812,10 @@ class _DropdownMenuState<T> extends State<DropdownMenu<T>> {
796812
if (widget.searchCallback != null) {
797813
currentHighlight = widget.searchCallback!(filteredEntries, _localTextEditingController!.text);
798814
} else {
799-
currentHighlight = search(filteredEntries, _localTextEditingController!);
815+
final bool shouldUpdateCurrentHighlight = _shouldUpdateCurrentHighlight(filteredEntries);
816+
if (shouldUpdateCurrentHighlight) {
817+
currentHighlight = search(filteredEntries, _localTextEditingController!);
818+
}
800819
}
801820
if (currentHighlight != null) {
802821
scrollToHighlight();

packages/flutter/test/material/dropdown_menu_test.dart

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1292,6 +1292,29 @@ void main() {
12921292

12931293
}, variant: TargetPlatformVariant.desktop());
12941294

1295+
// Regression test for https://github.com/flutter/flutter/issues/151878.
1296+
testWidgets('Searching for non matching item does not crash',
1297+
(WidgetTester tester) async {
1298+
await tester.pumpWidget(MaterialApp(
1299+
home: Scaffold(
1300+
body: DropdownMenu<TestMenu>(
1301+
enableFilter: true,
1302+
requestFocusOnTap: true,
1303+
dropdownMenuEntries: menuChildren,
1304+
),
1305+
),
1306+
));
1307+
1308+
// Open the menu.
1309+
await tester.tap(find.byType(DropdownMenu<TestMenu>));
1310+
await tester.pump();
1311+
await tester.enterText(find.byType(TextField).first, 'Me');
1312+
await tester.pumpAndSettle();
1313+
await tester.enterText(find.byType(TextField).first, 'Meu');
1314+
await tester.pumpAndSettle();
1315+
expect(tester.takeException(), isNull);
1316+
});
1317+
12951318
// Regression test for https://github.com/flutter/flutter/issues/147253.
12961319
testWidgets('Default search prioritises the current highlight on desktop platforms',
12971320
(WidgetTester tester) async {

0 commit comments

Comments
 (0)