Reputation: 4483
I have a list of the countries name in local json. I can load my local json and assign to DropDown button. there is a 193 countries in json file as ex. shown below. If I want to select United State, user have to scroll all the way down. How can enter a countries name such as; if I user enter U or u the dropdown can makes quick filtering and list all the countries that starts with U such as United State. How do I search in Flutter DropDownbutton items?
{
"country": [
{
"countryCode": "AD",
"countryName": "Andorra",
"currencyCode": "EUR",
"isoNumeric": "020"
},
{
"countryCode": "AE",
"countryName": "United Arab Emirates",
"currencyCode": "AED",
"isoNumeric": "784"
},
{
"countryCode": "AF",
"countryName": "Afghanistan",
"currencyCode": "AFN",
"isoNumeric": "004"
},
//...
]
}
Upvotes: 14
Views: 49020
Reputation: 344
here is clean way to implement dropdown search using auto complete widget
class DropdownSearch<T extends DropAble> extends StatelessWidget {
final TextEditingController textController;
final List<T> items;
final void Function(T) onSuggestionSelected;
final bool required;
final String placeHolder;
final String label;
final String? Function(T?) validator;
const DropdownSearch({
super.key,
required this.textController,
required this.items,
required this.onSuggestionSelected,
this.required = false,
this.label = "",
this.placeHolder = "",
required this.validator,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 14),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${required ? "* " : ""}$label",
style: Theme.of(context).textTheme.labelMedium,
),
const SizedBox(
height: 14,
),
RawAutocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
return items.map((e)=>e.name ?? "").toList()
.where((item) =>
(item).toLowerCase().contains(textEditingValue.text.toLowerCase()))
.toList();
},
onSelected: (String selection) {
textController.text = selection;
T? item = items.firstWhereOrNull((element) => element.name == selection);
if (item != null) {
onSuggestionSelected(item);
}
},
fieldViewBuilder: (
BuildContext context,
TextEditingController textEditingController,
FocusNode focusNode,
VoidCallback onFieldSubmitted,
) {
return TextFormField(
controller: textEditingController,
decoration: InputDecoration(
hintText: placeHolder,
),
focusNode: focusNode,
onFieldSubmitted: (String value) {
T? item = items.firstWhereOrNull((element) => element.name == value);
onFieldSubmitted();
if (item != null) {
onSuggestionSelected(item);
}
},
// validator: vali,
);
},
optionsViewBuilder: (
BuildContext context,
AutocompleteOnSelected<String> onSelected,
Iterable<String> options,
) {
return Align(
alignment: Get.locale?.languageCode == "ar" ? Alignment.topRight : Alignment.topLeft,
child: Material(
elevation: 4.0,
child: SizedBox(
width: Get.width-70,
child: ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: options.length,
itemBuilder: (BuildContext context, int index) {
final option = options.elementAt(index);
return GestureDetector(
onTap: () {
textController.text = option;
onSelected(option);
T? item = items.firstWhereOrNull((element) => element.name == option);
if(item != null){
onSuggestionSelected(item);
}
},
child: ListTile(
title: Text(option),
),
);
},
),
),
),
);
},
),
const SizedBox(
height: 14,
),
],
),
);
}
}
Upvotes: 0
Reputation: 1437
This is a bit old I think, as now using just the included material package and utilizing the DropDownMenu widget works.
You use the enableFilter: True
property to enable the manual search.
If you somehow click the widget and it doesn't allow you to type for searching, then adding the property requestFocusOnTap: true
helps especially in ListViews / ListTiles / Scrollables Cards
DropdownMenu<CountryEntity>(
controller: countryController,
label: const Text('Country'),
width: 300,
dropdownMenuEntries: countryEntities,
enableFilter: true,
menuStyle: const MenuStyle(
alignment: Alignment.bottomLeft,
maximumSize:
MaterialStatePropertyAll(Size.fromHeight(Sizes.p124)),),
requestFocusOnTap: true,
onSelected: (country) {
setState(() {
selectedCountryId = country;
});
},
);
Upvotes: 2
Reputation: 1887
You can use dropdown_search package like below.
import 'package:dropdown_search/dropdown_search.dart';
DropdownSearch<String>(
popupProps: PopupProps.menu(
showSearchBox: true,
showSelectedItems: true,
disabledItemFn: (String s) => s.startsWith('I'),
),
items: ["Brazil", "Italia (Disabled)", "Tunisia", 'Canada'],
dropdownDecoratorProps: DropDownDecoratorProps(
dropdownSearchDecoration: InputDecoration(
labelText: "Menu mode",
hintText: "country in menu mode",
),
),
onChanged: print,
selectedItem: "Brazil",
)
Make sure you have set showSearchBox
attribute under popupProps
. This attribute is not yet mentioned in the documentation. See PopupProps source code for more attributes.
Upvotes: 2
Reputation: 121
Use textfield_search: ^0.8.0 plugin view sample
TextFieldSearch(
decoration: InputDecoration(
hintText: "Search",
prefixIcon: Icon(
Icons.search,
color: Colors.black45,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey[200],
),
initialList: constants.VEHICLELIST,
label: "label",
controller: selectedVehicle),
Upvotes: 0
Reputation: 784
You can use searchable_dropdown package instead: https://pub.dev/packages/searchable_dropdown
And here is my example code searchable_dropdown dont work with class list
Make sure that you put the following if you use a class list like my example
@override
String toString() {
return this.key;
}
Upvotes: 9
Reputation: 3449
One way is to use a TextEditingController
to filter your ListView
like this:
class YourPage extends StatefulWidget {
@override
State createState() => YourPageState();
}
class YourPageState extends State<YourPage> {
List<Country> countries = new List<Country>();
TextEditingController controller = new TextEditingController();
String filter;
@override
void initState() {
super.initState();
//fill countries with objects
controller.addListener(() {
setState(() {
filter = controller.text;
});
});
}
@override
void dispose() {
super.dispose();
controller.dispose();
}
@override
Widget build(BuildContext context) {
return new Material(
color: Colors.transparent,
child: new Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
new Padding(
padding: new EdgeInsets.only(top: 8.0, left: 16.0, right: 16.0),
child: new TextField(
style: new TextStyle(fontSize: 18.0, color: Colors.black),
decoration: InputDecoration(
prefixIcon: new Icon(Icons.search),
suffixIcon: new IconButton(
icon: new Icon(Icons.close),
onPressed: () {
controller.clear();
FocusScope.of(context).requestFocus(new FocusNode());
},
),
hintText: "Search...",
),
controller: controller,
)),
new Expanded(
child: new Padding(
padding: new EdgeInsets.only(top: 8.0),
child: _buildListView()),
)
],
));
}
Widget _buildListView() {
return ListView.builder(
itemCount: countries.length,
itemBuilder: (BuildContext context, int index) {
if (filter == null || filter == "") {
return _buildRow(countries[index]);
} else {
if (countries[index].countryName
.toLowerCase()
.contains(filter.toLowerCase())) {
return _buildRow(countries[index]);
} else {
return new Container();
}
}
});
}
Widget _buildRow(Country c) {
return new ListTile(
title: new Text(
c.countryName,
),
subtitle: new Text(
c.countryCode,
));
}
}
Upvotes: 5