You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Rebuild Autocomplete options at arbitrary time #159443
When the user finishes typing in the field, a user of Autocomplete might make two network requests: one to quickly fetch some approximate options and another, longer request to get more detailed results. Autocomplete should be able to update the options with the result of each request. Currently the optionsViewBuilder is only called in response to the completion of the future returned by optionsBuilder.
Proposal
The hacky solution is probably to introduce a controller with a method to imperatively update the options: AutocompleteController.setOptions.
A better solution might require a breaking change. Really it seems like maybe the options should just be passed into Autocomplete as a parameter? That would also require the ability to listen to changes in the field.
Related
There are other request to be able to display things in the options view at certain times, for example while loading: #147377. Solving this issue would probably also fix that.
Reacted by Diego Garcia, Max Weber, Greg Price, Arzan Nebiu, Shatanik Mahanty, Valentin Vignal, Artur Saidov and Piyush-Chek
I was creating a new issue for #157244 before I realize there's an existing issue,
the use cases I have are similar:
It seems Autocomplete.optionsBuilder is only called when the text editing controller's TextEditingValue changes. This does not work if optionsBuilder captures free variables, for example:
Click/tap the input field at the top, to focus it.
Expected: The list of options appears.
Actual: No options appear, even if you wait indefinitely.
If you then start typing a query (like "a"), the options that match your query appear promptly. (*) If you delete what you've typed so the query is empty again, the full list of possible options appears. So everything works great after you first start typing something.
(*) Unless you do so fast enough that the "network fetch" (the 100ms sleep, in the demo) to get the list of options hasn't finished yet. In that case it's the same situation as at the beginning.
In Zulip, the main user-facing effect of this is here: zulip/zulip-flutter#1626. The user is reading a chat view with a bunch of previous messages in it to various different topics; they decide to compose a new message, and tap the text field to select what topic to send to; we want to show them an autocomplete of the existing topics; but currently that autocomplete doesn't appear unless they start typing. (Getting the full list of past topics in the channel involves a network request to the server.)
Similarly if the user is in the middle of writing a message and they type "@", we want to show them an autocomplete of other users they can @-mention; but currently that autocomplete doesn't appear until they start typing part of the person's name to narrow it down. Very often the person they want to mention is one of the top handful we'd show without needing a query (e.g. it's one of the recent participants in that thread), so we'd like to not make them start typing the name.
Example app
import'package:flutter/material.dart';
voidmain() =>runApp(const_MyApp());
class_MyAppextendsStatelessWidget {
const_MyApp();
@overrideWidgetbuild(BuildContext context) {
returnMaterialApp(
title:'Autocomplete Demo',
home:Scaffold(
appBar:AppBar(title:Text('Autocomplete Demo')),
body:const_MyHomePageBody()));
}
}
class_MyHomePageBodyextendsStatefulWidget {
const_MyHomePageBody();
@overrideState<_MyHomePageBody> createState() =>_MyHomePageBodyState();
}
class_MyHomePageBodyStateextendsState<_MyHomePageBody> {
FocusNode focusNode =FocusNode();
TextEditingController controller =TextEditingController();
/// The list of possible options available for this autocomplete. /// /// Null until the actual list has been fetched from the network, /// or otherwise asynchronously obtained.List<String>? allOptions;
/// The options from [allOptions] that match the user's current query.List<String> results = [];
voidinitState() {
super.initState();
focusNode.addListener(_handleFocusChange);
controller.addListener(_updateResults);
}
void_handleFocusChange() {
if (allOptions ==null) {
// Only fetch the list of possible options when the input gets focus.// (In a real app, this might be because this is only part of the page// and the user might never interact with this input, so we don't want// to eagerly do a bunch of work that might not be needed.)_fetchOptions();
}
}
Future<void> _fetchOptions() async {
// In real life, imagine a network fetch, or a slow async computation.// For this demo, just sleep.awaitFuture<void>.delayed(Duration(milliseconds:100));
allOptions = ['apple', 'banana', 'cucumber', 'durian'];
// We have the list of possible options, so update the list of// matching options accordingly._updateResults();
// Alas, this doesn't cause the list to get shown in the UI!// Even if the whole widget tree gets rebuilt, [RawAutocomplete]// is keeping its own copy of the list of options; and it doesn't// update that copy until the text input changes.// So the user has to type something before they see any options.
}
void_updateResults() {
final query = controller.text;
// In real life the matching logic might be more complex// (and the options themselves might be more complex than strings);// but that doesn't interact with this issue.
results = (allOptions ?? []).where(
(option) => option.contains(query)).toList();
}
@overrideWidgetbuild(BuildContext context) {
returnColumn(children: [
TextButton(
// This button is just for convenience in DartPad,// to be able to get focus back to the editor pane.
onPressed: focusNode.unfocus,
child:Text('Unfocus')),
RawAutocomplete<String>(
focusNode: focusNode,
textEditingController: controller,
fieldViewBuilder: (_, _, _, _) =>TextField(focusNode: focusNode, controller: controller),
optionsBuilder: (query) => results,
optionsViewBuilder: (_, _, _) {
returnMaterial(
elevation:4.0,
child:ConstrainedBox(
constraints:constBoxConstraints(maxHeight:300),
child:ListView.builder(
padding:EdgeInsets.zero,
shrinkWrap:true,
itemCount: results.length,
itemBuilder: (_, index) =>InkWell(child:Text(results[index])))));
}),
]);
}
}
A while ago, my colleague @chrisbobbe wrote up a draft for how to adapt the Autocomplete / RawAutocomplete API to resolve this issue: see branch here (or permalink).
The core of it is that instead of optionsBuilder and onSelected, the RawAutocomplete widget takes as a parameter an instance of a new class RawAutocompleteController. That class extends ChangeNotifier; so the RawAutocompleteController implementation listens to the controller, and the app can update the list of options by calling a setter on the controller — very much like how a widget can control a text field via a TextEditingController.
Copying here @chrisbobbe's original description of the branch:
In my Flutter fork, [that draft branch] makes several NFC prep commits, then a larger commit that gives RawAutocomplete's caller the ability to control when its options-list state is updated. (That commit breaks lots of the public API, but it's probably possible in theory to offer the new API as an opt-in without breaking the current API.)
I wrote at the time a suggestion for how that draft could be adapted to preserve the existing API and avoid a breaking change:
For making the API compatible — continuing to offer the existing API which is nice and convenient for the cases it works well for — I think the main tool I'd reach for is to have a separate constructor, along the lines of ListView.custom and the other ListView constructors.
So there'd be a new RawAutocomplete.custom constructor which takes a controller, and then there'd be the existing constructor, and if you use the existing constructor then the state sets up a controller for you based on the optionsBuilder and onSelected and so on.
That probably means a lot of the code that's deleted in your demo would just move onto a subclass (perhaps a private subclass) of RawAutocompleteController, which would get instantiated by default if you didn't provide a controller.
That was in 2023 🙂, and we haven't yet found time to come back and turn that draft branch into a mergeable upstream PR. If anyone else would like to pick this up, they'd be very welcome to use that draft or any part of it as a starting point.
Activity
justinmc commentedon Dec 12, 2024
FYI @gnprice who brought this to my attention.
LongCatIsLooong commentedon Feb 25, 2025
I was creating a new issue for #157244 before I realize there's an existing issue,
the use cases I have are similar:
optionsViewBuilderdoes not get the last options ofoptionsBuilder#157244 as a duplicate of this issuegnprice commentedon Jul 9, 2025
I sat down today and wrote some example code for this issue; see below.
The repro is:
Run the app, say on https://dartpad.dev/ .
Click/tap the input field at the top, to focus it.
Expected: The list of options appears.
Actual: No options appear, even if you wait indefinitely.
If you then start typing a query (like "a"), the options that match your query appear promptly. (*) If you delete what you've typed so the query is empty again, the full list of possible options appears. So everything works great after you first start typing something.
(*) Unless you do so fast enough that the "network fetch" (the 100ms sleep, in the demo) to get the list of options hasn't finished yet. In that case it's the same situation as at the beginning.
In Zulip, the main user-facing effect of this is here: zulip/zulip-flutter#1626. The user is reading a chat view with a bunch of previous messages in it to various different topics; they decide to compose a new message, and tap the text field to select what topic to send to; we want to show them an autocomplete of the existing topics; but currently that autocomplete doesn't appear unless they start typing. (Getting the full list of past topics in the channel involves a network request to the server.)
Similarly if the user is in the middle of writing a message and they type "@", we want to show them an autocomplete of other users they can @-mention; but currently that autocomplete doesn't appear until they start typing part of the person's name to narrow it down. Very often the person they want to mention is one of the top handful we'd show without needing a query (e.g. it's one of the recent participants in that thread), so we'd like to not make them start typing the name.
Example app
gnprice commentedon Jul 10, 2025
A while ago, my colleague @chrisbobbe wrote up a draft for how to adapt the Autocomplete / RawAutocomplete API to resolve this issue: see branch here (or permalink).
The core of it is that instead of
optionsBuilderandonSelected, theRawAutocompletewidget takes as a parameter an instance of a new classRawAutocompleteController. That class extends ChangeNotifier; so the RawAutocompleteController implementation listens to the controller, and the app can update the list of options by calling a setter on the controller — very much like how a widget can control a text field via a TextEditingController.Copying here @chrisbobbe's original description of the branch:
I wrote at the time a suggestion for how that draft could be adapted to preserve the existing API and avoid a breaking change:
That was in 2023 🙂, and we haven't yet found time to come back and turn that draft branch into a mergeable upstream PR. If anyone else would like to pick this up, they'd be very welcome to use that draft or any part of it as a starting point.
gnprice commentedon Jul 10, 2025
Here's also the same branch as a draft PR just now, in case that's a more convenient form to read it or use it:
RawAutocomplete: Add controller param for caller-managed options
plchekspend commentedon Mar 25, 2026
RawAutocomplete: Add controller param for caller-managed options
RawAutocomplete: Add controller param for caller-managed options
RawAutocomplete: Add controller param for caller-managed options
RawAutocomplete: Add controller param for caller-managed options
RawAutocomplete: Add controller param for caller-managed options
RawAutocomplete: Add controller param for caller-managed options
autocomplete: Add controller param for caller-managed options
autocomplete: Add controller param for caller-managed options
autocomplete: Add controller param for caller-managed options
RawAutocomplete: Fix focusNode listener mismatch in didUpdateWidget/d…
RawAutocomplete: Fix focusNode listener mismatch in didUpdateWidget/d…