dropdown_plus_bloc 0.1.6 copy "dropdown_plus_bloc: ^0.1.6" to clipboard
dropdown_plus_bloc: ^0.1.6 copied to clipboard

Customizable Flutter dropdowns with BLoC/Cubit integration: searchable single-select and multi-select with chips, offline caching, and theming.

dropdown_plus #

pub package License: MIT

A highly customisable Flutter dropdown package with optional BLoC / Cubit integration. Use *Plus widgets with a cubit, or plain widgets with your own state.

Widget Description
SearchableDropdownPlus Single-select searchable dropdown (BLoC/Cubit)
MultiSelectDropdownPlus Multi-select with chips (BLoC/Cubit)
SearchableDropdown Single-select searchable dropdown (no BLoC β€” pass items / isLoading)
MultiSelectDropdown Multi-select with chips (no BLoC β€” pass items / isLoading)

Features #

  • πŸ”Œ BLoC / Cubit integration β€” SearchableDropdownPlus / MultiSelectDropdownPlus wire to any Cubit or Bloc
  • πŸ“¦ Plain StatefulWidget API β€” SearchableDropdown / MultiSelectDropdown work with setState, Provider, Riverpod, etc.
  • πŸ” Real-time search β€” calls your cubit's search method as the user types
  • πŸ“΄ Offline caching β€” falls back to client-side filtering when no internet is available
  • 🎨 Preset + custom theming β€” use themeStyle for out-of-the-box looks or DropdownPlusTheme for full control
  • 🧩 Custom builders β€” override item rows, chip display, and the trigger button content
  • πŸ”„ Controlled mode β€” sync selected value(s) from external state (e.g. QR scan, form reset)
  • βœ… Multi-select helpers β€” "Select All" / "Clear All" header, "+N more" overflow chip
  • 🎞 Smooth animations β€” animated open/close, arrow rotation, item selection

Screenshots #

Single select #

Single select demo

Multi select #

Multi select demo   Multi select with selection

Installation #

Add to your pubspec.yaml:

dependencies:
  dropdown_plus: ^0.1.0
  flutter_bloc: ">=8.0.0 <10.0.0"   # only transitive dep needed

Then run:

flutter pub get

Quick Start #

import 'package:dropdown_plus/dropdown_plus.dart';

Single Select #

SearchableDropdownPlus<WorkerCubit, WorkerState>(
  cubit: context.read<WorkerCubit>(),
  hintText: 'Search worker…',
  onSearch: (query) => context.read<WorkerCubit>().search(query),
  onStateChange: (state, updateList, updateLoading) {
    if (state is WorkersLoaded) {
      updateList(
        state.workers
            .map((w) => DropdownItem(value: w, label: w.name))
            .toList(),
      );
      updateLoading(false);
    } else if (state is WorkersLoading) {
      updateLoading(true);
    } else if (state is WorkersError) {
      updateLoading(false);
    }
  },
  onSelectionChanged: (item) {
    final worker = item.value as Worker;
    // use worker
  },
)

Multi Select #

MultiSelectDropdownPlus<WorkerCubit, WorkerState>(
  cubit: context.read<WorkerCubit>(),
  hintText: 'Select workers…',
  onSearch: (query) => context.read<WorkerCubit>().search(query),
  onStateChange: (state, updateList, updateLoading) {
    if (state is WorkersLoaded) {
      updateList(
        state.workers
            .map((w) => DropdownItem(value: w, label: '${w.name} (${w.id})'))
            .toList(),
      );
      updateLoading(false);
    } else if (state is WorkersLoading) {
      updateLoading(true);
    }
  },
  onSelectionChanged: (items) {
    final workers = items.map((e) => e.value as Worker).toList();
    // use workers
  },
)

Without BLoC #

Pass the current item list and loading flag from your own state. If onSearch is omitted, the search box filters locally over items. If onSearch is set, call your API and rebuild with updated items / isLoading.

Single select

SearchableDropdown(
  hintText: 'Search worker…',
  items: workers,
  isLoading: loadingWorkers,
  selectedValue: selectedWorkerItem,
  onSearch: (query) async {
    setState(() => loadingWorkers = true);
    final list = await api.searchWorkers(query);
    setState(() {
      workers = list.map((w) => DropdownItem(value: w, label: w.name)).toList();
      loadingWorkers = false;
    });
  },
  onSelectionChanged: (item) =>
      setState(() => selectedWorkerItem = item),
)

Multi select

MultiSelectDropdown(
  hintText: 'Select workers…',
  items: workers,
  isLoading: loadingWorkers,
  selectedItems: selectedWorkerItems,
  onSearch: (query) async {
    setState(() => loadingWorkers = true);
    final list = await api.searchWorkers(query);
    setState(() {
      workers = list.map((w) => DropdownItem(value: w, label: w.name)).toList();
      loadingWorkers = false;
    });
  },
  onSelectionChanged: (items) =>
      setState(() => selectedWorkerItems = items),
)

Theme Customisation #

Pass a DropdownPlusTheme to any dropdown widget (*Plus or plain) to change its appearance:

SearchableDropdownPlus(
  ...
  dropdownTheme: DropdownPlusTheme(
    // Trigger button
    backgroundColor: Colors.grey[100],
    borderColor: Colors.grey[300],
    activeBorderColor: Colors.deepPurple,
    borderRadius: 12,

    // Hint & text
    hintStyle: TextStyle(color: Colors.grey, fontSize: 14),
    triggerTextStyle: TextStyle(color: Colors.black87, fontWeight: FontWeight.w600),

    // Dropdown panel
    menuBackgroundColor: Colors.white,
    menuBorderRadius: 16,
    menuElevation: 8,
    menuMaxHeight: 280,

    // Search bar
    searchBarBackgroundColor: Colors.grey[50],
    searchHintStyle: TextStyle(color: Colors.grey),
    searchIconColor: Colors.grey,

    // Items
    itemTextStyle: TextStyle(color: Colors.black87),
    selectedItemTextStyle: TextStyle(color: Colors.deepPurple, fontWeight: FontWeight.bold),
    selectedItemBackgroundColor: Colors.deepPurple.withValues(alpha: 0.08),

    // Loading / empty
    loadingIndicatorColor: Colors.deepPurple,
    noResultsTextStyle: TextStyle(color: Colors.grey),
  ),
)

Preset Theme Styles (themeStyle) #

Use themeStyle when you want a ready-to-use UI without configuring every field:

SearchableDropdownPlus<WorkerCubit, WorkerState>(
  cubit: context.read<WorkerCubit>(),
  hintText: 'Search worker…',
  themeStyle: DropdownPlusThemeStyle.compact,
  onSearch: (query) => context.read<WorkerCubit>().search(query),
  onStateChange: (state, updateList, updateLoading) {
    // ...
  },
)

Available presets:

Enum Value Style
DropdownPlusThemeStyle.material Default Material-like appearance
DropdownPlusThemeStyle.minimal Light borders and subtle surfaces
DropdownPlusThemeStyle.rounded Larger radius and softer card-like look
DropdownPlusThemeStyle.outlined Strong border-focused style
DropdownPlusThemeStyle.dark Dark surfaces with light foregrounds
DropdownPlusThemeStyle.compact Dense spacing and smaller visuals

You can also start from a preset and override specific properties:

MultiSelectDropdownPlus<WorkerCubit, WorkerState>(
  cubit: context.read<WorkerCubit>(),
  hintText: 'Select workers…',
  themeStyle: DropdownPlusThemeStyle.dark,
  dropdownTheme: DropdownPlusThemePresets
      .forStyle(DropdownPlusThemeStyle.dark)
      .copyWith(borderRadius: 16),
  onSearch: (query) => context.read<WorkerCubit>().search(query),
  onStateChange: (state, updateList, updateLoading) {
    // ...
  },
)

dropdownTheme takes precedence over themeStyle when both are provided.

Dark Theme Example #

dropdownTheme: DropdownPlusTheme(
  backgroundColor: const Color(0xFF1E1E2E),
  menuBackgroundColor: const Color(0xFF2A2A3E),
  borderColor: Colors.white12,
  activeBorderColor: Colors.blueAccent,
  hintStyle: TextStyle(color: Colors.white38),
  triggerTextStyle: TextStyle(color: Colors.white),
  itemTextStyle: TextStyle(color: Colors.white70),
  selectedItemTextStyle: TextStyle(color: Colors.blueAccent, fontWeight: FontWeight.w600),
  selectedItemBackgroundColor: Colors.blueAccent.withValues(alpha:0.15),
  dividerColor: Colors.white10,
  searchBarBackgroundColor: Colors.white10,
  searchHintStyle: TextStyle(color: Colors.white38),
  searchTextStyle: TextStyle(color: Colors.white),
  searchIconColor: Colors.white38,
  chipBackgroundColor: Colors.blueAccent.withValues(alpha: 0.2),
  chipTextStyle: TextStyle(color: Colors.blueAccent),
  chipBorderColor: Colors.blueAccent.withValues(alpha: 0.4),
  loadingIndicatorColor: Colors.blueAccent,
  checkboxActiveColor: Colors.blueAccent,
  headerBackgroundColor: const Color(0xFF252535),
  arrowIconColor: Colors.white54,
)

Property Type Default Description
backgroundColor Color? Colors.white Trigger button background
borderColor Color? outline@50% Border colour when closed
activeBorderColor Color? primary Border colour when open
borderWidth double 1.0 Border width when closed
activeBorderWidth double 1.5 Border width when open
borderRadius double 10.0 Trigger corner radius
contentPadding EdgeInsets? h14 v12 Trigger inner padding
hintStyle TextStyle? onSurface@60% Placeholder text style
triggerTextStyle TextStyle? bodyMedium Selected value text style (single)
menuBackgroundColor Color? Colors.white Panel background
menuBorderRadius double 12.0 Panel corner radius
menuElevation double 12.0 Panel shadow elevation
menuMaxHeight double 320.0 Panel max height
menuBorderColor Color? outline@20% Panel border colour
searchBarBackgroundColor Color? surface@30% Search input container
searchHintStyle TextStyle? onSurface@50% Search hint
searchTextStyle TextStyle? theme default Search input text
searchIconColor Color? onSurface@50% Search icon
itemTextStyle TextStyle? onSurface 14sp Normal item text
selectedItemTextStyle TextStyle? primary w500 Selected item text
selectedItemBackgroundColor Color? primaryContainer@30% Selected item row bg
itemPadding EdgeInsets? h16 v12 Item row padding
dividerColor Color? outline@8% Divider between items
checkboxBorderColor Color? outline@40% Circle checkbox border (unselected)
checkboxActiveColor Color? primary Circle checkbox fill (selected)
checkboxSize double 22.0 Circle checkbox diameter
chipBackgroundColor Color? primary@10% Chip background
chipTextStyle TextStyle? primary w500 12sp Chip text
chipBorderColor Color? primary@30% Chip border
chipBorderRadius double 16.0 Chip corner radius
chipDeleteIconColor Color? primary Chip Γ— icon colour
chipDeleteIconSize double 14.0 Chip Γ— icon size
countChipBackgroundColor Color? surfaceContainerHighest "+N more" chip background
countChipTextStyle TextStyle? onSurface@70% "+N more" chip text
loadingIndicatorColor Color? primary Spinner colour
loadingTextStyle TextStyle? onSurface@60% 13sp Loading message style
noResultsTextStyle TextStyle? onSurface@60% 13sp No results message style
noResultsIconColor Color? onSurface@40% No results icon colour
arrowIconColor Color? onSurface@60% Caret icon colour
arrowIconSize double 22.0 Caret icon size
headerBackgroundColor Color? surface@30% Multi-select header row bg
selectAllTextStyle TextStyle? primary w600 13sp "Select All" button style
selectedCountTextStyle TextStyle? primary w600 11sp "N selected" badge text
selectedCountBackgroundColor Color? primary@10% "N selected" badge bg

API Reference #

SearchableDropdownPlus<C, S> #

Parameter Type Required Description
cubit C βœ… BLoC/Cubit instance
onSearch void Function(String) βœ… Called on every search change
onStateChange void Function(S, updateList, updateLoading) βœ… Maps state to list/loading updates
hintText String βœ… Placeholder text
selectedValue DropdownItem? β€” Pre-selected value (controlled mode)
onSelectionChanged void Function(DropdownItem)? β€” User selection callback
searchHint String? β€” Search input placeholder
noResultsText String? β€” Empty-state message
loadingText String? β€” Loading-state message
needInitialFetch bool β€” Trigger search on mount (default: false)
dropdownTheme DropdownPlusTheme? β€” Visual customisation
themeStyle DropdownPlusThemeStyle? β€” Preset style (ignored when dropdownTheme is set)
itemBuilder Widget Function(item, isSelected)? β€” Custom item row
selectedValueBuilder Widget Function(item)? β€” Custom trigger content
checkInternetConnection Future<bool> Function()? β€” Custom connectivity check

MultiSelectDropdownPlus<C, S> #

All parameters from SearchableDropdownPlus plus:

Parameter Type Default Description
selectedItems List<DropdownItem> [] Pre-selected items (controlled)
onSelectionChanged void Function(List<DropdownItem>)? β€” Selection change callback
maxDisplayChips int 2 Max chips before "+N more" overflow
selectedItemBuilder Widget Function(List<DropdownItem>)? β€” Custom chips display
buttonHeight double? β€” Fixed trigger height
buttonWidth double? β€” Fixed trigger width

SearchableDropdown #

Parameter Type Required Description
hintText String βœ… Placeholder text
items List<DropdownItem> βœ… Items to show (update from parent when search results change)
isLoading bool βœ… Shows loading UI in trigger and panel when empty
onSearch void Function(String)? β€” Remote search on each keystroke when online; omit for local-only filter
selectedValue DropdownItem? β€” Controlled selection
onSelectionChanged void Function(DropdownItem)? β€” User picked an item
searchHint String? β€” Search placeholder
noResultsText String? β€” Empty state text
loadingText String? β€” Loading message
needInitialFetch bool β€” If true and onSearch is set, calls onSearch('') on mount
dropdownTheme DropdownPlusTheme? β€” Theme overrides
themeStyle DropdownPlusThemeStyle? β€” Preset style
itemBuilder Widget Function(item, isSelected)? β€” Custom row
selectedValueBuilder Widget Function(item)? β€” Custom trigger when selected
checkInternetConnection Future<bool> Function()? β€” Offline β†’ local filter

MultiSelectDropdown #

Same parameters as SearchableDropdown, plus:

Parameter Type Default Description
selectedItems List<DropdownItem> [] Controlled selection
onSelectionChanged void Function(List<DropdownItem>)? β€” Selection changed
maxDisplayChips int 2 Chips before "+N more"
selectedItemBuilder Widget Function(List<DropdownItem>)? β€” Custom chip row in trigger
buttonHeight double? β€” Fixed trigger height
buttonWidth double? β€” Fixed trigger width

Offline Caching #

Provide checkInternetConnection to enable offline fallback:

SearchableDropdownPlus(
  ...
  checkInternetConnection: () async {
    final result = await Connectivity().checkConnectivity();
    return result != ConnectivityResult.none;
  },
)

When offline, the widget performs client-side filtering on the cached item list instead of calling onSearch.


Controlled Mode #

Use selectedValue / selectedItems to sync selection with external state (e.g. form reset, QR scan):

// For QR scan β€” increment key to force re-sync
SearchableDropdownPlus(
  key: ValueKey(qrKey),
  selectedValue: scannedItem,
  ...
)

Custom Builders #

Custom item row #

SearchableDropdownPlus(
  ...
  itemBuilder: (item, isSelected) {
    final worker = item.value as Worker;
    return ListTile(
      leading: CircleAvatar(child: Text(worker.name[0])),
      title: Text(worker.name),
      subtitle: Text(worker.department),
      trailing: isSelected ? Icon(Icons.check, color: Colors.green) : null,
    );
  },
)

Custom selected chips (multi-select) #

MultiSelectDropdownPlus(
  ...
  selectedItemBuilder: (selected) => Text(
    selected.map((e) => e.label).join(' β€’ '),
    overflow: TextOverflow.ellipsis,
  ),
)

License #

MIT Β© Lidhin

2
likes
160
points
100
downloads

Documentation

API reference

Publisher

verified publisherlidhin.com

Weekly Downloads

Customizable Flutter dropdowns with BLoC/Cubit integration: searchable single-select and multi-select with chips, offline caching, and theming.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter, flutter_bloc

More

Packages that depend on dropdown_plus_bloc