Form
Autocomplete
An autocomplete provides a list of suggestions based on the user's input and shows typeahead text for the first match. It is a form-field and can therefore be used in a form.
FAutocomplete(
label: const Text('Autocomplete'),
hint: 'What can it do?',
items: const ['Keyboard navigation', 'Typeahead suggestions', 'Tab to complete'],
);CLI
To generate and customize this style:
dart run forui style create autocompleteUsage
FAutocomplete(...)
FAutocomplete(
controller: FAutocompleteController(vsync: this),
style: (style) => style,
label: const Text('Country'),
description: const Text('Select your country of residence'),
hint: 'Type to search countries',
onChange: (value) => print('Selected country: $value'),
onSaved: (value) => print('Saved country: $value'),
onReset: () => print('Reset'),
autovalidateMode: AutovalidateMode.onUserInteraction,
builder: (context, styles, child) => child!,
prefixBuilder: (context, style, states) => Icon(FIcons.globe),
suffixBuilder: (context, style, states) => Icon(FIcons.search),
contentConstraints: const FAutoWidthPortalConstraints(maxHeight: 400),
clearable: (value) => value.text.isNotEmpty,
rightArrowToComplete: true,
initialText: 'Canada',
items: [
'United States',
'Canada',
'Japan',
'United Kingdom',
'Germany',
'France',
'Australia',
],
);FAutocomplete.builder(...)
FAutocomplete.builder(
filter: (text) async {
const countries = ['United States', 'Canada', 'Japan'];
return countries.where((country) => country.toLowerCase().contains(query.toLowerCase()));
},
contentBuilder: (context, text, suggestions) => [
for (final suggestion in suggestions) FAutocompleteItem(value: suggestion)
],
controller: FAutocompleteController(vsync: this),
style: (style) => style,
label: const Text('Country'),
description: const Text('Select your country of residence'),
hint: 'Type to search countries',
onChange: (value) => print('Selected country: $value'),
onSaved: (value) => print('Saved country: $value'),
onReset: () => print('Reset'),
autovalidateMode: AutovalidateMode.onUserInteraction,
builder: (context, styles, child) => child!,
prefixBuilder: (context, style, states) => Icon(FIcons.globe),
suffixBuilder: (context, style, states) => Icon(FIcons.search),
contentConstraints: const FAutoWidthPortalConstraints(maxHeight: 300),
rightArrowToComplete: true,
clearable: (value) => value.text.isNotEmpty,
);Examples
Detailed
FAutocomplete.builder(
hint: 'Type to search',
filter: (query) => const ['Bug', 'Feature', 'Question'].where((i) => i.toLowerCase().contains(query.toLowerCase())),
contentBuilder: (context, query, suggestions) => [
for (final suggestion in suggestions)
switch (suggestion) {
'Bug' => FAutocompleteItem(
value: 'Bug',
prefix: const Icon(FIcons.bug),
title: const Text('Bug'),
subtitle: const Text('An unexpected problem or behavior'),
),
'Feature' => FAutocompleteItem(
value: 'Feature',
prefix: const Icon(FIcons.filePlus2),
title: const Text('Feature'),
subtitle: const Text('A new feature or enhancement'),
),
'Question' => FAutocompleteItem(
value: 'Question',
prefix: const Icon(FIcons.messageCircleQuestionMark),
title: const Text('Question'),
subtitle: const Text('A question or clarification'),
),
_ => FAutocompleteItem(value: suggestion),
},
],
);Sections
const _timezones = {
'North America': [
'Eastern Standard Time (EST)',
'Central Standard Time (CST)',
'Mountain Standard Time (MST)',
'Pacific Standard Time (PST)',
'Alaska Standard Time (AKST)',
'Hawaii Standard Time (HST)',
],
'South America': [
'Argentina Time (ART)',
'Bolivia Time (BOT)',
'Brasilia Time (BRT)',
'Chile Standard Time (CLT)',
],
'Europe & Africa': [
'Greenwich Mean Time (GMT)',
'Central European Time (CET)',
'Eastern European Time (EET)',
'Western European Summer Time (WEST)',
'Central Africa Time (CAT)',
'Eastern Africa Time (EAT)',
],
'Asia': [
'Moscow Time (MSK)',
'India Standard Time (IST)',
'China Standard Time (CST)',
'Japan Standard Time (JST)',
'Korea Standard Time (KST)',
'Indonesia Standard Time (IST)',
],
'Australia & Pacific': [
'Australian Western Standard Time (AWST)',
'Australian Central Standard Time (ACST)',
'Australian Eastern Standard Time (AEST)',
'New Zealand Standard Time (NZST)',
'Fiji Time (FJT)',
],
};
FAutocomplete.builder(
hint: 'Type to search timezones',
filter: (query) => timezones.values
.expand((list) => list)
.where((timezone) => timezone.toLowerCase().contains(query.toLowerCase())),
contentBuilder: (context, query, suggestions) => [
for (final MapEntry(key: label, value: zones) in timezones.entries)
if (zones.where(suggestions.contains).toList() case final zones when zones.isNotEmpty)
FAutocompleteSection(label: Text(label), items: zones),
],
);Dividers
FAutocomplete.builder(
hint: 'Type to search levels',
filter: (query) => const ['1A', '1B', '2A', '2B', '3', '4'].where((i) => i.toLowerCase().contains(query.toLowerCase())),
contentBuilder: (context, query, suggestions) => <FAutocompleteItemMixin>[
FAutocompleteRichSection(
label: const Text('Level 1'),
divider: FItemDivider.indented,
children: [
if (suggestions.contains('1A')) FAutocompleteItem(value: '1A'),
if (suggestions.contains('1B')) FAutocompleteItem(value: '1B'),
],
),
FAutocompleteSection(label: const Text('Level 2'), items: ['2A', '2B'].where(suggestions.contains).toList()),
if (suggestions.contains('3')) FAutocompleteItem(value: '3'),
if (suggestions.contains('4')) FAutocompleteItem(value: '4'),
].where((item) => item is! FAutocompleteSection || item.children.isNotEmpty).toList(),
);Behavior
Async
const fruits = ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry', 'Pineapple'];
FAutocomplete.builder(
hint: 'Type to search fruits',
filter: (query) async {
await Future.delayed(const Duration(seconds: 3));
return query.isEmpty ? fruits : fruits.where((fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()));
},
contentBuilder: (context, query, values) => [
for (final fruit in values) FAutocompleteItem(value: fruit),
],
);Async with Custom Loading
const fruits = ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry', 'Pineapple'];
FAutocomplete.builder(
hint: 'Type to search fruits',
filter: (query) async {
await Future.delayed(const Duration(seconds: 3));
return query.isEmpty ? fruits : fruits.where((fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()));
},
contentBuilder: (context, query, suggestions) => [
for (final suggestion in suggestions) FAutocompleteItem(value: suggestion),
],
contentLoadingBuilder: (context, style) => Padding(
padding: const EdgeInsets.all(14.0),
child: Text('Here be dragons...', style: style.emptyTextStyle),
),
);Async with Custom Error Handling
FAutocomplete.builder(
hint: 'Type to search fruits',
filter: (query) async {
await Future.delayed(const Duration(seconds: 3));
throw StateError('Error loading data');
},
contentBuilder: (context, query, values) => [for (final fruit in values) FAutocompleteItem(value: fruit)],
contentErrorBuilder: (context, error, trace) => Padding(
padding: const EdgeInsets.all(14.0),
child: Icon(FIcons.circleX, size: 15, color: context.theme.colors.primary),
),
);Clearable
FAutocomplete(
hint: 'Type to search fruits',
clearable: (value) => value.text.isNotEmpty,
items: const ['Apple', 'Banana', 'Orange', 'Grape', 'Strawberry', 'Pineapple'],
);Form
class FormAutocompletePage extends StatefulWidget {
@override
State<FormAutocompletePage> createState() => _FormAutocompletePageState();
}
class _FormAutocompletePageState extends State<FormAutocompletePage> {
final _key = GlobalKey<FormState>();
@override
Widget build(BuildContext context) => Form(
key: _key,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
FAutocomplete(
label: const Text('Department'),
description: const Text('Type to search your dream department'),
hint: 'Search departments',
validator: (department) => department == null || department.isEmpty ? 'Please select a department' : null,
items: const ['Engineering', 'Marketing', 'Sales', 'Human Resources', 'Finance'],
),
const SizedBox(height: 25),
FButton(
child: const Text('Submit'),
onPress: () {
if (_key.currentState!.validate()) {
// Form is valid, do something with department.
}
},
),
],
),
);
}