Repository navigation
Reset accessibility state when recycling Android views - #59047
Open
YevheniiKotyrlo wants to merge 3 commits into
Open
YevheniiKotyrlo wants to merge 3 commits into
YevheniiKotyrlo wants to merge 3 commits into
Conversation
With view recycling on, prepareToRecycleView clears the accessibility tags a view's props wrote but not the state those props applied to the view itself. The next view built from it applies only the props it declares, so it announces the previous view's content description and role, reads as selected, keeps its live region and screen-reader focusability, and stays hidden from accessibility services when the previous view was. Reset that state to a new view's, clear the accessibility delegate on the view, and restore TextView's default importance in ReactTextView.recycleView.
Contributor
Author
With experimental_accessibilityOrder on, a recycled ReactViewGroup kept the order list its previous props set, and a recycled child kept the parent and focusability tags its former parent's order put on it, the parent tag holding on to that view. ReactViewGroup.initView clears the list and prepareToRecycleView clears both tags, as a new view has them.
YevheniiKotyrlo
marked this pull request as ready for review
October 14, 2026 09:10
javache
requested changes
Oct 14, 2026
|
|
||
| // Accessibility state the props apply to the view itself, as a new view has it. The delegate | ||
| // is cleared on the view: ViewCompat would install an empty one and mark the view important. | ||
| view.setAccessibilityDelegate(null); |
Contributor
There was a problem hiding this comment.
I'm not sure about this one - the accessibility delegate dynamically updates with the state of the view, there should be no need to explicitly reset it.
Contributor
Author
There was a problem hiding this comment.
Most of what it reports follows the view's tags, but performAccessibilityAction routes through accessibilityActionsMap, which still holds the previous props' actions: a view recycled from one with an activate action dispatches that action on TalkBack's click instead of clicking. testRecycledViewPerformsItsOwnAccessibilityClick in a7847e5 fails without this line.
A delegate keeps the actions it registered while building node info, so a view recycled from one that declared an activate action dispatched that action on TalkBack's click instead of performing the click. The case fails without prepareToRecycleView clearing the delegate.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary:
With
enableViewRecyclingon, a recycled view keeps the accessibility state its previous props applied.BaseViewManager.prepareToRecycleViewclears the accessibility tags, but not the content description,importantForAccessibility, the selected state, the live region,screenReaderFocusable, theroleand collection tags, or the accessibility delegate. The next view of that type is built from it and applies only the props it declares, so TalkBack announces a label and a role the view never had, the kept delegate sends TalkBack's click to an action only the previous props declared, and a view last used inside anaria-hiddensubtree hides its new content from accessibility services. Withexperimental_accessibilityOrderon as well, a recycledReactViewGroupkeeps its previous order list, and a recycled child keeps the parent and focusability tags its former parent's order put on it.This resets that state in
prepareToRecycleViewto what a new view has, clears the order list inReactViewGroup.initView, and restoresTextView's default importance inReactTextView.recycleView. The delegate is cleared withView.setAccessibilityDelegate(null):ViewCompat.setAccessibilityDelegate(view, null)replaces a compat delegate with an empty one and turnsautoimportance intoyes. It sits beside theenabledreset #58799 added.Changelog:
[ANDROID] [FIXED] - Recycled views no longer keep the accessibility state of their previous props
Test Plan:
New cases in
BaseViewManagerTestandReactTextViewPropertyTestrecycle a view throughonDropViewInstanceandcreateViewand compare it with a new one. Before the change seven fail: content description"Dismiss sheet",roletagLIST, a leftover delegate, a click dispatched to the previous props'activateaction instead of performed, importance4where a new text view has1, the order list["first"], and the former parent inaccessibility_order_parent. With it,./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest -PenableWarningsAsErrors=truepasses (727 tests), the release sources compile with the same flag, and so does the RNTester debug build; the rest ofbuild_androidwas not run.RNTester with view recycling on, on an Android 16 emulator with TalkBack, before and after: plain rows mounted after labelled rows were removed are read as a selected "Dismiss sheet" button without the change, and as their text with it.
On a Galaxy S23 (Android 14) app with view recycling on, plain rows mounted after rows carrying
aria-label,role,aria-selected,aria-disabledandaria-hiddenwere removed reported the removed rows' label as their content description before the change. Built from source with it, the same rows report no label, role, selected or disabled state, and their text is in the accessibility tree.