searchable_dropdown_form_field 0.1.1
searchable_dropdown_form_field: ^0.1.1 copied to clipboard
A customizable, searchable Flutter dropdown with form validation and generic value support.
example/lib/main.dart
import 'package:searchable_dropdown_form_field/searchable_dropdown_form_field.dart';
import 'package:flutter/material.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'),
];
Future<List<DropdownItem<String>>> _loadCountries(String query) async {
await Future<void>.delayed(const Duration(milliseconds: 400));
final normalizedQuery = query.toLowerCase();
return _countries
.where((item) => item.label.toLowerCase().contains(normalizedQuery))
.toList();
}
@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>(
value: _remoteCountry,
items: const [],
hintText: 'Search remote countries',
dialogTitle: 'Async bottom sheet',
popupMode: DropdownPopupMode.bottomSheet,
asyncItemsLoader: _loadCountries,
searchDebounceDuration: const Duration(milliseconds: 300),
showClearButton: true,
decoration: const InputDecoration(
labelText: 'Remote country',
border: OutlineInputBorder(),
),
onChanged: (value) => setState(() => _remoteCountry = value),
),
const SizedBox(height: 24),
FilledButton(
onPressed: () => _formKey.currentState!.validate(),
child: const Text('Submit'),
),
],
),
),
);
}
}