Autocomplete, or type-ahead, is a user interface feature that predicts and suggests words or phrases as the user types. It significantly enhances the user experience by reducing typing effort and helping users find what they’re looking for more quickly. In Flutter, implementing autocomplete functionality is straightforward thanks to the Autocomplete
widget. This article will guide you through implementing autocomplete in Flutter, complete with practical examples and best practices.
What is Autocomplete?
Autocomplete is a feature commonly found in search bars and text fields where suggestions appear as the user types. These suggestions are typically based on a predefined list of data, the user’s history, or server-provided data.
Why Use Autocomplete?
- Improved User Experience: Reduces the amount of typing needed.
- Faster Data Entry: Helps users find and select options quickly.
- Reduced Errors: Ensures users select from a valid set of options.
How to Implement Autocomplete Functionality in Flutter
To implement autocomplete in Flutter, we primarily use the Autocomplete
widget, which provides the structure and behavior for handling user input and displaying suggestions.
Step 1: Add the Autocomplete
Widget
The core of the autocomplete functionality in Flutter is the Autocomplete
widget. It takes two primary parameters: optionsBuilder
and onSelected
.
import 'package:flutter/material.dart';
class AutocompleteExample extends StatefulWidget {
@override
_AutocompleteExampleState createState() => _AutocompleteExampleState();
}
class _AutocompleteExampleState extends State<AutocompleteExample> {
static const List<String> _kOptions = <String>[
'apple',
'banana',
'cherry',
'date',
'elderberry',
'fig',
'grape',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Autocomplete Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return _kOptions.where((String option) {
return option.contains(textEditingValue.text.toLowerCase());
});
},
onSelected: (String selection) {
print('You just selected $selection');
},
fieldViewBuilder: (BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Search',
),
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
);
},
),
),
);
}
}
In this code:
_kOptions
is a predefined list of strings that will be suggested to the user.optionsBuilder
is a function that takes the current text editing value and returns a list of possible suggestions based on the user’s input. In this case, it filters_kOptions
to include only items that contain the user’s input.onSelected
is a callback function that is called when the user selects an option from the suggestions.fieldViewBuilder
allows you to customize the text input field that users interact with. Here, it’s a simpleTextFormField
with a border and label.
Step 2: Customize the Input Field
The fieldViewBuilder
parameter of the Autocomplete
widget allows you to fully customize the input field. Here’s how you can modify it to add styling or additional functionality.
fieldViewBuilder: (BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8.0),
),
labelText: 'Search for an item',
prefixIcon: const Icon(Icons.search),
),
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
);
},
In this enhanced version:
- The
TextFormField
is wrapped with additional styling to give it rounded borders and a prefix search icon. - The
InputDecoration
provides a label and custom styling, making the input field more visually appealing and user-friendly.
Step 3: Customizing the Options View
The optionsViewBuilder
parameter can be used to customize the appearance of the suggestion options displayed to the user.
optionsViewBuilder: (BuildContext context, AutocompleteOnSelected<String> onSelected, Iterable<String> options) {
return Align(
alignment: Alignment.topLeft,
child: Material(
elevation: 4.0,
child: SizedBox(
height: 200.0,
child: ListView(
padding: const EdgeInsets.all(8.0),
children: options.map((String option) {
return GestureDetector(
onTap: () {
onSelected(option);
},
child: ListTile(
title: Text(option),
),
);
}).toList(),
),
),
),
);
},
In this customization:
- Suggestions are displayed in a
ListView
within aMaterial
widget for a card-like appearance. - Each suggestion is tappable and calls the
onSelected
function when tapped.
Step 4: Using Asynchronous Data
In real-world applications, you might fetch autocomplete options from an API. Here’s how you can integrate asynchronous data fetching with the Autocomplete
widget.
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class AsyncAutocompleteExample extends StatefulWidget {
@override
_AsyncAutocompleteExampleState createState() => _AsyncAutocompleteExampleState();
}
class _AsyncAutocompleteExampleState extends State<AsyncAutocompleteExample> {
Future<List<String>> getOptions(String query) async {
final response = await http.get(Uri.parse('https://example.com/api/autocomplete?q=$query'));
if (response.statusCode == 200) {
List<dynamic> data = json.decode(response.body);
return data.map((item) => item['name'].toString()).toList();
} else {
throw Exception('Failed to load options');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Async Autocomplete Example'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Autocomplete<String>(
optionsBuilder: (TextEditingValue textEditingValue) async {
if (textEditingValue.text == '') {
return const Iterable<String>.empty();
}
return await getOptions(textEditingValue.text);
},
onSelected: (String selection) {
print('You just selected $selection');
},
fieldViewBuilder: (BuildContext context, TextEditingController textEditingController, FocusNode focusNode, VoidCallback onFieldSubmitted) {
return TextFormField(
controller: textEditingController,
focusNode: focusNode,
decoration: const InputDecoration(
border: OutlineInputBorder(),
labelText: 'Search',
),
onFieldSubmitted: (String value) {
onFieldSubmitted();
},
);
},
),
),
);
}
}
Key points:
- The
getOptions
function fetches autocomplete options from an API endpoint. - The
optionsBuilder
is now anasync
function that callsgetOptions
and returns the fetched suggestions. - Error handling is included to manage potential issues with the API request.
Best Practices for Implementing Autocomplete
- Debounce Input: Implement a debounce mechanism to avoid making API requests for every keystroke. This can improve performance and reduce unnecessary network traffic.
- Handle Loading States: Display a loading indicator while fetching data from an API.
- Provide Visual Feedback: Highlight the matched portion of the suggestion to improve usability.
- Limit Suggestions: Display a maximum number of suggestions to avoid overwhelming the user.
- Accessibility: Ensure your autocomplete implementation is accessible to users with disabilities by providing proper labels and handling keyboard navigation.
Conclusion
Implementing autocomplete functionality in Flutter can significantly enhance the user experience by making data entry faster and more efficient. By leveraging the Autocomplete
widget and customizing its behavior, you can create a seamless and user-friendly search experience. Whether you’re using local data or fetching suggestions from an API, Flutter provides the tools and flexibility you need to build robust autocomplete features.