Nick
Nick

Reputation: 4483

How do I search in Flutter DropDown button

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

Answers (6)

ayman omara
ayman omara

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

Abel
Abel

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

ruwan800
ruwan800

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

manu mathew
manu mathew

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

mike
mike

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

soupjake
soupjake

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

Related Questions