title: Flutter: Autocomplete not showing suggestions when suggestion list changes
body: <h2>Framework &amp; Architecture</h2>
  <p>I have a specific architecture in my Flutter app. I am using BLoC pattern (<code>flutter_bloc</code>) to maintain state and fetch data from remote server.</p>
  <h2>How autocomplete should behave</h2>
  <p>I want to build autocomplete input. When user types, it starts fetching data from server after few milliseconds. As user types, the list of suggestions should be updated from the remote server and shown with filtered values to the user. Additionally, I need to set initial value of the autocomplete text field if such value is present <sup>1</sup>. The way data is presented is also custom. Suggestion list presents user with suggestions containing both <code>name</code> and <code>id</code> values but text field can only contain <code>name</code> value (this <code>name</code> value is also used for searching the suggestions) <sup>2</sup>.</p>
  <p>I am not having much luck when using <code>RawAutocomplete</code> widget from Flutter material library. I have succeeded in making the initial value appear in the field by leveraging <code>TextEditingController</code> and <code>didUpdateWidget</code> method. The problem is, when I'm typing into the field, the suggestions are being fetched and passed to the widget but the suggestion list (built via <code>optionsViewBuilder</code>) is not being built. Usually the list appears if I change value in the field but that's too late to be useful.</p>
  <h2>This is what I have tried:</h2>
  <h3><a href="https://dartpad.dev/5f4a8df1299587d141ccfce313b2b40d?null_safety=true" rel="noreferrer">Link to live demo</a></h3>
  <p><em>NOTE: Try typing &quot;xyz&quot;, that is a pattern that should match one of the suggestions. Waiting a bit and deleting single character will show the suggestions.</em></p>
  <p>I am attaching two components as an example. Parent component called <code>DetailPage</code> takes care of triggering fetch of the suggestions and also stores selected suggestion / value of the input. Child component <code>DetailPageForm</code> contains actual input.
  The example is artificially constrained but it is in regular <code>MaterialApp</code> parent widget. For brevity I'm not including <em>BLoC</em> code and just using regular streams. The code runs fine and I created it specifically for this example.</p>
  <h3><code>DetailPage</code></h3>
  <pre class="lang-dart prettyprint-override"><code>import 'dart:async';
  import 'package:flutter/material.dart';
  
  import 'detail_page_form.dart';
  
  @immutable
  class Suggestion {
    const Suggestion({
      this.id,
      this.name,
    });
  
    final int id;
    final String name;
  }
  
  class MockApi {
    final _streamController = StreamController&lt;List&lt;Suggestion&gt;&gt;();
  
    Future&lt;void&gt; fetch() async {
      await Future.delayed(Duration(seconds: 2));
      _streamController.add([
        Suggestion(id: 1, name: 'xyz'),
        Suggestion(id: 2, name: 'jkl'),
      ]);
    }
  
    void dispose() {
      _streamController.close();
    }
  
    Stream&lt;List&lt;Suggestion&gt;&gt; get stream =&gt; _streamController.stream;
  }
  
  class DetailPage extends StatefulWidget {
    final _mockApi = MockApi();
  
    void _fetchSuggestions(String query) {
      print('Fetching with query: $query');
      _mockApi.fetch();
    }
  
    @override
    _DetailPageState createState() =&gt; _DetailPageState(
          onFetch: _fetchSuggestions,
          stream: _mockApi.stream,
        );
  }
  
  class _DetailPageState extends State&lt;DetailPage&gt; {
    _DetailPageState({
      this.onFetch,
      this.stream,
    });
  
    final OnFetchCallback onFetch;
    final Stream&lt;List&lt;Suggestion&gt;&gt; stream;
    /* NOTE: This value can be used for initial value of the
             autocomplete input
    */
    Suggestion _value;
  
    _handleSelect(Suggestion suggestion) {
      setState(() {
        _value = suggestion;
      });
    }
  
    @override
    Widget build(BuildContext context) {
      return Scaffold(
          appBar: AppBar(title: Text('Detail')),
          body: StreamBuilder&lt;List&lt;Suggestion&gt;&gt;(
              initialData: [],
              stream: stream,
              builder: (context, snapshot) {
                if (snapshot.hasError) {
                  return Container(
                      padding: const EdgeInsets.all(10.0),
                      decoration: BoxDecoration(
                          color: Colors.red,
                          ),
                      child: Flex(
                          direction: Axis.horizontal,
                          children: [ Text(snapshot.error.toString()) ]
                          )
                      );
                }
  
                return DetailPageForm(
                    list: snapshot.data,
                    value: _value != null ? _value.name : '',
                    onSelect: _handleSelect,
                    onFetch: onFetch,
                    );
              }));
    }
  }
  </code></pre>
  <h3><code>DetailPageForm</code></h3>
  <pre class="lang-dart prettyprint-override"><code>import 'dart:async';
  
  import 'package:flutter/material.dart';
  
  import 'detail_page.dart';
  
  typedef OnFetchCallback = void Function(String);
  typedef OnSelectCallback = void Function(Suggestion);
  
  class DetailPageForm extends StatefulWidget {
    DetailPageForm({
      this.list,
      this.value,
      this.onFetch,
      this.onSelect,
    });
  
    final List&lt;Suggestion&gt; list;
    final String value;
    final OnFetchCallback onFetch;
    final OnSelectCallback onSelect;
  
    @override
    _DetailPageFormState createState() =&gt; _DetailPageFormState();
  }
  
  class _DetailPageFormState extends State&lt;DetailPageForm&gt; {
    Timer _debounce;
    TextEditingController _controller = TextEditingController();
    FocusNode _focusNode = FocusNode();
    List&lt;Suggestion&gt; _list;
  
    @override
    void initState() {
      super.initState();
      _controller.text = widget.value ?? '';
      _list = widget.list;
    }
  
    @override
    void dispose() {
      super.dispose();
      _controller.dispose();
    }
  
    @override
    void didUpdateWidget(covariant DetailPageForm oldWidget) {
      super.didUpdateWidget(oldWidget);
  
      if (oldWidget.value != widget.value) {
        _controller = TextEditingController.fromValue(TextEditingValue(
            text: widget.value,
            selection: TextSelection.fromPosition(TextPosition(offset: widget.value.length)),
            ));
      }
  
      if (oldWidget.list != widget.list) {
        setState(() {
          _list = widget.list;
        });
      }
    }
  
    void _handleInput(String value) {
      if (_debounce != null &amp;&amp; _debounce.isActive) {
        _debounce.cancel();
      }
  
      _debounce = Timer(const Duration(milliseconds: 300), () {
        widget.onFetch(value);
      });
    }
  
    @override
    Widget build(BuildContext context) {
      print(_list);
      return Container(
          padding: const EdgeInsets.all(10.0),
          child: RawAutocomplete&lt;Suggestion&gt;(
            focusNode: _focusNode,
            textEditingController: _controller,
            optionsBuilder: (TextEditingValue textEditingValue) {
              return _list.where((Suggestion option) {
                return option.name
                    .trim()
                    .toLowerCase()
                    .contains(textEditingValue.text.trim().toLowerCase());
              });
            },
            fieldViewBuilder: (BuildContext context,
                TextEditingController textEditingController,
                FocusNode focusNode,
                VoidCallback onFieldSubmitted) {
              return TextFormField(
                controller: textEditingController,
                focusNode: focusNode,
                onChanged: _handleInput,
                onFieldSubmitted: (String value) {
                  onFieldSubmitted();
                },
              );
            },
            optionsViewBuilder: (context, onSelected, options) {
              return Align(
                alignment: Alignment.topLeft,
                child: Material(
                  elevation: 4.0,
                  child: SizedBox(
                    height: 200.0,
                    child: ListView.builder(
                      padding: const EdgeInsets.all(8.0),
                      itemCount: options.length,
                      itemBuilder: (BuildContext context, int index) {
                        final option = options.elementAt(index);
                        return GestureDetector(
                          onTap: () {
                            onSelected(option);
                          },
                          child: ListTile(
                            title: Text('${option.id} ${option.name}'),
                          ),
                        );
                      },
                    ),
                  ),
                ),
              );
            },
            onSelected: widget.onSelect,
          ));
    }
  }
  </code></pre>
  <p>On the image you can see right at the end that I have to delete one letter to get the suggestions to show up.</p>
  <p><a href="https://i.stack.imgur.com/PQvvP.gif" rel="noreferrer"><img src="https://i.stack.imgur.com/PQvvP.gif" alt="Example Flutter Application image" /></a></p>
  <h2>Expected behaviour</h2>
  <p>I expect the suggestions list to be re-built every time new suggestions are available and provide them to the user.</p>
  <hr />
  <p><sup>1</sup> The reason for that being that the input should show user a value that was selected before. This value might also be stored on the device. So the input is either empty or pre-filled with the value.<br />
  <sup>2</sup> This example is constrained but basically text field should not contain the same text that the suggestions contain for specific reasons.</p>