Implementing Autocomplete Functionality in Flutter

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 simple TextFormField 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 a Material 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 an async function that calls getOptions 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.