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@override2Widget build(BuildContext _) => FAutocomplete(3 label: const Text('Autocomplete'),4 hint: 'What can it do?',5 items: features,6);7CLI
To generate and customize this style:
dart run forui style create autocompleteUsage
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@override2Widget 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);34Sections
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};4041class SectionAutocompleteExample extends StatelessWidget {42 @override43 Widget build(BuildContext _) => FAutocomplete.builder(44 hint: 'Type to search timezones',45 filter: (query) => timezones.values46 .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 zones53 when zones.isNotEmpty)54 .section(label: Text(label), items: zones),55 ],56 );57}58Dividers
1@override2Widget 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);34Behavior
Async
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class AsyncAutocompleteExample extends StatelessWidget {11 @override12 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.isEmpty17 ? fruits18 : 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}27Async with Custom Loading
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class AsyncLoadingAutocompleteExample extends StatelessWidget {11 @override12 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.isEmpty17 ? fruits18 : 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}31Async with Custom Error Handling
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class AsyncErrorAutocompleteExample extends StatelessWidget {11 @override12 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}31Clearable
1const fruits = [2 'Apple',3 'Banana',4 'Orange',5 'Grape',6 'Strawberry',7 'Pineapple',8];910class ClearableAutocompleteExample extends StatelessWidget {11 @override12 Widget build(BuildContext _) => FAutocomplete(13 hint: 'Type to search fruits',14 clearable: (value) => value.text.isNotEmpty,15 items: fruits,16 );17}18Form
1class FormAutocompleteExample extends StatefulWidget {2 @override3 State<FormAutocompleteExample> createState() =>4 _FormAutocompleteExampleState();5}67class _FormAutocompleteExampleState extends State<FormAutocompleteExample> {8 final _key = GlobalKey<FormState>();910 @override11 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.isEmpty21 ? '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