optionsViewBuilder is not called in Autocomplete after fetching results from server

Viewed 484

I'm trying to make an auto suggest input which fetches the results from a backend API. Here is my code:

import 'dart:async';

import 'package:flutter/material.dart';

import 'package:hello_world/api.dart';
import 'package:hello_world/autocomplete_item.dart';

class Debouncer {
  final int milliseconds;

  VoidCallback? action;
  Timer? _timer;

  Debouncer({this.milliseconds = 250});

  run(VoidCallback action) {
    _timer?.cancel();
    _timer = Timer(Duration(milliseconds: milliseconds), action);
  }
}

class AutoCompleteInput extends StatefulWidget {
  const AutoCompleteInput({
    Key? key,
    this.label = 'Suggest',
    this.textInputAction,
    this.validator,
    this.errorMessage,
  }) : super(key: key);

  final String label;

  final TextInputAction? textInputAction;

  final FormFieldValidator<String>? validator;

  final String? errorMessage;

  @override
  _AutoCompleteInputState createState() => _AutoCompleteInputState();
}

class _AutoCompleteInputState extends State<AutoCompleteInput> {
  final _debouncer = Debouncer(milliseconds: 500);

  List<AutoCompleteItem> _options = [];

  bool _isLoading = false;

  @override
  Widget build(BuildContext context) {
    return Autocomplete<AutoCompleteItem>(
      displayStringForOption: (AutoCompleteItem item) => item.value,
      optionsBuilder: _optionsBuilder,
      fieldViewBuilder: (
        BuildContext context,
        TextEditingController fieldTextEditingController,
        FocusNode fieldFocusNode,
        VoidCallback onFieldSubmitted,
      ) {
        return TextFormField(
          controller: fieldTextEditingController,
          focusNode: fieldFocusNode,
          autocorrect: false,
          maxLength: 50,
          maxLines: 1,
          keyboardType: TextInputType.text,
          textInputAction: widget.textInputAction,
          decoration: InputDecoration(
            labelText: widget.label,
            contentPadding: EdgeInsets.zero,
            errorText: widget.errorMessage,
            counterText: '',
            suffix: _isLoading
                ? Padding(
                    padding: const EdgeInsets.only(right: 1),
                    child: SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(strokeWidth: 2),
                    ),
                  )
                : null,
          ),
          validator: widget.validator,
          onFieldSubmitted: (String value) {
            onFieldSubmitted();
          },
          onChanged: (text) {
            _debouncer.run(() async {
              setState(() => _isLoading = true);

              await _fetchResults(text);

              setState(() => _isLoading = false);
            });
          },
        );
      },
      optionsViewBuilder: (
        BuildContext context,
        AutocompleteOnSelected<AutoCompleteItem> onSelected,
        Iterable<AutoCompleteItem> options,
      ) {
        return Align(
          alignment: Alignment.topLeft,
          child: Material(
            elevation: 2,
            child: Container(
              width: 300,
              constraints: BoxConstraints(maxHeight: 250),
              child: ListView.builder(
                padding: EdgeInsets.all(10.0),
                itemCount: options.length,
                shrinkWrap: true,
                itemBuilder: (BuildContext context, int index) {
                  final AutoCompleteItem option = options.elementAt(index);

                  return GestureDetector(
                    onTap: () {
                      onSelected(option);
                    },
                    child: ListTile(
                      dense: true,
                      title: Text(option.value),
                    ),
                  );
                },
              ),
            ),
          ),
        );
      },
      onSelected: (AutoCompleteItem selection) {
        print('${selection.type} => ${selection.value}');
      },
    );
  }

  Future<void> _fetchResults(String text) async {
    // WE PERFORM THE HTTP REQUEST HERE AND THEN ASSIGN THE RESPONSE TO `_options`

    setState(() async {
      _options = await Api.fetchSuggestions(text);
    });
  }

  Iterable<AutoCompleteItem> _optionsBuilder(
      TextEditingValue textEditingValue) {
    if (textEditingValue.text == '') {
      return const Iterable<AutoCompleteItem>.empty();
    }
    return _options;
  }
}

As you can see, I call _fetchResults method (which fetches the data from the API) inside the onChanged method of TextFormField. But when the data is fetched and the state is updated, there is no overlay of suggestions! I checked the length of _options and it has all of the results but still setState did not forced Autocomplete widget to rebuild it's overlay. Although when I remove the last character of the TextFormField, it immediately shows the overlay. How can I fix this issue?

0 Answers
Related