searchable_dropdown_form_field 0.2.0 copy "searchable_dropdown_form_field: ^0.2.0" to clipboard
searchable_dropdown_form_field: ^0.2.0 copied to clipboard

A customizable, searchable Flutter dropdown with form validation and generic value support.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:searchable_dropdown_form_field/searchable_dropdown_form_field.dart';

void main() => runApp(const ExampleApp());

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Searchable dropdown example',
      theme: ThemeData(colorSchemeSeed: Colors.indigo),
      home: const ExamplePage(),
    );
  }
}

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

  @override
  State<ExamplePage> createState() => _ExamplePageState();
}

class _ExamplePageState extends State<ExamplePage> {
  final _formKey = GlobalKey<FormState>();
  String? _country;
  String? _remoteCountry;

  static const _countries = [
    DropdownItem(label: 'Bangladesh', value: 'BD'),
    DropdownItem(label: 'India', value: 'IN'),
    DropdownItem(label: 'Nepal', value: 'NP'),
    DropdownItem(label: 'Sri Lanka', value: 'LK'),
  ];

  static final _remoteCountries = List.generate(
    45,
    (index) => DropdownItem(
      label: 'Remote country ${index + 1}',
      value: 'REMOTE_${index + 1}',
    ),
  );

  Future<List<DropdownItem<String>>> _loadCountryPage(
    String query,
    int page,
  ) async {
    await Future<void>.delayed(const Duration(milliseconds: 400));
    final normalizedQuery = query.toLowerCase();
    final matches = _remoteCountries
        .where((item) => item.label.toLowerCase().contains(normalizedQuery))
        .toList();
    const pageSize = 12;
    final start = (page - 1) * pageSize;
    if (start >= matches.length) return [];
    final end = (start + pageSize).clamp(0, matches.length).toInt();
    return matches.sublist(start, end);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Searchable dropdown')),
      body: Form(
        key: _formKey,
        child: ListView(
          padding: const EdgeInsets.all(24),
          children: [
            CustomSearchableDropdown<String>(
              value: _country,
              dialogTitle: 'Select a country',
              hintText: 'Choose a country',
              decoration: InputDecoration(
                labelText: 'Country',
                helperText: 'Search and select your country',
                prefixIcon: const Icon(Icons.public),
                filled: true,
                fillColor: Theme.of(
                  context,
                ).colorScheme.surfaceContainerHighest,
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
              searchDecoration: InputDecoration(
                hintText: 'Type a country name',
                prefixIcon: const Icon(Icons.travel_explore),
                filled: true,
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.circular(12),
                ),
              ),
              textStyle: const TextStyle(
                color: Colors.indigo,
                fontWeight: FontWeight.w600,
              ),
              dialogTitleStyle: Theme.of(context)
                  .textTheme
                  .headlineSmall
                  ?.copyWith(fontWeight: FontWeight.bold),
              searchTextStyle: const TextStyle(fontSize: 16),
              itemTextStyle: const TextStyle(fontSize: 16),
              emptyResultTextStyle: const TextStyle(
                color: Colors.grey,
                fontStyle: FontStyle.italic,
              ),
              dialogShape: RoundedRectangleBorder(
                borderRadius: BorderRadius.circular(20),
              ),
              dialogContentPadding: const EdgeInsets.fromLTRB(24, 12, 24, 24),
              itemPadding: const EdgeInsets.symmetric(horizontal: 8),
              showClearButton: true,
              items: _countries,
              searchMatcher: (item, query) =>
                  item.label.toLowerCase().contains(query.toLowerCase()) ||
                  item.value.toLowerCase().contains(query.toLowerCase()),
              validator: (value) =>
                  value == null ? 'Please select a country' : null,
              onChanged: (value) => setState(() => _country = value),
            ),
            const SizedBox(height: 24),
            CustomSearchableDropdown<String>.async(
              value: _remoteCountry,
              hintText: 'Search paginated countries',
              dialogTitle: 'Infinite pagination',
              popupMode: DropdownPopupMode.bottomSheet,
              asyncPageLoader: _loadCountryPage,
              searchDebounceDuration: const Duration(milliseconds: 300),
              paginationTriggerDistance: 120,
              showClearButton: true,
              semanticLabel: 'Paginated remote country',
              searchSemanticLabel: 'Search remote country pages',
              listPadding: const EdgeInsets.symmetric(vertical: 8),
              separatorBuilder: (context, index) => const Divider(height: 1),
              asyncErrorBuilder: (context, error, retry) => Column(
                mainAxisSize: MainAxisSize.min,
                children: [
                  Text('Unable to load countries: $error'),
                  const SizedBox(height: 8),
                  OutlinedButton.icon(
                    onPressed: retry,
                    icon: const Icon(Icons.refresh),
                    label: const Text('Retry'),
                  ),
                ],
              ),
              decoration: const InputDecoration(
                labelText: 'Paginated country',
                helperText: 'Scroll to load the next page',
                border: OutlineInputBorder(),
              ),
              onChanged: (value) => setState(() => _remoteCountry = value),
            ),
            const SizedBox(height: 24),
            FilledButton(
              onPressed: () => _formKey.currentState!.validate(),
              child: const Text('Submit'),
            ),
          ],
        ),
      ),
    );
  }
}
3
likes
160
points
205
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

A customizable, searchable Flutter dropdown with form validation and generic value support.

Homepage

License

MIT (license)

Dependencies

flutter

More

Packages that depend on searchable_dropdown_form_field