Skip to content

RawAutocomplete: Add controller param for caller-managed options - #183705

Draft
chrisbobbe wants to merge 3 commits into
flutter:masterfrom
chrisbobbe:pr-raw-autocomplete-controller
Draft

chrisbobbe wants to merge 3 commits into
flutter:masterfrom
chrisbobbe:pr-raw-autocomplete-controller

Conversation

@chrisbobbe

Copy link
Copy Markdown
Contributor

Fixes #159443.

This lets RawAutocomplete's callers just specify the autocomplete options directly, on their own schedule, rather than via optionsBuilder which is only called on text-field-value changes. This is non-breaking: callers opt in with RawAutocomplete's new controller param.

In a separate commit, this also fixes a behavior that seems undesirable: for callers who use optionsBuilder, the passed callback was only being called when the text field's TextEditingValue.text changed. Now the callback is also called when the selection or composing range changes.

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

Note: The Flutter team is currently trialing the use of Gemini Code Assist for GitHub. Comments from the gemini-code-assist bot should not be taken as authoritative feedback from the Flutter team. If you find its comments useful you can update your code accordingly, but if you are unsure or disagree with the feedback, please feel free to wait for a Flutter team member's review for guidance on which automated comments should be addressed.

@github-actions github-actions Bot added the framework flutter/packages/flutter repository. See also f: labels. label Mar 15, 2026
@google-cla

google-cla Bot commented Mar 15, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from 39f25d0 to 7467c52 Compare March 15, 2026 06:55
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Looks like my commit messages' Claude Code "co-authored-by" lines triggered that CLA-not-signed failure:

Co-Authored-By: Claude Opus 4.6 (1M context) [email protected]

I believe I've followed Flutter's AI contribution guidelines and I'm prepared to discuss this code. 🙂 I did remove those co-authored-by lines to get CI passing.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a RawAutocompleteController to allow for programmatic control over the options in a RawAutocomplete widget. This is a valuable addition, providing more flexibility to developers. The changes also include a fix to ensure that the optionsBuilder is called on any change to the TextEditingValue, not just text changes, which improves the widget's responsiveness. The implementation is clean, well-documented, and includes appropriate tests for the new functionality.

Comment on lines +144 to +147
set options(Iterable<T> value) {
_options = value;
notifyListeners();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent unnecessary notifications and rebuilds when the options haven't changed, consider adding a check for equality before calling notifyListeners(). A deep equality check might be needed if a new Iterable with the same elements is provided.

For example, you could use listEquals from package:flutter/foundation.dart, but be mindful of the performance cost of converting iterables to lists if they can be large:

  set options(Iterable<T> value) {
    if (listEquals(value.toList(growable: false), _options.toList(growable: false))) {
      return;
    }
    _options = value;
    notifyListeners();
  }

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like the kind of detail that would be fine to leave up to callers? We should not do a deep equality check.

@gnprice gnprice Mar 17, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, there shouldn't be a deep equality check here. (So that's a bad suggestion from Gemini.)

I think it'd be typical for a setter like this to have a quick identity check before doing something potentially expensive like notifying listeners. That could be explicit as identical; or some places in the framework use == and rely on the convention that == should only be overridden when the implementation will still be cheap.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense; updated to add the == check.

@victorsanni victorsanni left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for finally getting back to this.

Can you fix the failing tests?

@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from 7467c52 to b6d9140 Compare March 31, 2026 18:55
@github-actions github-actions Bot removed the CICD Run CI/CD label Mar 31, 2026
@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from b6d9140 to 1a2198e Compare March 31, 2026 18:56
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Revision pushed.

@gnprice gnprice added the CICD Run CI/CD label Mar 31, 2026
@justinmc
justinmc requested a review from victorsanni March 31, 2026 22:24
@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from 1a2198e to 3b76fe2 Compare April 1, 2026 03:18
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 1, 2026
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Fixed the dart format issues flagged in CI.

@gnprice gnprice added the CICD Run CI/CD label Apr 1, 2026
@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from 3b76fe2 to 90c3cb5 Compare April 1, 2026 17:50
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Fixed the dartdoc issue flagged in CI.

@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 1, 2026
@gnprice gnprice added the CICD Run CI/CD label Apr 1, 2026
@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from 90c3cb5 to 4d9a598 Compare April 1, 2026 19:59
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 1, 2026
@chrisbobbe

chrisbobbe commented Apr 1, 2026 •

Copy link
Copy Markdown
Contributor Author

Fixed a leak flagged in CI:

Windows framework_tests_widgets_leak_tracking

00:27 +685 -1: C:/b/s/w/ir/x/w/flutter/packages/flutter/test/widgets/autocomplete_test.dart: (tearDownAll) [E]
  Expected: leak free
    Actual: <Instance of 'Leaks'>
     Which: contains leaks:
            # The text is generated by leak_tracker.
            # For leak troubleshooting tips open:
            # https://github.com/flutter/flutter/blob/main/docs/contributing/testing/Leak-tracking.md
            notDisposed:
              total: 1
              objects:
                RawAutocompleteController<String>:
                  test: RawAutocomplete does not crash when hiding non-displayed overlay
                  identityHashCode: 164054042
            
            
  
  package:matcher                                              expect
  package:leak_tracker_testing/src/leak_testing.dart 69:24     LeakTesting.collectedLeaksReporter.<fn>
  package:leak_tracker_flutter_testing/src/testing.dart 74:37  maybeTearDownLeakTrackingForAll
  ===== asynchronous gap ===========================
  dart:async                                                   _CustomZone.registerBinaryCallback
  package:flutter_test/src/test_compat.dart 302:3              _tearDownForTestFile

This was a bit complicated and involves a latent bug:

  • The latent bug: In main, _RawAutocompleteState adds _onFocusChange as a listener on the passed FocusNode, in initState, but never removes it. didUpdateWidget and dispose pass a different method to removeListener instead: _updateOptionsViewVisibility.
  • For convenience, copy-pasting the test where the RawAutocompleteController leak was identified:
Details
  testWidgets('RawAutocomplete does not crash when hiding non-displayed overlay', (
    WidgetTester tester,
  ) async {
    final controller = TextEditingController();
    final focusNode = FocusNode();

    addTearDown(controller.dispose);
    addTearDown(focusNode.dispose);

    await tester.pumpWidget(
      TestWidgetsApp(
        home: RawAutocomplete<String>(
          key: const ValueKey('initial'),
          textEditingController: controller,
          focusNode: focusNode,
          optionsBuilder: (TextEditingValue value) => [],
          fieldViewBuilder: (context, ctrl, node, onFieldSubmitted) {
            return TestTextField(controller: ctrl, focusNode: node);
          },
          optionsViewBuilder: (context, onSelected, options) => const SizedBox(),
        ),
      ),
    );

    // Change the key to force the RawAutocomplete to dispose and re-initialize.
    await tester.pumpWidget(
      TestWidgetsApp(
        home: RawAutocomplete<String>(
          key: const ValueKey('new-key'),
          textEditingController: controller,
          focusNode: focusNode,
          optionsBuilder: (TextEditingValue value) => [],
          fieldViewBuilder: (context, ctrl, node, onFieldSubmitted) {
            return TestTextField(controller: ctrl, focusNode: node);
          },
          optionsViewBuilder: (context, onSelected, options) => const SizedBox(),
        ),
      ),
    );

    await tester.enterText(find.byType(TestTextField), 'trigger');
    await tester.pump();

    expect(tester.takeException(), isNull);
  });
  • The leaked RawAutocompleteController must be _internalController. As you can see, a controller is not passed in either call to the RawAutocomplete constructor.
  • That controller is created lazily, by the state for the first ('initial'-keyed) RawAutocomplete, after that state is disposed. This is the controller that CI flags as "notDisposed".
  • How is that allowed to happen? It's because the first state's _onFocusChange listener wasn't removed from the FocusNode, as it should have been, when the state was disposed. That listener is called post-dispose when tester.enterText triggers focus, causing this call chain: _onFocusChange -> _updateOptionsViewVisibility -> getter _canShowOptionsView -> getter _options -> getter _controller -> _internalController ??= RawAutocompleteController<T>(), thus lazily creating a RawAutocompleteController.

Fixed by fixing the latent bug: I added a commit changing didUpdateWidget and dispose to remove the listener _onFocusChange, as intended, instead of the other method _updateOptionsViewVisibility. Tests pass at that commit and after.

@gnprice gnprice added the CICD Run CI/CD label Apr 1, 2026
@victorsanni
victorsanni requested review from gnprice and justinmc April 4, 2026 01:35
@chrisbobbe

chrisbobbe commented Apr 12, 2026 •

Copy link
Copy Markdown
Contributor Author

Fixed a docs issue flagged in CI, which was that the Material Autocomplete widget's dartdoc was referring (via the {@macro …}) to the controller field that existed on RawAutocomplete but not Autocomplete.

Fixed by surfacing the controller-based interface in Autocomplete too 🙂—might as well offer it there too. The implementation just passes the controller down to the RawAutocomplete, same as with optionsBuilder, optionsViewOpenDirection, etc.

@gnprice gnprice added the CICD Run CI/CD label Apr 12, 2026
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Fixed by surfacing the controller-based interface in Autocomplete too

Ah 🙂 I see from the current CI failure that this won't be possible during the code freeze: #184093

Looks like the freeze is expected to end sometime in May, so I'd prefer to leave the Material support out of this PR and do it as a followup after the freeze instead. So I'll add a commit removing the Autocomplete change, and fix the docs issue by moving the [controller] reference out of RawAutocomplete's {@template …}.

@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from 856069a to a5fc3ba Compare April 14, 2026 00:02
@github-actions github-actions Bot removed the CICD Run CI/CD label Apr 14, 2026
Fixes flutter#159443.

RawAutocomplete.optionsBuilder only receives a TextEditingValue, so
it cannot react to changes in external state (e.g. a data source
that loads asynchronously after focus). This adds
AutocompleteController, a ChangeNotifier with an options
getter/setter, and a new controller param on RawAutocomplete that is
mutually exclusive with optionsBuilder. When a controller is
provided, the caller sets controller.options directly and the widget
reacts.

Internally, the state now uses AutocompleteController for both
paths: optionsBuilder results flow through an internal controller,
while an external controller is used directly. The post-update work
(semantics announcements, highlight clamping, selection validity,
visibility) is extracted into _handleOptionsChanged, which listens
to whichever controller is active.
@chrisbobbe
chrisbobbe force-pushed the pr-raw-autocomplete-controller branch from a5fc3ba to 01aa1b6 Compare April 14, 2026 00:21
@github-actions github-actions Bot removed the p: material_ui material_ui package in flutter/packages label Apr 14, 2026
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Revision pushed, PTAL!

@gnprice gnprice added the CICD Run CI/CD label Apr 14, 2026

@justinmc justinmc left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this! I've wanted to get it fixed for awhile.

Do you have any thoughts on passing the list of options in to RawAutocomplete directly versus using a controller as you've done here? I think you could achieve the same thing by calling setState for the most part, but I'm curious if/why the controller pattern is better for your use case. I'm open to either way, just making sure we think it through.


// Makes sure that options change only when content of the field changes.
var shouldUpdateOptions = false;
if (value.text != _lastFieldText) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was there any specific reason that you wanted the options to rebuild when any part of the TextEditingValue changes instead of just the text? I believe this choice was deliberate (though I'm not 100% sure it was the right decision). If I play around with HTML's datalist, it doesn't update for composing text (https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/datalist).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also the new controller should be a workaround for people that do want this behavior, right? I think you could listen to the TextEditingValue and update the options whenever you want.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the options to rebuild when any part of the TextEditingValue changes instead of just the text?

I remember deciding to keep this behavior a while back:
https://github.com/flutter/flutter/pull/148995/changes#r1612467521 because the optionsBuilder API exposes the entire TextEditingValue, and so there might exist a user who monitors selection changes that will be broken if we only rebuild on text changes.

But I agree it makes it unnecessarily expensive.

@chrisbobbe chrisbobbe May 4, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So the current behavior when just the selection or composing changes (not the text) is to call and await optionsBuilder and drop the result on the floor 🙂 without updating the options:

`_onChangedField` in `main`

See that shouldUpdateOptions is false if the text has not changed, and see the conditional return when that is false (marked "HERE"):

  // Assigning an ID to every call of _onChangedField is necessary to avoid a
  // situation where _options is updated by an older call when multiple
  // _onChangedField calls are running simultaneously.
  int _onChangedCallId = 0;
  // Called when _textEditingController changes.
  Future<void> _onChangedField() async {
    // During a selection, changes to the field text should not trigger
    // options update.
    if (_selecting) {
      return;
    }
    final TextEditingValue value = _textEditingController.value;

    // Makes sure that options change only when content of the field changes.
    var shouldUpdateOptions = false;
    if (value.text != _lastFieldText) {
      shouldUpdateOptions = true;
      _onChangedCallId += 1;
    }
    _lastFieldText = value.text;
    final int callId = _onChangedCallId;
    final Iterable<T> options = await widget.optionsBuilder(value);

    if (!mounted) {
      return;
    }

    // Makes sure that previous call results do not replace new ones.
    if (callId != _onChangedCallId || !shouldUpdateOptions) {
      return; // <----------------- HERE
    }
    if (_options.isEmpty != options.isEmpty) {
      _announceSemantics(options.isNotEmpty);
    }
    _options = options;
    _updateHighlight(_highlightedOptionIndex.value);
    final T? selection = _selection;
    if (selection != null && value.text != widget.displayStringForOption(selection)) {
      _selection = null;
    }

    _updateOptionsViewVisibility();
  }

So it doesn't seem hard to fully commit to updating only on text changes, if we want to. We'd just want to remove the cases where optionsBuilder is called and its results are ignored, to save the unnecessary work. To do this, I guess there's a breaking-change question along the lines @victorsanni mentioned—

the optionsBuilder API exposes the entire TextEditingValue, and so there might exist a user who monitors selection changes that will be broken if we only rebuild on text changes.

—but the breakage is limited to callers whose optionsBuilder has some desired side effect on composing/IME changes, and I think chances of that are probably low.

In my read of the Git history, it was an accident in 7695582 to stop updating the options on non-text changes. See my second commit in this revision:

RawAutocomplete: Update options on selection/composing changes, not just text

Previously, _onChangedField would call optionsBuilder on every
TextEditingValue change but silently discard the result unless
.text had changed. This meant an optionsBuilder that depended on
.selection or .composing would never see its results applied.

That guard was introduced in 919d20511 ("Dismiss Autocomplete with
ESC", #97790) for a narrow purpose: after the user pressed ESC to
hide the options overlay, a selection-only change (e.g. moving the
cursor) should not re-show it. Commit 76955826a (#150776) then
repurposed the same _lastFieldText check to also gate all option
updates, as part of adding race-condition protection for async
optionsBuilder.

This change separates the two concerns:
- Race-condition protection (_onChangedCallId) now applies to every
  call, not just text changes.
- ESC-dismiss behavior is handled by a dedicated _userHidOptions
  flag, which is set when the user presses ESC and cleared when the
  text changes, the field is re-focused, or the user navigates
  options with arrow keys.

Happy to go either way, i.e. commit optionsBuilder to text-changes-only (by removing unnecessary calls) or keep the approach in this revision where we update on selection/IME changes too.

As one data point, in Zulip we do want to change the autocomplete options on selection changes, especially cursor-position changes (i.e. when the selection is collapsed), as part of supporting @-mentions in the middle of a Zulip message you're composing. But as you say @justinmc that's easy to do in the new API. 🙂

@justinmc
justinmc requested a review from victorsanni April 21, 2026 22:28

@victorsanni victorsanni left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM so far, left a few comments

/// The current list of selectable options.
Iterable<T> get options => _options;
Iterable<T> _options = Iterable<T>.empty();
set options(Iterable<T> value) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to document the setter as well? Add add that if options is set, it will trigger a rebuild of the options view etc?


// Makes sure that options change only when content of the field changes.
var shouldUpdateOptions = false;
if (value.text != _lastFieldText) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the options to rebuild when any part of the TextEditingValue changes instead of just the text?

I remember deciding to keep this behavior a while back:
https://github.com/flutter/flutter/pull/148995/changes#r1612467521 because the optionsBuilder API exposes the entire TextEditingValue, and so there might exist a user who monitors selection changes that will be broken if we only rebuild on text changes.

But I agree it makes it unnecessarily expensive.

}

// Makes sure that previous call results do not replace new ones.
if (callId != _onChangedCallId) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we consolidate this if-block into the one just above?

void didUpdateWidget(RawAutocomplete<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (!identical(oldWidget.controller, widget.controller)) {
oldWidget.controller?.removeListener(_handleOptionsChanged);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need a test for this as well? That swapping out controllers at runtime works correctly?

expect(lastOptions, <String>['dingo', 'flamingo', 'goose']);
});

group('AutocompleteController', () {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we test for the async case? Since optionsBuilder can be asynchronous, is it worth it to test asynchronous updates to controller.options as well?

@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Coming back to this today; I left #183705 (comment) and hope to share more thoughts and a new revision tomorrow, but I've run out of time for today :)

@justinmc
justinmc requested review from justinmc and victorsanni May 5, 2026 22:40

@victorsanni victorsanni left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @chrisbobbe, can you fix the merge conflicts in this PR?

@Piinks Piinks added the waiting for response The Flutter team cannot make further progress on this issue until the original reporter responds label Jun 2, 2026
@Piinks

Piinks commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Hi @chrisbobbe is this something you would still like to work on?

@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Absolutely, yes; sorry I've let this sit. Will return to it as soon as I can.

@github-actions github-actions Bot removed the waiting for response The Flutter team cannot make further progress on this issue until the original reporter responds label Jul 6, 2026
@Piinks

Piinks commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Looks like there are some conflicts here, still planning on returning to this one @chrisbobbe ?

@chrisbobbe

chrisbobbe commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Coming back to this today. Apologies again for letting it sit.

I'd like to send a few PRs as prep for this (I'll edit this comment to list them), to shrink the diff for this PR. I've also re-sequenced the work so that we can defer the decision about non-text changes (IME, selection; see #183705 (comment)) for a separate PR after this one.

Those prep PRs are:

@victorsanni

victorsanni commented Aug 18, 2026 •

Copy link
Copy Markdown
Contributor

looks like I've reached the open-PRs limit.

I think you can open more than 1, as long as they are drafts.

Also there are new merge conflicts here.

@chrisbobbe
chrisbobbe marked this pull request as draft August 18, 2026 21:11
dayanruben pushed a commit to dayanruben/flutter that referenced this pull request Oct 15, 2026
…ispose (flutter#190686)

This is a minor bugfix I'm proposing as prep for my work on flutter#159443. For
details on how that work blocks on this, see
flutter#183705 (comment).

initState registers _onFocusChange on the passed FocusNode, but
didUpdateWidget and dispose were removing/adding
_updateOptionsViewVisibility instead. This meant the _onFocusChange
listener was never properly removed, so it could fire on an
already-disposed State.

Also, when the focus node changes, refresh _hasFocus from the new node,
since its focus state may not match the old node's.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Victor Sanni <[email protected]>
suryaKommana2662 pushed a commit to suryaKommana2662/flutter that referenced this pull request Oct 29, 2026
…ispose (flutter#190686)

This is a minor bugfix I'm proposing as prep for my work on flutter#159443. For
details on how that work blocks on this, see
flutter#183705 (comment).

initState registers _onFocusChange on the passed FocusNode, but
didUpdateWidget and dispose were removing/adding
_updateOptionsViewVisibility instead. This meant the _onFocusChange
listener was never properly removed, so it could fire on an
already-disposed State.

Also, when the focus node changes, refresh _hasFocus from the new node,
since its focus state may not match the old node's.

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [AI contribution guidelines] and understand my
responsibilities, or I am not using AI tools.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [ ] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [ ] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[AI contribution guidelines]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#ai-contribution-guidelines
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md

---------

Co-authored-by: Victor Sanni <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CICD Run CI/CD framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Rebuild Autocomplete options at arbitrary time

5 participants