Fix/id focus state - #2362
Conversation
|
Code review(medium · 2 findings) Scope: the branch is now fix/id-focus-state (PR #2362, base android_TV_implementation) — 17 added / 6 removed lines in view_util.dart only. Note: 23c57fb added resync logic in box_wrapper.dart, and a2b0dcd reverted all of it, so box_wrapper is net-unchanged in the PR. Analyzes clean apart from the pre-existing unused_local_variable at line 434. Two findings, both on the new block at view_util.dart:382-390. The core issue is that the copy is one-directional — it only ever writes true, and never re-checks the rebuilt node's actual state. That's correct for the case the comment describes (Flutter reusing the element, FocusNode persists), but wrong when the rebuild produces a new element, and the initState/didUpdateWidget resync that covered exactly that case was removed in the second commit with nothing put in its place. The blast radius is limited to ${id.hasFocus} bindings since the focus ring reads live focus state — but that binding is the point of the PR. The second is narrower: hoisting the lookup and widening the type check to HasController means getContextById's parent-scope walk can now match an outer-scope widget sharing the same id, which the old EnsembleController-only check silently ignored for legacy widgets. The rest of the diff is clean — the previousContext EnsembleController refactor is behavior-preserving, hoisting getContextById out of the widgetInstance != null branch has no side effects (pure map walk), CustomWidgetModel still returns early before the new code, and running the copy before _updateWidgetBindings means skipping the ModelChangeEvent dispatch is right. |
The focus-state copy in view_util carries hasFocus onto a rebuilt
controller, but it is one-directional: when the rebuild produces a new
element whose FocusNode is not actually focused, nothing clears the
copied true and the ${id.hasFocus} binding stays true permanently.
Re-assert the controller against the live focus node in both TV focus
wrapper states (initState post-frame for inherited state, didUpdateWidget
on controller swap), deferring the ModelChangeEvent dispatch to post-frame.
Also scope the view_util copy to ids registered in the current scope so
an ancestor-scope hit with the same id (a collision, not the widget being
rebuilt) cannot transfer focus onto the rebuilt widget.
Description
This PR fixes
${id.hasFocus}resetting tofalsewhen a focused TV widget is rebuilt after navigating back.The previous widget controller is available through the widget ID context. The replacement controller now inherits its
hasFocusvalue before bindings are evaluated, preventing ID-bound focus styles from rendering unfocused during the rebuild.Related Issue
Type of Change
What Has Changed
Preserve ID-bound focus state during rebuilds
id.hasFocusto the replacement controller before bindings run.Behavior Compatibility
hasFocusis set by the existing TV focus wrappers.How to Test
From
modules/ensemble, run: