dropinity 0.0.2 copy "dropinity: ^0.0.2" to clipboard
dropinity: ^0.0.2 copied to clipboard

a powerful and customizable paginated dropdown widget for Flutter

Dropinity #

A customizable Flutter dropdown widget with search functionality and pagination support for both local and remote data.

Features #

  • πŸ” Real-time search filtering
  • πŸ“‘ API integration with pagination (via Pagify)
  • πŸ’Ύ Local and remote data support
  • 🎨 Fully customizable UI
  • 🎭 Smooth animations
  • 🎯 Type-safe generics

Installation #

dependencies:
  dropinity: ^0.0.2

Quick Start #

Local Mode #

typedef DropinityLocal<Model> = Dropinity<void, Model>;

DropinityLocal<String>(
  controller: DropinityController(),
  buttonData: ButtonData(
    hint: Text('Select country'),
    selectedItemWidget: (country) => Text(country ?? ''),
  ),
  textFieldData: TextFieldData(
    onSearch: (pattern, country) =>
      country?.toLowerCase().contains(pattern?.toLowerCase() ?? '') ?? false,
  ),
  values: ['USA', 'Canada', 'Mexico'],
  valuesData: ValuesData(
    itemBuilder: (context, i, country) => ListTile(title: Text(country)),
  ),
  onChanged: (country) => print('Selected: $country'),
)

API Mode #

final controller = PagifyController<User>();

Dropinity<ApiResponse, User>.withApiRequest(
  controller: DropinityController(),
  buttonData: ButtonData(
    hint: Text('Select user'),
    selectedItemWidget: (user) => Text(user?.name ?? ''),
  ),
  textFieldData: TextFieldData(
    onSearch: (pattern, user) =>
      user?.name.toLowerCase().contains(pattern?.toLowerCase() ?? '') ?? false,
  ),
  pagifyData: DropinityPagifyData(
    controller: controller,
    asyncCall: (context, page) => ApiService.getUsers(page: page),
    mapper: (response) => PagifyData(
      data: response.users,
      total: response.total,
      totalPages: response.totalPages,
    ),
    errorMapper: (response) => response.error,
    itemBuilder: (context, data, index, user) => ListTile(
      title: Text(user.name),
      subtitle: Text(user.email),
    ),
  ),
  onChanged: (user) => print('Selected: ${user.name}'),
)

Controller #

Control the dropdown programmatically:

final controller = DropinityController();

// Methods
controller.expand();      // Open dropdown
controller.collapse();    // Close dropdown

// Properties
controller.isExpanded;    // Check if open
controller.isCollapsed;   // Check if closed

// Example usage
ElevatedButton(
  onPressed: () {
    if (controller.isExpanded) {
      controller.collapse();
    } else {
      controller.expand();
    }
  },
  child: Text('Toggle'),
)

// Don't forget to dispose
@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Configuration #

ButtonData #

ButtonData<Model>(
  required selectedItemWidget: (item) => Widget,
  hint: Widget?,                    // Placeholder text
  buttonWidth: double,              // Default: double.infinity
  buttonHeight: double,             // Default: 50
  color: Color?,                    // Background color
  buttonBorderColor: Color?,
  buttonBorderRadius: BorderRadius?,
  padding: EdgeInsetsGeometry?,
  expandedListIcon: Widget?,        // Icon when open
  collapsedListIcon: Widget?,       // Icon when closed
)

TextFieldData #

TextFieldData<Model>(
  required onSearch: (pattern, item) => bool,
  controller: TextEditingController?,
  title: String?,                   // Label text
  prefixIcon: Widget?,
  suffixIcon: Widget?,
  borderRadius: double?,
  borderColor: Color?,
  contentPadding: EdgeInsetsGeometry?,
  fillColor: Color?,
  maxLength: int?,
  style: TextStyle?,
)

ValuesData (Local Mode) #

ValuesData<Model>(
  required itemBuilder: (context, index, item) => Widget,
)

DropinityPagifyData (API Mode) #

DropinityPagifyData<FullResponse, Model>(
  required controller: PagifyController<Model>,
  required asyncCall: (context, page) => Future<FullResponse>,
  required mapper: (response) => PagifyData<Model>,
  required errorMapper: PagifyErrorMapper,
  required itemBuilder: (context, data, index, item) => Widget,
  padding: EdgeInsetsGeometry,      // Default: EdgeInsets.zero
  itemExtent: double?,
  loadingBuilder: Widget?,
  errorBuilder: (error) => Widget?,
  emptyListView: Widget?,
  noConnectionText: String?,
  // Callbacks
  onUpdateStatus: (status) => void,
  onLoading: () => void,
  onSuccess: (context, data) => void,
  onError: (context, code, error) => void,
)

Styling Example #

Dropinity<void, Product>(
  controller: DropinityController(),
  listHeight: 350,
  curve: Curves.easeInOut,
  listBackgroundColor: Colors.grey[50]!,
  buttonData: ButtonData(
    buttonHeight: 60,
    color: Colors.blue[50],
    buttonBorderRadius: BorderRadius.circular(16),
    buttonBorderColor: Colors.blue,
    padding: EdgeInsets.all(16),
    hint: Row(
      children: [
        Icon(Icons.shopping_cart, color: Colors.blue),
        SizedBox(width: 8),
        Text('Select product'),
      ],
    ),
    selectedItemWidget: (product) => Text(product?.name ?? ''),
  ),
  textFieldData: TextFieldData(
    title: 'Search products',
    prefixIcon: Icon(Icons.search),
    borderRadius: 12,
    fillColor: Colors.white,
    onSearch: (pattern, product) =>
      product?.name.toLowerCase().contains(pattern?.toLowerCase() ?? '') ?? false,
  ),
  values: products,
  valuesData: ValuesData(
    itemBuilder: (context, i, product) => Container(
      padding: EdgeInsets.all(12),
      child: Text(product.name),
    ),
  ),
  onChanged: (product) => print(product.name),
)

Form Integration #

class MyForm extends StatefulWidget {
  @override
  _MyFormState createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
  Country? selectedCountry;
  City? selectedCity;
  
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Dropinity<void, Country>(
          controller: DropinityController(),
          buttonData: ButtonData(
            hint: Text('Select country'),
            selectedItemWidget: (c) => Text(c?.name ?? ''),
          ),
          textFieldData: TextFieldData(
            onSearch: (p, c) => c?.name.contains(p ?? '') ?? false,
          ),
          values: countries,
          valuesData: ValuesData(
            itemBuilder: (ctx, i, c) => ListTile(title: Text(c.name)),
          ),
          onChanged: (country) {
            setState(() {
              selectedCountry = country;
              selectedCity = null;
            });
          },
        ),
        
        if (selectedCountry != null)
          Dropinity<void, City>(
            controller: DropinityController(),
            buttonData: ButtonData(
              hint: Text('Select city'),
              selectedItemWidget: (c) => Text(c?.name ?? ''),
            ),
            textFieldData: TextFieldData(
              onSearch: (p, c) => c?.name.contains(p ?? '') ?? false,
            ),
            values: selectedCountry!.cities,
            valuesData: ValuesData(
              itemBuilder: (ctx, i, c) => ListTile(title: Text(c.name)),
            ),
            onChanged: (city) => setState(() => selectedCity = city),
          ),
      ],
    );
  }
}

Best Practices #

  • Use API mode for datasets > 1000 items
  • Always dispose controllers
  • Use typedef DropinityLocal<T> = Dropinity<void, T> for cleaner local mode syntax
  • Implement efficient onSearch logic
  • Use listHeight to prevent overflow
@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Made with ❀️ by Ahmed Emara
LinkedIn

8
likes
150
points
--
downloads

Publisher

unverified uploader

Weekly Downloads

a powerful and customizable paginated dropdown widget for Flutter

Repository (GitHub)
View/report issues

Topics

#dropdown #pagination #lazyloading #network #api

Documentation

API reference

License

MIT (license)

Dependencies

flutter, pagify

More

Packages that depend on dropinity