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.

1@override
2Widget build(BuildContext _) => FAutocomplete(
3 label: const Text('Autocomplete'),
4 hint: 'What can it do?',
5 items: features,
6);
7

CLI

To generate and customize this style:

dart run forui style create autocomplete

Usage

FAutocomplete(...)

1FAutocomplete(
2 style: const .delta(fieldStyle: .delta(contentPadding: .zero)),
3 enabled: true,
4 hint: 'Hint',
5 items: const ['Apple', 'Banana', 'Cherry'],
6)

FAutocomplete.builder(...)

1FAutocomplete.builder(
2 style: const .delta(fieldStyle: .delta(contentPadding: .zero)),
3 enabled: true,
4 filter: (query) => [
5 'Apple',
6 'Banana',
7 ].where((item) => item.toLowerCase().startsWith(query.toLowerCase())),
8 contentBuilder: (context, query, values) => [
9 for (final value in values) .item(value: value),
10 ],
11)

Examples

Detailed

1@override
2Widget build(BuildContext _) => FAutocomplete.builder(
3 hint: 'Type to search',
4 filter: (query) => const [
5 'Bug',
6 'Feature',
7 'Question',
8 ].where((i) => i.toLowerCase().contains(query.toLowerCase())),
9 contentBuilder: (context, query, suggestions) => [
10 for (final suggestion in suggestions)
11 switch (suggestion) {
12 'Bug' => .item(
13 value: 'Bug',
14 prefix: const Icon(FIcons.bug),
15 title: const Text('Bug'),
16 subtitle: const Text('An unexpected problem or behavior'),
17 ),
18 'Feature' => .item(
19 value: 'Feature',
20 prefix: const Icon(FIcons.filePlusCorner),
21 title: const Text('Feature'),
22 subtitle: const Text('A new feature or enhancement'),
23 ),
24 'Question' => .item(
25 value: 'Question',
26 prefix: const Icon(FIcons.messageCircleQuestionMark),
27 title: const Text('Question'),
28 subtitle: const Text('A question or clarification'),
29 ),
30 _ => .item(value: suggestion),
31 },
32 ],
33);
34

Sections

1const timezones = {
2 'North America': [
3 'Eastern Standard Time (EST)',
4 'Central Standard Time (CST)',
5 'Mountain Standard Time (MST)',
6 'Pacific Standard Time (PST)',
7 'Alaska Standard Time (AKST)',
8 'Hawaii Standard Time (HST)',
9 ],
10 'South America': [
11 'Argentina Time (ART)',
12 'Bolivia Time (BOT)',
13 'Brasilia Time (BRT)',
14 'Chile Standard Time (CLT)',
15 ],
16 'Europe & Africa': [
17 'Greenwich Mean Time (GMT)',
18 'Central European Time (CET)',
19 'Eastern European Time (EET)',
20 'Western European Summer Time (WEST)',
21 'Central Africa Time (CAT)',
22 'Eastern Africa Time (EAT)',
23 ],
24 'Asia': [
25 'Moscow Time (MSK)',
26 'India Standard Time (IST)',
27 'China Standard Time (CST)',
28 'Japan Standard Time (JST)',
29 'Korea Standard Time (KST)',
30 'Indonesia Standard Time (IST)',
31 ],
32 'Australia & Pacific': [
33 'Australian Western Standard Time (AWST)',
34 'Australian Central Standard Time (ACST)',
35 'Australian Eastern Standard Time (AEST)',
36 'New Zealand Standard Time (NZST)',
37 'Fiji Time (FJT)',
38 ],
39};
40
41class SectionAutocompleteExample extends StatelessWidget {
42 @override
43 Widget build(BuildContext _) => FAutocomplete.builder(
44 hint: 'Type to search timezones',
45 filter: (query) => timezones.values
46 .expand((list) => list)
47 .where(
48 (timezone) => timezone.toLowerCase().contains(query.toLowerCase()),
49 ),
50 contentBuilder: (context, query, suggestions) => [
51 for (final MapEntry(key: label, value: zones) in timezones.entries)
52 if (zones.where(suggestions.contains).toList() case final zones
53 when zones.isNotEmpty)
54 .section(label: Text(label), items: zones),
55 ],
56 );
57}
58

Dividers

1@override
2Widget build(BuildContext _) => FAutocomplete.builder(
3 hint: 'Type to search levels',
4 filter: (query) => const [
5 '1A',
6 '1B',
7 '2A',
8 '2B',
9 '3',
10 '4',
11 ].where((i) => i.toLowerCase().contains(query.toLowerCase())),
12 contentBuilder: (context, query, suggestions) =>
13 <FAutocompleteItemMixin>[
14 .richSection(
15 label: const Text('Level 1'),
16 divider: .indented,
17 children: [
18 if (suggestions.contains('1A')) .item(value: '1A'),
19 if (suggestions.contains('1B')) .item(value: '1B'),
20 ],
21 ),
22 .section(
23 label: const Text('Level 2'),
24 items: ['2A', '2B'].where(suggestions.contains).toList(),
25 ),
26 if (suggestions.contains('3')) .item(value: '3'),
27 if (suggestions.contains('4')) .item(value: '4'),
28 ]
29 .where(
30 (item) => item is! FAutocompleteSection || item.children.isNotEmpty,
31 )
32 .toList(),
33);
34

Behavior

Async

1const fruits = [
2 'Apple',
3 'Banana',
4 'Orange',
5 'Grape',
6 'Strawberry',
7 'Pineapple',
8];
9
10class AsyncAutocompleteExample extends StatelessWidget {
11 @override
12 Widget build(BuildContext _) => FAutocomplete.builder(
13 hint: 'Type to search fruits',
14 filter: (query) async {
15 await Future.delayed(const Duration(seconds: 3));
16 return query.isEmpty
17 ? fruits
18 : fruits.where(
19 (fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()),
20 );
21 },
22 contentBuilder: (context, query, values) => [
23 for (final fruit in values) .item(value: fruit),
24 ],
25 );
26}
27

Async with Custom Loading

1const fruits = [
2 'Apple',
3 'Banana',
4 'Orange',
5 'Grape',
6 'Strawberry',
7 'Pineapple',
8];
9
10class AsyncLoadingAutocompleteExample extends StatelessWidget {
11 @override
12 Widget build(BuildContext _) => FAutocomplete.builder(
13 hint: 'Type to search fruits',
14 filter: (query) async {
15 await Future.delayed(const Duration(seconds: 3));
16 return query.isEmpty
17 ? fruits
18 : fruits.where(
19 (fruit) => fruit.toLowerCase().startsWith(query.toLowerCase()),
20 );
21 },
22 contentLoadingBuilder: (context, style) => Padding(
23 padding: const .all(14.0),
24 child: Text('Here be dragons...', style: style.emptyTextStyle),
25 ),
26 contentBuilder: (context, query, suggestions) => [
27 for (final suggestion in suggestions) .item(value: suggestion),
28 ],
29 );
30}
31

Async with Custom Error Handling

1const fruits = [
2 'Apple',
3 'Banana',
4 'Orange',
5 'Grape',
6 'Strawberry',
7 'Pineapple',
8];
9
10class AsyncErrorAutocompleteExample extends StatelessWidget {
11 @override
12 Widget build(BuildContext _) => FAutocomplete.builder(
13 hint: 'Type to search fruits',
14 filter: (query) async {
15 await Future.delayed(const Duration(seconds: 3));
16 throw StateError('Error loading data');
17 },
18 contentBuilder: (context, query, values) => [
19 for (final fruit in values) .item(value: fruit),
20 ],
21 contentErrorBuilder: (context, error, trace) => Padding(
22 padding: const .all(14.0),
23 child: Icon(
24 FIcons.circleX,
25 size: 15,
26 color: context.theme.colors.primary,
27 ),
28 ),
29 );
30}
31

Clearable

1const fruits = [
2 'Apple',
3 'Banana',
4 'Orange',
5 'Grape',
6 'Strawberry',
7 'Pineapple',
8];
9
10class ClearableAutocompleteExample extends StatelessWidget {
11 @override
12 Widget build(BuildContext _) => FAutocomplete(
13 hint: 'Type to search fruits',
14 clearable: (value) => value.text.isNotEmpty,
15 items: fruits,
16 );
17}
18

Form

1class FormAutocompleteExample extends StatefulWidget {
2 @override
3 State<FormAutocompleteExample> createState() =>
4 _FormAutocompleteExampleState();
5}
6
7class _FormAutocompleteExampleState extends State<FormAutocompleteExample> {
8 final _key = GlobalKey<FormState>();
9
10 @override
11 Widget build(BuildContext _) => Form(
12 key: _key,
13 child: Column(
14 crossAxisAlignment: .start,
15 children: [
16 FAutocomplete(
17 label: const Text('Department'),
18 description: const Text('Type to search your dream department'),
19 hint: 'Search departments',
20 validator: (department) => department == null || department.isEmpty
21 ? 'Please select a department'
22 : null,
23 items: const [
24 'Engineering',
25 'Marketing',
26 'Sales',
27 'Human Resources',
28 'Finance',
29 ],
30 ),
31 const SizedBox(height: 25),
32 FButton(
33 child: const Text('Submit'),
34 onPress: () {
35 if (_key.currentState!.validate()) {
36 // Form is valid, do something with department.
37 }
38 },
39 ),
40 ],
41 ),
42 );
43}
44

On this page