Widget to let the user search through a keyword string typed on a customizable keyboard in a single or multiple choices list presented as a dropdown in a dialog box or a menu.
This widget has been successfully tested on iOS, Android and Chrome.
The following examples are extracted from the example project available in the repository.
See code below.
Add to your pubspec.yaml
in the dependencies
section:
searchable_dropdown:
Get packages with command:
flutter packages get
Import:
import 'package:searchable_dropdown/searchable_dropdown.dart';
Call either the single choice or the multiple choice constructor.
Search choices Widget with a single choice that opens a dialog or a menu to let the user do the selection conveniently with a search.
factory SearchableDropdown.single({
Key key,
@required List<DropdownMenuItem<T>> items,
@required Function onChanged,
T value,
TextStyle style,
dynamic searchHint,
dynamic hint,
dynamic disabledHint,
dynamic icon = const Icon(Icons.arrow_drop_down),
dynamic underline,
dynamic doneButton,
dynamic label,
dynamic closeButton = "Close",
bool displayClearIcon = true,
Icon clearIcon = const Icon(Icons.clear),
Color iconEnabledColor,
Color iconDisabledColor,
double iconSize = 24.0,
bool isExpanded = false,
bool isCaseSensitiveSearch = false,
Function searchFn,
Function onClear,
Function selectedValueWidgetFn,
TextInputType keyboardType = TextInputType.text,
Function validator,
bool assertUniqueValue = true,
Function displayItem,
bool dialogBox = true,
BoxConstraints menuConstraints,
bool readOnly: false,
Color menuBackgroundColor,
}
)
Search choices Widget with a multiple choice that opens a dialog or a menu to let the user do the selection conveniently with a search.
SearchableDropdown<T>.multiple(
{
Key key,
@required List<DropdownMenuItem<T>> items,
@required Function onChanged,
List<int> selectedItems: const [],
TextStyle style,
dynamic searchHint,
dynamic hint,
dynamic disabledHint,
dynamic icon: const Icon(Icons.arrow_drop_down),
dynamic underline,
dynamic doneButton: "Done",
dynamic label,
dynamic closeButton: "Close",
bool displayClearIcon: true,
Icon clearIcon: const Icon(Icons.clear),
Color iconEnabledColor,
Color iconDisabledColor,
double iconSize: 24.0,
bool isExpanded: false,
bool isCaseSensitiveSearch: false,
Function searchFn,
Function onClear,
Function selectedValueWidgetFn,
TextInputType keyboardType: TextInputType.text,
Function validator,
Function displayItem,
bool dialogBox: true,
BoxConstraints menuConstraints,
bool readOnly: false,
Color menuBackgroundColor,
}
)
Clone repository:
git clone https://github.com/icemanbsi/searchable_dropdown.git
Go to plugin folder:
cd searchable_dropdown
Optionally enable web:
flutter config --enable-web
Create project:
flutter create .
To run automated tests:
flutter test
Optionally generate documentation:
pub global activate dartdoc
dartdoc
Go to example app folder:
cd example
To run web:
run -d chrome
To build web to folder build/web
:
flutter build web
To run on a connected device:
flutter run
To build Android app to build/app/outputs/apk/release/app-release.apk
:
flutter build apk
To build iOS app on Mac:
flutter build ios
SearchableDropdown.single(
items: items,
value: selectedValue,
hint: "Select one",
searchHint: "Select one",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
isExpanded: true,
),
SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: Padding(
padding: const EdgeInsets.all(12.0),
child: Text("Select any"),
),
searchHint: "Select any",
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
closeButton: (selectedItems) {
return (selectedItems.isNotEmpty
? "Save ${selectedItems.length == 1 ? '"' + items[selectedItems.first].value.toString() + '"' : '(' + selectedItems.length.toString() + ')'}"
: "Save without selection");
},
isExpanded: true,
),
SearchableDropdown.single(
items: items,
value: selectedValue,
hint: "Select one",
searchHint: "Select one",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
doneButton: "Done",
displayItem: (item, selected) {
return (Row(children: [
selected
? Icon(
Icons.radio_button_checked,
color: Colors.grey,
)
: Icon(
Icons.radio_button_unchecked,
color: Colors.grey,
),
SizedBox(width: 7),
Expanded(
child: item,
),
]));
},
isExpanded: true,
),
SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: Padding(
padding: const EdgeInsets.all(12.0),
child: Text("Select any"),
),
searchHint: "Select any",
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
displayItem: (item, selected) {
return (Row(children: [
selected
? Icon(
Icons.check,
color: Colors.green,
)
: Icon(
Icons.check_box_outline_blank,
color: Colors.grey,
),
SizedBox(width: 7),
Expanded(
child: item,
),
]));
},
selectedValueWidgetFn: (item) {
return (Center(
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: BorderSide(
color: Colors.brown,
width: 0.5,
),
),
margin: EdgeInsets.all(12),
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(item.toString()),
))));
},
doneButton: (selectedItemsDone, doneContext) {
return (RaisedButton(
onPressed: () {
Navigator.pop(doneContext);
setState(() {});
},
child: Text("Save")));
},
closeButton: null,
style: TextStyle(fontStyle: FontStyle.italic),
searchFn: (String keyword, items) {
List<int> ret = List<int>();
if (keyword != null && items != null && keyword.isNotEmpty) {
keyword.split(" ").forEach((k) {
int i = 0;
items.forEach((item) {
if (k.isNotEmpty &&
(item.value
.toString()
.toLowerCase()
.contains(k.toLowerCase()))) {
ret.add(i);
}
i++;
});
});
}
if (keyword.isEmpty) {
ret = Iterable<int>.generate(items.length).toList();
}
return (ret);
},
clearIcon: Icon(Icons.clear_all),
icon: Icon(Icons.arrow_drop_down_circle),
label: "Label for multi",
underline: Container(
height: 1.0,
decoration: BoxDecoration(
border:
Border(bottom: BorderSide(color: Colors.teal, width: 3.0))),
),
iconDisabledColor: Colors.brown,
iconEnabledColor: Colors.indigo,
isExpanded: true,
),
SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select 3 items",
searchHint: "Select 3",
validator: (selectedItemsForValidator) {
if (selectedItemsForValidator.length != 3) {
return ("Must select 3");
}
return (null);
},
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
doneButton: (selectedItemsDone, doneContext) {
return (RaisedButton(
onPressed: selectedItemsDone.length != 3
? null
: () {
Navigator.pop(doneContext);
setState(() {});
},
child: Text("Save")));
},
closeButton: (selectedItems) {
return (selectedItems.length == 3 ? "Ok" : null);
},
isExpanded: true,
),
SearchableDropdown.single(
items: items,
value: selectedValue,
hint: "Select one",
searchHint: null,
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: false,
isExpanded: true,
menuConstraints: BoxConstraints.tight(Size.fromHeight(350)),
),
SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select any",
searchHint: "",
doneButton: "Close",
closeButton: SizedBox.shrink(),
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
dialogBox: false,
isExpanded: true,
menuConstraints: BoxConstraints.tight(Size.fromHeight(350)),
),
SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select any",
searchHint: "Select any",
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
dialogBox: false,
closeButton: (selectedItemsClose) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
selectedItems.clear();
selectedItems.addAll(
Iterable<int>.generate(items.length).toList());
});
},
child: Text("Select all")),
RaisedButton(
onPressed: () {
setState(() {
selectedItems.clear();
});
},
child: Text("Select none")),
],
);
},
isExpanded: true,
menuConstraints: BoxConstraints.tight(Size.fromHeight(350)),
),
SearchableDropdown.multiple(
items: items,
selectedItems: selectedItems,
hint: "Select any",
searchHint: "Select any",
displayClearIcon: false,
onChanged: (value) {
setState(() {
selectedItems = value;
});
},
dialogBox: true,
closeButton: (selectedItemsClose) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
RaisedButton(
onPressed: () {
setState(() {
selectedItems.clear();
selectedItems.addAll(
Iterable<int>.generate(items.length).toList());
});
},
child: Text("Select all")),
RaisedButton(
onPressed: () {
setState(() {
selectedItems.clear();
});
},
child: Text("Select none")),
],
);
},
isExpanded: true,
),
SearchableDropdown.single(
items: Iterable<int>.generate(20).toList().map((i) {
return (DropdownMenuItem(
child: Text(i.toString()),
value: i.toString(),
));
}).toList(),
value: selectedValue,
hint: "Select one number",
searchHint: "Select one number",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: true,
keyboardType: TextInputType.number,
isExpanded: true,
),
SearchableDropdown.single(
items: ExampleNumber.list.map((exNum) {
return (DropdownMenuItem(
child: Text(exNum.numberString), value: exNum));
}).toList(),
value: selectedNumber,
hint: "Select one number",
searchHint: "Select one number",
onChanged: (value) {
setState(() {
selectedNumber = value;
});
},
dialogBox: true,
isExpanded: true,
),
SearchableDropdown.single(
items: [
DropdownMenuItem(
child: Text(
"way too long text for a smartphone at least one that goes in a normal sized pair of trousers but maybe not for a gigantic screen like there is one at my cousin's home in a very remote country where I
wouldn't want to go right now"),
value:
"way too long text for a smartphone at least one that goes in a normal sized pair of trousers but maybe not for a gigantic screen like there is one at my cousin's home in a very remote country where I
wouldn't want to go right now",
)
],
value: "",
hint: "Select one",
searchHint: "Select one",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: true,
isExpanded: true,
),
SearchableDropdown.single(
items: [
DropdownMenuItem(
child: Text(
"one item"),
value:
"one item",
)
],
value: "one item",
hint: "Select one",
searchHint: "Select one",
disabledHint: "Disabled",
onChanged: (value) {
setState(() {
selectedValue = value;
});
},
dialogBox: true,
isExpanded: true,
readOnly: true,
),
SearchableDropdown.single(
items: [
DropdownMenuItem(
child: Text(
"one item"),
value:
"one item",
)
],
value: "one item",
hint: "Select one",
searchHint: "Select one",
disabledHint: "Disabled",
onChanged: null,
dialogBox: true,
isExpanded: true,
),
Feel free to log your feature requests/comments/questions/bugs here: https://github.com/icemanbsi/searchable_dropdown/issues
We would be happy to merge pull request proposals provided that:
Contributions and forks are very welcome!
In your pull request, feel free to add your line in the contributors section below:
Continuous integration/deployment status: