skip to Main Content

The code below creates an AutoComplete field and a MaterialButton that should work together, but they’re not.

The AutoComplete needs to be able to:

  1. Take direct user input from typing or list selection
  2. Be updated by a click of the MaterialButton

Problem is that I can’t get both of these things to work in the same widget.

When I set TextFormField(controller: field2TextEditingController, …) the AutoComplete displays the drop-down categoryList properly, but then the MaterialButton can not update the value.

And when I set TextFormField(controller: _category2Controller, …) the MaterialButton can update the value, but the Auto Complete no longer displays the drop-down categoriesList.

Do you have any idea how I can get both of these things to work simultaneously?

Here’s my code:

    dependencies:
      flutter_textfield_autocomplete: ^1.0.2
    
    
      String category2 = "";
    
      TextEditingController _category2Controller = TextEditingController();
    
      List<String> categoriesList = [];
      categoriesList = ["Breakfast", "Dessert", "Dinner", "Groceries", "Gas", "Insurance", "Lunch", "Vacation", "Vehicle Repair", "Wine"];
    
    
    Autocomplete<String>(
                            optionsBuilder: (TextEditingValue textEditingValue) {
                              return categoriesList
                                  .where((theString) => theString
                                  .toLowerCase()
                                  .startsWith(
                                  textEditingValue.text.toLowerCase()))
                                  .toList();
                            },
                            fieldViewBuilder: (BuildContext context,
                                TextEditingController field2TextEditingController,
                                FocusNode fieldFocusNode,
                                VoidCallback onFieldSubmitted) {
                              return TextFormField(
                                controller: _category2Controller,           // DOES NOT DISPLAY DROP DOWN LIST
                                // controller: field2TextEditingController,    // CANNOT ACCEPT VALUE FROM BUTTON
                                focusNode: fieldFocusNode,
                                decoration:
                                  const InputDecoration(labelText: 'Category 2'),
                                onChanged: (value) {
                                  category2 = value.toString();
                                  _category2Controller..text = value.toString()
                                    ..selection = TextSelection.collapsed(offset: _category2Controller.text.length);
                                },
                              );
                            },
                            displayStringForOption: (value) => value,
                            onSelected: (value) {
                              _category2Controller.text = value.toString();
                              category2 = value.toString();
                            },
                          ),
    
    
    MaterialButton(
                                padding: const EdgeInsets.all(10),
                                color: Colors.blueAccent,
                                shape: RoundedRectangleBorder(
                                    borderRadius: BorderRadius.circular(5)),
                                child: const Text(
                                  "Zachary's Pizza - Dinner",
                                  style: TextStyle(color: Colors.white, fontSize: 15),
                                ),
                                onPressed: () {
                                                               _category2Controller.text = "Dinner";
                                  category2 = "Dinner";
                                },
                              ),

Any comments and/or suggestions are greatly appreciated!

2

Answers


  1. Chosen as BEST ANSWER

    Based on this Stackoverflow post I decided to use RawAutocomplete instead. This widget allows me to pass my own TextEditingController, which made it easy to assign values to the RawAutocomplete from both inside & outside of that widget itself.

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Raw AutoComplete Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: const MyHomePage(),
        );
      }
    }
    
    class MyHomePage extends StatefulWidget {
      const MyHomePage({super.key});
      @override
      State<MyHomePage> createState() => _MyHomePageState();
    }
    
    class _MyHomePageState extends State<MyHomePage> {
      final TextEditingController _textEditingController = TextEditingController();
      final FocusNode _focusNode = FocusNode();
      final GlobalKey _autocompleteKey = GlobalKey();
    
      final List<String> _animals = <String>['aardvark', 'ape', 'baboon', 'bobcat', 'chameleon', 'cat', 'dog', 'elephant', 'fox', 'zebra'];
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: const Text("Raw AutoComplete Demo"),
          ),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
    
                MaterialButton(   // Button assigns value to RawAutocomplete
                  child: const Text('Jaguar'),
                  onPressed: () {
                    _textEditingController.text = "Jaguar";
                  },
                ),
    
                MaterialButton(   // Button clears any value in RawAutocomplete
                  child: const Text('Clear'),
                  onPressed: () {
                    _textEditingController.text = "";
                  },
                ),
    
                RawAutocomplete<String>(
                  key: _autocompleteKey,
                  focusNode: _focusNode,
                  textEditingController: _textEditingController,
                  optionsBuilder: (TextEditingValue textEditingValue) {
                    return _animals.where((String option) {
                      return option.contains(textEditingValue.text.toLowerCase());
                    }).toList();
                  },
                  optionsViewBuilder: (BuildContext context,
                      AutocompleteOnSelected<String> onSelected,
                      Iterable<String> options) {
                    return Material(
                      elevation: 4.0,
                      child: ListView(
                        children: options
                            .map((String option) => GestureDetector(
                                  onTap: () {
                                    onSelected(option);
                                  },
                                  child: ListTile(
                                    title: Text(option),
                                  ),
                                ))
                            .toList(),
                      ),
                    );
                  },
                  fieldViewBuilder: (
                    BuildContext context,
                    TextEditingController fieldTextEditingController,
                    FocusNode fieldFocusNode,
                    VoidCallback onFieldSubmitted,
                  ) {
                    return TextFormField(
                      controller: fieldTextEditingController,
                      focusNode: fieldFocusNode,
                      decoration: const InputDecoration(labelText: 'Choose Animal'),
                    );
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    

  2. Try something like this:

    TextEditingController? _categoryController;
    
    MaterialButton(
      child: const Text('Groceries'),
      onPressed: () {
        _categoryController?.text = "Groceries";
        // Navigator.pop(context);
      },
    ),
    Autocomplete<String>(
      optionsBuilder: (TextEditingValue textEditingValue) {
        return categoriesList
            .where((theString) => theString
                .toLowerCase()
                .startsWith(textEditingValue.text.toLowerCase()))
            .toList();
      },
      fieldViewBuilder: (
        BuildContext context,
        TextEditingController fieldTextEditingController,
        FocusNode fieldFocusNode,
        VoidCallback onFieldSubmitted,
      ) {
        _categoryController ??= fieldTextEditingController; // initialize _categoryController if null
        return TextFormField(
          controller: _categoryController, // use _categoryController everywhere to update the TextFormField
          focusNode: fieldFocusNode,
          decoration: const InputDecoration(labelText: 'Category'),
        );
      },
      displayStringForOption: (value) => value,
    )
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search