Skip to content

RawAutocomplete: Fix focusNode listener mismatch in didUpdateWidget/dispose - #190686

Merged
auto-submit[bot] merged 4 commits into
flutter:masterfrom
chrisbobbe:raw-autocomplete-focus-listener
Oct 15, 2026
Merged

auto-submit[bot] merged 4 commits into
flutter:masterfrom
chrisbobbe:raw-autocomplete-focus-listener

Conversation

@chrisbobbe

@chrisbobbe chrisbobbe commented Aug 6, 2026 •

Copy link
Copy Markdown
Contributor

This is a minor bugfix I'm proposing as prep for my work on #159443. For details on how that work blocks on this, see #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

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.

@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 updates RawAutocompleteState to use _onFocusChange instead of _updateOptionsViewVisibility as the listener for focus node changes, and adds a test to verify that the listener is removed from a caller-provided focus node upon disposal. The review feedback identifies an issue where transitioning from a custom focus node to a null focus node leaves the newly created internal focus node without the _onFocusChange listener, and provides a code suggestion to resolve this.

Comment on lines +690 to +695
oldWidget.focusNode?.removeListener(_onFocusChange);
if (oldWidget.focusNode == null) {
_internalFocusNode?.dispose();
_internalFocusNode = null;
}
widget.focusNode?.addListener(_updateOptionsViewVisibility);
widget.focusNode?.addListener(_onFocusChange);

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.

high

When transitioning from a custom focusNode (non-null) to the default focusNode (null), oldWidget.focusNode is not null, so the if (oldWidget.focusNode == null) block is skipped.

Since widget.focusNode is null, widget.focusNode?.addListener(_onFocusChange) is a no-op. As a result, _internalFocusNode remains null until it is lazily initialized by the _focusNode getter. However, when it is lazily initialized, the _onFocusChange listener is never added to it. This means the autocomplete widget will not respond to focus changes on the newly created internal focus node.

To fix this, we should initialize _internalFocusNode and add the listener when transitioning from a custom focus node to null.

      oldWidget.focusNode?.removeListener(_onFocusChange);
      if (oldWidget.focusNode == null) {
        _internalFocusNode?.dispose();
        _internalFocusNode = null;
      } else if (widget.focusNode == null) {
        _internalFocusNode = FocusNode()..addListener(_onFocusChange);
      }
      widget.focusNode?.addListener(_onFocusChange);

@chrisbobbe chrisbobbe Aug 7, 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.

No change needed. Gemini seems to have hallucinated this claim:

However, when it is lazily initialized, the _onFocusChange listener is never added to it.

The listener is added when the internal node is lazily initialized, in the _focusNode getter:

  FocusNode get _focusNode {
    return widget.focusNode ?? (_internalFocusNode ??= FocusNode()..addListener(_onFocusChange));
  }

so a rebuild from a custom focusNode to null is covered: the next _focusNode access creates the internal node with the listener already attached. (_internalTextEditingController follows the same pattern.) This PR also doesn't touch that logic; it only corrects which listener didUpdateWidget and dispose remove.

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.

/gemini can you check this again?

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.

You are correct, and I apologize for the confusion. I misread the _focusNode getter implementation. Since _focusNode lazily initializes _internalFocusNode and attaches the _onFocusChange listener upon first access, the transition from a custom focusNode to null is indeed handled correctly by the existing logic. My previous concern about the listener not being added was unfounded. Thank you for pointing this out.

@github-actions github-actions Bot added the framework flutter/packages/flutter repository. See also f: labels. label Aug 7, 2026

@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.

Approach LGTM but left some comments.

@victorsanni
victorsanni requested a review from justinmc August 11, 2026 19:37
@victorsanni victorsanni added the CICD Run CI/CD label Aug 18, 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.

The approach looks good to me but +1 to @victorsanni's comments. Thanks for the continued work on Autocomplete @chrisbobbe !

…ispose

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 refresh _hasFocus when the focus node changes, since the new
node's focus state may not match the old node's, and add tests
covering focus-node swaps.
@chrisbobbe
chrisbobbe force-pushed the raw-autocomplete-focus-listener branch from 8186f2c to 68d850d Compare August 27, 2026 23:17
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Aug 27, 2026
@chrisbobbe

chrisbobbe commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks @victorsanni and @justinmc for the reviews! Revision pushed; now _hasFocus is updated from the new focusNode, and I added those three requested tests.

victorsanni
victorsanni previously approved these changes Aug 31, 2026
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

@victorsanni it looks like you've approved this; thanks for the review! Would you re-add the CICD label please?

@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Gentle bump on this, which is part of my work toward fixing #159443. 🙂

justinmc
justinmc previously approved these changes Oct 13, 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.

LGTM 👍 . Sorry for the delay here @chrisbobbe!

Comment on lines +4114 to +4115
expect(focusNode2.hasFocus, isFalse);
expect(focusNode1.hasListeners, isFalse);

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.

Nit: Consider also expecting the focusNode1.hasFocus and focusNode2.hasListeners.

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.

Do you mean like this?

diff --git packages/flutter/test/widgets/autocomplete_test.dart packages/flutter/test/widgets/autocomplete_test.dart
index d09a4d030..d6e09528a 100644
--- packages/flutter/test/widgets/autocomplete_test.dart
+++ packages/flutter/test/widgets/autocomplete_test.dart
@@ -4109,8 +4109,10 @@ void main() {
     // Swap in a focus node that, unlike the old one, is unfocused.
     await pumpRawAutocomplete(focusNode2);
     await tester.pump();
+    expect(focusNode2.hasListeners, isTrue);
     expect(focusNode2.hasFocus, isFalse);
     expect(focusNode1.hasListeners, isFalse);
+    expect(focusNode1.hasFocus, isTrue);
 
     // Make options available again, without changing the focus state.
     textEditingController.text = '';

The second new expect, expect(focusNode1.hasFocus, isTrue), is meant to correspond to "unlike the old one" in the code comment. But it fails in that position, actually. I'm not sure I would've expected that. It passes if I move it before the await pumpRawAutocomplete(focusNode2);.

It looks like the replacement of focusNode1 causes focusNode1 to become unfocused, as part of being "detached". Here's a dump of the logs when I run just this one test with debugFocusChanges on:

diff --git packages/flutter/test/widgets/autocomplete_test.dart packages/flutter/test/widgets/autocomplete_test.dart
index d09a4d030..053fa0e4c 100644
--- packages/flutter/test/widgets/autocomplete_test.dart
+++ packages/flutter/test/widgets/autocomplete_test.dart
@@ -4107,10 +4107,17 @@ void main() {
     expect(find.byKey(optionsKey), findsNothing);
 
     // Swap in a focus node that, unlike the old one, is unfocused.
+    print('focusNode1: $focusNode1');
+    print('focusNode2: $focusNode2');
+    print('---------');
+    debugFocusChanges = true;
     await pumpRawAutocomplete(focusNode2);
+    debugFocusChanges = false;
     await tester.pump();
+    expect(focusNode2.hasListeners, isTrue);
     expect(focusNode2.hasFocus, isFalse);
     expect(focusNode1.hasListeners, isFalse);
+    expect(focusNode1.hasFocus, isTrue);
 
     // Make options available again, without changing the focus state.
     textEditingController.text = '';
Details
$ flutter test packages/flutter/test/widgets/autocomplete_test.dart  --name 'refreshes the internal focus state when the focusNode changes'
00:01 +0: refreshes the internal focus state when the focusNode changes                                                                                                                  
focusNode1: _SpyFocusNode#12d50([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS)
focusNode2: _SpyFocusNode#298a1
---------
FOCUS: Detaching node:
    _SpyFocusNode#12d50([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS)
    With enclosing scope FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH, focusedChildren: [_SpyFocusNode#12d50([PRIMARY FOCUS])], traversalEdgeBehavior: TraversalEdgeBehavior.parentScope)
FOCUS: Setting FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH) as focused child for scope:
    FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])(context: FocusScope, IN FOCUS PATH, focusedChildren: [FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [IN FOCUS PATH])])
FOCUS: Setting FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])(context: FocusScope, IN FOCUS PATH, focusedChildren: [FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [IN FOCUS PATH])]) as focused child for scope:
    FocusScopeNode#97929(View Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH, focusedChildren: [FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])])
FOCUS: Setting FocusScopeNode#97929(View Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH, focusedChildren: [FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])]) as focused child for scope:
    FocusScopeNode#4f83e(Root Focus Scope [IN FOCUS PATH])(IN FOCUS PATH, focusedChildren: [FocusScopeNode#97929(View Scope [IN FOCUS PATH])])
FOCUS: Scheduling update, current focus is _SpyFocusNode#12d50([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS), next focus will be FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH)
FOCUS: Unfocused node:
    primary focus was _SpyFocusNode#12d50([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS)
    next focus will be FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH)
FOCUS: Node was detached: _SpyFocusNode#12d50([PRIMARY FOCUS])(context: Focus, PRIMARY FOCUS)
FOCUS: Refreshing focus state. Next focus will be FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope)(context: _FocusScopeWithExternalFocusNode)
FOCUS: Updating focus from null to FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])(context: _FocusScopeWithExternalFocusNode, PRIMARY FOCUS)
FOCUS: Setting FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])(context: _FocusScopeWithExternalFocusNode, PRIMARY FOCUS) as focused child for scope:
    FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])(context: FocusScope, IN FOCUS PATH, focusedChildren: [FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])])
FOCUS: Setting FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])(context: FocusScope, IN FOCUS PATH, focusedChildren: [FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])]) as focused child for scope:
    FocusScopeNode#97929(View Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH, focusedChildren: [FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])])
FOCUS: Setting FocusScopeNode#97929(View Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH, focusedChildren: [FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])]) as focused child for scope:
    FocusScopeNode#4f83e(Root Focus Scope [IN FOCUS PATH])(IN FOCUS PATH, focusedChildren: [FocusScopeNode#97929(View Scope [IN FOCUS PATH])])
FOCUS: Notified 12 dirty nodes:
    FocusNode#aa2a8(Navigator [IN FOCUS PATH])(context: Focus, IN FOCUS PATH)
    _FocusTraversalGroupNode#784f9(FocusTraversalGroup [IN FOCUS PATH])(context: Focus, NOT FOCUSABLE, IN FOCUS PATH)
    FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])(context: FocusScope, IN FOCUS PATH, focusedChildren: [FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])])
    FocusNode#b60f8([IN FOCUS PATH])(context: Focus, NOT FOCUSABLE, IN FOCUS PATH)
    FocusNode#1e109(Shortcuts: <Shortcut Registrar> [IN FOCUS PATH])(context: Focus, NOT FOCUSABLE, IN FOCUS PATH)
    _FocusTraversalGroupNode#a4e43(FocusTraversalGroup [IN FOCUS PATH])(context: Focus, NOT FOCUSABLE, IN FOCUS PATH)
    FocusNode#b8e8a(Shortcuts: <Default Text Editing Shortcuts> [IN FOCUS PATH])(context: Focus, NOT FOCUSABLE, IN FOCUS PATH)
    FocusNode#2c86b(Shortcuts: <Default WidgetsApp Shortcuts> [IN FOCUS PATH])(context: Focus, NOT FOCUSABLE, IN FOCUS PATH)
    FocusScopeNode#97929(View Scope [IN FOCUS PATH])(context: _FocusScopeWithExternalFocusNode, IN FOCUS PATH, focusedChildren: [FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])])
    _FocusTraversalGroupNode#d767d(FocusTraversalGroup [IN FOCUS PATH])(context: Focus, NOT FOCUSABLE, IN FOCUS PATH)
    FocusScopeNode#4f83e(Root Focus Scope [IN FOCUS PATH])(IN FOCUS PATH, focusedChildren: [FocusScopeNode#97929(View Scope [IN FOCUS PATH])])
    FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])(context: _FocusScopeWithExternalFocusNode, PRIMARY FOCUS)
FocusManager#87b36
 │ primaryFocus: FocusScopeNode#cb1fb(_ModalScopeState<dynamic>
 │   Focus Scope [PRIMARY FOCUS])
 │ primaryFocusCreator: _FocusScopeWithExternalFocusNode ←
 │   PrimaryScrollController ← _ActionsScope ← Actions ← Builder ←
 │   PageStorage ← Offstage ← _ModalScopeStatus ←
 │   UnmanagedRestorationScope ← RestorationScope ← AnimatedBuilder
 │   ←
 │   _ModalScope<dynamic>-[LabeledGlobalKey<_ModalScopeState<dynamic>>#8921d]
 │   ← Semantics ← Builder ← _RenderTheaterMarker ←
 │   _EffectiveTickerMode ← TickerMode ←
 │   _OverlayEntryWidget-[LabeledGlobalKey<_OverlayEntryWidgetState>#cf444]
 │   ← _Theater ← Overlay-[LabeledGlobalKey<OverlayState>#d01e2] ← ⋯
 │
 └─rootScope: FocusScopeNode#4f83e(Root Focus Scope [IN FOCUS PATH])
   │ IN FOCUS PATH
   │ focusedChildren: FocusScopeNode#97929(View Scope [IN FOCUS PATH])
   │
   └─Child 1: _FocusTraversalGroupNode#d767d(FocusTraversalGroup [IN FOCUS PATH])
     │ context: Focus
     │ NOT FOCUSABLE
     │ IN FOCUS PATH
     │
     └─Child 1: FocusScopeNode#97929(View Scope [IN FOCUS PATH])
       │ context: _FocusScopeWithExternalFocusNode
       │ IN FOCUS PATH
       │ focusedChildren: FocusScopeNode#701c8(Navigator Scope [IN FOCUS
       │   PATH])
       │
       └─Child 1: FocusNode#2c86b(Shortcuts: <Default WidgetsApp Shortcuts> [IN FOCUS PATH])
         │ context: Focus
         │ NOT FOCUSABLE
         │ IN FOCUS PATH
         │
         └─Child 1: FocusNode#b8e8a(Shortcuts: <Default Text Editing Shortcuts> [IN FOCUS PATH])
           │ context: Focus
           │ NOT FOCUSABLE
           │ IN FOCUS PATH
           │
           └─Child 1: _FocusTraversalGroupNode#a4e43(FocusTraversalGroup [IN FOCUS PATH])
             │ context: Focus
             │ NOT FOCUSABLE
             │ IN FOCUS PATH
             │
             └─Child 1: FocusNode#1e109(Shortcuts: <Shortcut Registrar> [IN FOCUS PATH])
               │ context: Focus
               │ NOT FOCUSABLE
               │ IN FOCUS PATH
               │
               └─Child 1: FocusNode#b60f8([IN FOCUS PATH])
                 │ context: Focus
                 │ NOT FOCUSABLE
                 │ IN FOCUS PATH
                 │
                 └─Child 1: FocusScopeNode#701c8(Navigator Scope [IN FOCUS PATH])
                   │ context: FocusScope
                   │ IN FOCUS PATH
                   │ focusedChildren: FocusScopeNode#cb1fb(_ModalScopeState<dynamic>
                   │   Focus Scope [PRIMARY FOCUS])
                   │
                   └─Child 1: _FocusTraversalGroupNode#784f9(FocusTraversalGroup [IN FOCUS PATH])
                     │ context: Focus
                     │ NOT FOCUSABLE
                     │ IN FOCUS PATH
                     │
                     └─Child 1: FocusNode#aa2a8(Navigator [IN FOCUS PATH])
                       │ context: Focus
                       │ IN FOCUS PATH
                       │
                       └─Child 1: FocusScopeNode#cb1fb(_ModalScopeState<dynamic> Focus Scope [PRIMARY FOCUS])
                         │ context: _FocusScopeWithExternalFocusNode
                         │ PRIMARY FOCUS
                         │
                         └─Child 1: FocusNode#193e8(Shortcuts)
                           │ context: Focus
                           │ NOT FOCUSABLE
                           │
                           └─Child 1: _SpyFocusNode#298a1
                               context: Focus

══╡ EXCEPTION CAUGHT BY FLUTTER TEST FRAMEWORK ╞════════════════════════════════════════════════════
The following TestFailure was thrown running a test:
Expected: true
  Actual: <false>

When the exception was thrown, this was the stack:
#4      main.<anonymous closure> (file:///Users/chrisbobbe/.local/lib/flutter/packages/flutter/test/widgets/autocomplete_test.dart:4120:5)
<asynchronous suspension>
#5      testWidgets.<anonymous closure>.<anonymous closure> (package:flutter_test/src/widget_tester.dart:192:15)
<asynchronous suspension>
#6      TestWidgetsFlutterBinding._runTestBody (package:flutter_test/src/binding.dart:2062:5)
<asynchronous suspension>
<asynchronous suspension>
(elided one frame from package:stack_trace)

This was caught by the test expectation on the following line:
  file:///Users/chrisbobbe/.local/lib/flutter/packages/flutter/test/widgets/autocomplete_test.dart line 4120
The test description was:
  refreshes the internal focus state when the focusNode changes
════════════════════════════════════════════════════════════════════════════════════════════════════
00:01 +0 -1: refreshes the internal focus state when the focusNode changes [E]                                                                                                           
  Test failed. See exception logs above.
  The test description was: refreshes the internal focus state when the focusNode changes
  

To run this test again: /Users/chrisbobbe/.local/lib/flutter/bin/cache/dart-sdk/bin/dart test /Users/chrisbobbe/.local/lib/flutter/packages/flutter/test/widgets/autocomplete_test.dart -p vm --plain-name 'refreshes the internal focus state when the focusNode changes'
00:01 +0 -1: Some tests failed.    

See in particular "primary focus was [focusNode1]" / "next focus will be [focusNode2]", under "FOCUS: Unfocused node:".

I think this test doesn't necessarily need to pin focusNode1's state after the switch. The important thing after the switch is that the new internal focus state matches the incoming focusNode2's state. In my next revision, I'll add the expect(focusNode2.hasListeners, isTrue);, though, as a sanity check, because it would be clearly wrong if focusNode2 had no listeners after the switch.

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.

I think the reason focusNode1 doesn't have focus is that it has been disposed. That's kind of irrelevant to what you're trying to test, so I'm on board with omitting that check.

@justinmc justinmc added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 13, 2026
@auto-submit auto-submit Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 13, 2026
@auto-submit

auto-submit Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

autosubmit label was removed for flutter/flutter/190686, because The base commit of the PR is older than 7 days and can not be merged. Please merge the latest changes from the main into this branch and resubmit the PR.

@chrisbobbe
chrisbobbe dismissed stale reviews from justinmc and victorsanni via f3a07ec October 13, 2026 21:05
@flutter-dashboard flutter-dashboard Bot removed the CICD Run CI/CD label Oct 13, 2026
@chrisbobbe

chrisbobbe commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the review! Revision pushed, and please see my reply inline above: #190686 (comment)

@victorsanni victorsanni added the CICD Run CI/CD label Oct 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.

Re LGTM 👍

Comment on lines +4114 to +4115
expect(focusNode2.hasFocus, isFalse);
expect(focusNode1.hasListeners, isFalse);

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.

I think the reason focusNode1 doesn't have focus is that it has been disposed. That's kind of irrelevant to what you're trying to test, so I'm on board with omitting that check.

@victorsanni victorsanni added the autosubmit Merge PR when tree becomes green via auto submit App label Oct 14, 2026
@auto-submit
auto-submit Bot added this pull request to the merge queue Oct 15, 2026
@chrisbobbe

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews!

Merged via the queue into flutter:master with commit 95a6d95 Oct 15, 2026
30 checks passed
@flutter-dashboard flutter-dashboard Bot removed the autosubmit Merge PR when tree becomes green via auto submit App label Oct 15, 2026
auto-submit Bot pushed a commit to flutter/packages that referenced this pull request Oct 15, 2026
flutter/flutter@426be96...5c7e45a

2026-10-15 [email protected] [web_paragraph] Honor TextHeightBehavior.leadingDistribution and default to proportional (flutter/flutter#193736)
2026-10-15 [email protected] Roll Skia from 79a00d7c4898 to d131b7789482 (2 revisions) (flutter/flutter#194446)
2026-10-15 [email protected] Roll Dart SDK from b8ffa9233096 to 84686ea04815 (1 revision) (flutter/flutter#194442)
2026-10-15 [email protected] Roll Dart SDK from e81af2dac9d1 to b8ffa9233096 (1 revision) (flutter/flutter#194432)
2026-10-15 [email protected] Roll Skia from ef24b8f87a2a to 79a00d7c4898 (4 revisions) (flutter/flutter#194431)
2026-10-15 [email protected] Roll Skia from 48c51004ee4e to ef24b8f87a2a (1 revision) (flutter/flutter#194427)
2026-10-15 [email protected] Roll Dart SDK from 6b50116630d0 to e81af2dac9d1 (5 revisions) (flutter/flutter#194422)
2026-10-15 [email protected] Roll Skia from b1be97266d3a to 48c51004ee4e (10 revisions) (flutter/flutter#194421)
2026-10-15 [email protected] RawAutocomplete: Fix focusNode listener mismatch in didUpdateWidget/dispose (flutter/flutter#190686)
2026-10-14 [email protected] [flutter_tools] Wait for iOS simulator log stream before launch (flutter/flutter#193142)
2026-10-14 [email protected] Move Material/Cupertino PRs from design triage to framework triage (flutter/flutter#194244)
2026-10-14 [email protected] refactor(flutter_tools): share FlutterCommand options and OptionBundles (flutter/flutter#194356)
2026-10-14 [email protected] Roll pub packages (flutter/flutter#194413)
2026-10-14 [email protected] Add ContextMenuButtonKind (flutter/flutter#193528)
2026-10-14 [email protected] Split SwiftPM plugin into multiple products (flutter/flutter#193521)
2026-10-14 [email protected] Roll Skia from 43d707a3be0e to b1be97266d3a (8 revisions) (flutter/flutter#194392)
2026-10-14 [email protected] Roll pub packages (flutter/flutter#194398)
2026-10-14 [email protected] [flutter_tools] Migrate AndroidSdk and AndroidStudio away from globals (flutter/flutter#193650)
2026-10-14 [email protected] [flutter_tools] Reload widget-preview when data assets change (flutter/flutter#193437)
2026-10-14 [email protected] [flutter tool] suppress sub-tool analytics during first run (flutter/flutter#194320)
2026-10-14 [email protected] Roll Skia from acde3755d2e2 to 43d707a3be0e (1 revision) (flutter/flutter#194385)

If this roll has caused a breakage, revert this CL and set the roller
to dry run mode using the controls here:
https://autoroll.skia.org/r/flutter-packages
Please CC [email protected],[email protected] on the revert to ensure that a human
is aware of the problem.

To file a bug in Packages: https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@chrisbobbe
chrisbobbe deleted the raw-autocomplete-focus-listener branch October 21, 2026 20:09
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.

3 participants