search_field_dropdown 1.2.7 copy "search_field_dropdown: ^1.2.7" to clipboard
search_field_dropdown: ^1.2.7 copied to clipboard

A customizable dropdown widget for Flutter with advanced styling, search functionality, and dynamic data handling.

example/lib/main.dart

import 'dart:convert';
import 'package:search_field_dropdown/search_field_dropdown.dart';
import 'Model/city_model.dart';
import 'Model/states_model.dart';
import 'Model/country_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;

class DummyUserModel {
  final int id;
  final String name;
  final String email;
  final String image;

  DummyUserModel(
      {required this.id,
      required this.name,
      required this.email,
      required this.image});

  factory DummyUserModel.fromJson(Map<String, dynamic> json) {
    return DummyUserModel(
      id: json['id'],
      name: "${json['firstName']} ${json['lastName']}",
      email: json['email'],
      image: json['image'],
    );
  }

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is DummyUserModel &&
          runtimeType == other.runtimeType &&
          id == other.id;

  @override
  int get hashCode => id.hashCode;
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FormFiled DropDown Example',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.deepPurple,
          primary: Colors.deepPurple,
        ),
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.deepPurple,
        ),
        useMaterial3: true,
      ),
      home: const DropDownClass(),
    );
  }
}

class DropDownClass extends StatefulWidget {
  const DropDownClass({super.key});

  @override
  State<DropDownClass> createState() => _DropDownClassState();
}

class _DropDownClassState extends State<DropDownClass> {
  final countryController = OverlayPortalController();
  List<OverlayPortalController> countryController1 = [];
  final stateController = OverlayPortalController();
  final cityController = OverlayPortalController();
  final itemController = OverlayPortalController();
  final multiCityController = OverlayPortalController();
  final userApiController = OverlayPortalController();

  CountryModel? selectedCountry;
  StatesModel? selectedState;
  CityModel? selectedCity;
  List<CityModel> selectedMultiCities = [];
  List<DummyUserModel> selectedApiUsers = [];

  List<StatesModel> tempStatesList = [];
  List<CountryModel> countryList = [];
  List<StatesModel> statesList = [];
  List<CityModel> tempCityList = [];
  List<CityModel> cityList = [];

  FocusNode focusNode = FocusNode();
  FocusNode focusNode2 = FocusNode();
  FocusNode focusNode3 = FocusNode();

  Future<void> loadCity() async {
    cityList = [];

    var data = await rootBundle.loadString("assets/JsonFiles/City.json");
    var cityData = json.decode(data);
    List list = cityData['city'].cast<Map<String, dynamic>>();
    cityList.addAll(list.map((e) => CityModel.fromJson(e)).toList());
    setState(() {});
  }

  Future<void> loadState() async {
    statesList = [];
    var data = await rootBundle.loadString("assets/JsonFiles/State.json");
    var stateData = json.decode(data);
    List list = stateData['states'].cast<Map<String, dynamic>>();
    statesList.addAll(list.map((e) => StatesModel.fromJson(e)).toList());
    setState(() {});
  }

  Future<void> loadCountry() async {
    countryList = [];
    var data = await rootBundle.loadString("assets/JsonFiles/Country.json");
    var countryData = json.decode(data);
    List list = countryData['countries'].cast<Map<String, dynamic>>();
    countryList.addAll(list.map((e) => CountryModel.fromJson(e)).toList());
    setState(() {});
  }

  bool isHidde = false;

  FocusNode focusNodes = FocusNode();

  @override
  void initState() {
    super.initState();
    loadCity();
    loadState();
    loadCountry();

    countryController1 =
        List.generate(15, (index) => OverlayPortalController());
  }

  final GlobalKey<SearchFieldDropdownState<String>> dropdownKey1 =
      GlobalKey<SearchFieldDropdownState<String>>();
  final GlobalKey<SearchFieldDropdownState<String>> dropdownKey2 =
      GlobalKey<SearchFieldDropdownState<String>>();
  final GlobalKey<SearchFieldDropdownState<String>> dropdownKey3 =
      GlobalKey<SearchFieldDropdownState<String>>();

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: Scaffold(
        appBar: AppBar(
          title: const Text(
            "FormFiled DropDown Example",
            style: TextStyle(color: Colors.white),
          ),
        ),
        body: Padding(
          padding: const EdgeInsets.all(16.0),
          child: SingleChildScrollView(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                const Text("Simple Single Selection",
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                const SizedBox(height: 8),
                SearchFieldDropdown<CountryModel>(
                  focusNode: focusNode,
                  enableInteractiveSelection: true,
                  autovalidateMode: AutovalidateMode.onUserInteraction,
                  controller: countryController,
                  initialItem: selectedCountry,
                  item: countryList,
                  decoration: SearchFieldDropdownDecoration(
                    textStyle: const TextStyle(
                        fontSize: 12, fontWeight: FontWeight.w400),
                    menuDecoration: BoxDecoration(
                      color: Colors.white,
                      borderRadius: BorderRadius.circular(4),
                      border: Border.all(color: Colors.blueAccent),
                    ),
                    fieldDecoration: InputDecoration(
                      hintText: "Select a country",
                      suffixIcon: IntrinsicWidth(
                        child: Row(
                          children: [
                            if (selectedCountry != null)
                              InkWell(
                                onTap: () {
                                  setState(() {
                                    selectedCountry = null;
                                  });
                                },
                                child: const Icon(
                                  Icons.clear,
                                  size: 20,
                                ),
                              ),
                            if (selectedCountry != null)
                              const SizedBox(width: 5),
                            const Icon(
                              Icons.arrow_drop_down_sharp,
                              size: 20,
                            ),
                            const SizedBox(width: 8),
                          ],
                        ),
                      ),
                    ),
                  ),
                  onChanged: (CountryModel? value) {
                    setState(() {
                      selectedCountry = value;
                    });
                  },
                  onSearch: (value) async {
                    return countryList.where((element) {
                      return element.name
                          .toLowerCase()
                          .contains(value.toLowerCase());
                    }).toList();
                  },
                  listItemBuilder: (context, item, isSelected) {
                    int index = countryList.indexOf(item);
                    return Container(
                      padding: const EdgeInsets.symmetric(
                          horizontal: 5, vertical: 5),
                      margin: EdgeInsets.fromLTRB(5, index == 0 ? 7 : 2, 5, 1),
                      decoration: BoxDecoration(
                          color: isSelected ? Colors.green : Colors.transparent,
                          borderRadius: BorderRadius.circular(2)),
                      child: Text(
                        item.name,
                        style: TextStyle(
                            fontSize: 12,
                            color: isSelected ? Colors.white : Colors.black,
                            fontWeight: FontWeight.w400),
                      ),
                    );
                  },
                  selectedItemBuilder: (context, item) {
                    return Text(
                      item.name,
                      style: const TextStyle(
                          fontSize: 12, fontWeight: FontWeight.w400),
                    );
                  },
                ),
                const SizedBox(height: 30),
                const Text("Simple Multi Selection",
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                const SizedBox(height: 8),
                SearchFieldDropdown<CityModel>(
                  initialItems: selectedMultiCities,
                  controller: multiCityController,
                  item: cityList,
                  decoration: SearchFieldDropdownDecoration(
                    isMultiSelect: true,
                    showSelectedItemsInField: false,
                    textStyle: const TextStyle(
                        fontSize: 12, fontWeight: FontWeight.w400),
                    menuDecoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(4),
                        border: Border.all(color: Colors.blueAccent)),
                    focusedItemDecoration: BoxDecoration(
                        color: Colors.green,
                        borderRadius: BorderRadius.circular(2)),
                    unfocusedItemDecoration: BoxDecoration(
                        color: Colors.transparent,
                        borderRadius: BorderRadius.circular(2)),
                    itemPadding:
                        const EdgeInsets.symmetric(horizontal: 5, vertical: 5),
                    fieldDecoration: const InputDecoration(
                      hintText: "Select multiple cities",
                      suffixIcon: Icon(Icons.arrow_drop_down_sharp, size: 20),
                    ),
                  ),
                  onItemsChanged: (List<CityModel> values) {
                    setState(() {
                      selectedMultiCities = values;
                    });
                  },
                  onSearch: (value) async {
                    return cityList.where((element) {
                      return element.name
                          .toLowerCase()
                          .contains(value.toLowerCase());
                    }).toList();
                  },
                  listItemBuilder: (context, item, isSelected) {
                    int index = cityList.indexOf(item);
                    return Padding(
                      padding: EdgeInsets.fromLTRB(5, index == 0 ? 7 : 2, 5, 1),
                      child: Text(
                        item.name,
                        style: TextStyle(
                            fontSize: 12,
                            color: isSelected ? Colors.white : Colors.black,
                            fontWeight: FontWeight.w400),
                      ),
                    );
                  },
                  selectedItemBuilder: (context, item) {
                    return Text(
                      item.name,
                      style: const TextStyle(
                          fontSize: 12, fontWeight: FontWeight.w400),
                    );
                  },
                  selectedItemsBuilder: (context, items) {
                    return items.map((e) => e.name).join(', ');
                  },
                  multiSelectDisplayBuilder:
                      (context, selectedItems, onRemove) {
                    if (selectedItems.isEmpty) return const SizedBox.shrink();
                    return Padding(
                      padding: const EdgeInsets.only(top: 8.0),
                      child: Wrap(
                        spacing: 8.0,
                        runSpacing: 8.0,
                        children: selectedItems.map((city) {
                          return Chip(
                            label: Text(city.name,
                                style: const TextStyle(fontSize: 12)),
                            deleteIcon: const Icon(Icons.close, size: 16),
                            onDeleted: () => onRemove(city),
                            backgroundColor: Colors.cyan.withValues(alpha: 0.1),
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(4),
                              side: BorderSide(
                                  color: Colors.cyan.withValues(alpha: 0.3)),
                            ),
                          );
                        }).toList(),
                      ),
                    );
                  },
                ),
                const SizedBox(height: 30),
                const Text("API Single Selection",
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                const SizedBox(height: 8),
                SearchFieldDropdown<DummyUserModel>(
                  controller: OverlayPortalController(),
                  isApiLoading: false,
                  item: const [],
                  onTap: () async {
                    final response = await http
                        .get(Uri.parse('https://dummyjson.com/users?limit=10'));
                    if (response.statusCode == 200) {
                      Map<String, dynamic> data = json.decode(response.body);
                      List users = data['users'];
                      return users
                          .map((e) => DummyUserModel.fromJson(e))
                          .toList();
                    }
                    return [];
                  },
                  onSearch: (value) async {
                    final response = await http.get(Uri.parse(
                        'https://dummyjson.com/users/search?q=$value'));
                    if (response.statusCode == 200) {
                      Map<String, dynamic> data = json.decode(response.body);
                      List users = data['users'];
                      return users
                          .map((e) => DummyUserModel.fromJson(e))
                          .toList();
                    }
                    return [];
                  },
                  onChanged: (DummyUserModel? value) {
                    // Update state if needed
                  },
                  decoration: SearchFieldDropdownDecoration(
                    textStyle: const TextStyle(
                        fontSize: 12, fontWeight: FontWeight.w400),
                    menuDecoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(4),
                        border: Border.all(color: Colors.blueAccent)),
                    itemPadding:
                        const EdgeInsets.symmetric(horizontal: 5, vertical: 8),
                    fieldDecoration: const InputDecoration(
                      hintText: "Search remote user... (Try 'John')",
                      suffixIcon: Icon(Icons.search, size: 20),
                    ),
                  ),
                  listItemBuilder: (context, item, isSelected) {
                    return Row(
                      children: [
                        CircleAvatar(
                            radius: 14,
                            backgroundImage: NetworkImage(item.image)),
                        const SizedBox(width: 10),
                        Expanded(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(item.name,
                                  style: const TextStyle(
                                      fontSize: 13,
                                      fontWeight: FontWeight.bold)),
                              Text(item.email,
                                  style: const TextStyle(
                                      fontSize: 11, color: Colors.grey)),
                            ],
                          ),
                        ),
                      ],
                    );
                  },
                  selectedItemBuilder: (context, item) {
                    return Text(item.name,
                        style: const TextStyle(
                            fontSize: 12, fontWeight: FontWeight.w400));
                  },
                ),
                const SizedBox(height: 30),
                const Text("API Multi Selection",
                    style:
                        TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
                const SizedBox(height: 8),
                SearchFieldDropdown<DummyUserModel>(
                  initialItems: selectedApiUsers,
                  controller: userApiController,
                  isApiLoading: false,
                  item: const [],
                  onTap: () async {
                    final response = await http
                        .get(Uri.parse('https://dummyjson.com/users?limit=10'));
                    if (response.statusCode == 200) {
                      Map<String, dynamic> data = json.decode(response.body);
                      List users = data['users'];
                      return users
                          .map((e) => DummyUserModel.fromJson(e))
                          .toList();
                    }
                    return [];
                  },
                  onSearch: (value) async {
                    final response = await http.get(Uri.parse(
                        'https://dummyjson.com/users/search?q=$value'));
                    if (response.statusCode == 200) {
                      Map<String, dynamic> data = json.decode(response.body);
                      List users = data['users'];
                      return users
                          .map((e) => DummyUserModel.fromJson(e))
                          .toList();
                    }
                    return [];
                  },
                  onItemsChanged: (List<DummyUserModel> values) {
                    setState(() {
                      selectedApiUsers = values;
                    });
                  },
                  decoration: SearchFieldDropdownDecoration(
                    isMultiSelect: true,
                    showSelectedItemsInField: false,
                    textStyle: const TextStyle(
                        fontSize: 12, fontWeight: FontWeight.w400),
                    menuDecoration: BoxDecoration(
                        color: Colors.white,
                        borderRadius: BorderRadius.circular(4),
                        border: Border.all(color: Colors.blueAccent)),
                    focusedItemDecoration: BoxDecoration(
                        color: Colors.blue.withValues(alpha: 0.1),
                        borderRadius: BorderRadius.circular(2)),
                    unfocusedItemDecoration: BoxDecoration(
                        color: Colors.transparent,
                        borderRadius: BorderRadius.circular(2)),
                    itemPadding:
                        const EdgeInsets.symmetric(horizontal: 5, vertical: 8),
                    fieldDecoration: const InputDecoration(
                      hintText: "Search remote users... (Try 'John')",
                      suffixIcon: Icon(Icons.search, size: 20),
                    ),
                  ),
                  listItemBuilder: (context, item, isSelected) {
                    return Row(
                      children: [
                        CircleAvatar(
                            radius: 14,
                            backgroundImage: NetworkImage(item.image)),
                        const SizedBox(width: 10),
                        Expanded(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              Text(item.name,
                                  style: const TextStyle(
                                      fontSize: 13,
                                      fontWeight: FontWeight.bold)),
                              Text(item.email,
                                  style: const TextStyle(
                                      fontSize: 11, color: Colors.grey)),
                            ],
                          ),
                        ),
                      ],
                    );
                  },
                  selectedItemBuilder: (context, item) {
                    return Text(item.name,
                        style: const TextStyle(
                            fontSize: 12, fontWeight: FontWeight.w400));
                  },
                  selectedItemsBuilder: (context, items) {
                    return items.map((e) => e.name).join(', ');
                  },
                  multiSelectDisplayBuilder:
                      (context, selectedItems, onRemove) {
                    if (selectedItems.isEmpty) return const SizedBox.shrink();
                    return Padding(
                      padding: const EdgeInsets.only(top: 8.0),
                      child: Wrap(
                        spacing: 8.0,
                        runSpacing: 8.0,
                        children: selectedItems.map((user) {
                          return Chip(
                            avatar: CircleAvatar(
                                backgroundImage: NetworkImage(user.image)),
                            label: Text(user.name,
                                style: const TextStyle(fontSize: 12)),
                            deleteIcon: const Icon(Icons.close, size: 16),
                            onDeleted: () => onRemove(user),
                            backgroundColor: Colors.blue.withValues(alpha: 0.1),
                            shape: RoundedRectangleBorder(
                              borderRadius: BorderRadius.circular(16),
                              side: BorderSide(
                                  color: Colors.blue.withValues(alpha: 0.2)),
                            ),
                          );
                        }).toList(),
                      ),
                    );
                  },
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}
7
likes
150
points
259
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A customizable dropdown widget for Flutter with advanced styling, search functionality, and dynamic data handling.

Homepage
Repository (GitHub)
View/report issues

Topics

#searchable-dropdown #flutter-custom-dropdown #search-field-dropdown #search-dropdown #drop-down

License

MIT (license)

Dependencies

cupertino_icons, flutter

More

Packages that depend on search_field_dropdown