Repository navigation
Reset accessibility state when recycling Android views - #59047
Closed
YevheniiKotyrlo wants to merge 4 commits into
Closed
YevheniiKotyrlo wants to merge 4 commits into
YevheniiKotyrlo wants to merge 4 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
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.
|
@javache has imported this pull request. If you are a Meta employee, you can view this in D125427268. |
YevheniiKotyrlo
marked this pull request as draft
October 16, 2026 09:44
…tions Clearing the whole delegate in prepareToRecycleView also removed the ReactScrollViewAccessibilityDelegate that ReactScrollView, ReactHorizontalScrollView and ReactNestedScrollView install in their constructors, which recycleView never installs again. The delegate now stays, and ReactAccessibilityDelegate.prepareToRecycle() clears the actions it cached from the previous props. accessibilityLabelledBy was only read in the call that set it, so it is a local there.
YevheniiKotyrlo
marked this pull request as ready for review
October 16, 2026 10:23
javache
approved these changes
Oct 16, 2026
Collaborator
|
This pull request was successfully merged by @YevheniiKotyrlo in 6f73542 When will my fix make it into a release? | How to file a pick request? |
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 actions its accessibility delegate cached. 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 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 stays, sinceReactScrollView,ReactHorizontalScrollViewandReactNestedScrollViewinstall theirs in their constructors: a newReactAccessibilityDelegate.prepareToRecycle()clears the actions it cached, andaccessibilityLabelledBy, read only in the call that sets it, is a local there. 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 six fail: content description"Dismiss sheet",roletagLIST, 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. Another case recycles each scroll view and checks that it keeps theReactScrollViewAccessibilityDelegateits constructor installed. With the change,./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest -PenableWarningsAsErrors=truepasses (727 tests) and the release sources compile with the same flag; the RNTester debug build and the rest ofbuild_androidwere 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.Both device runs used the first revision of this change, which cleared the delegate on recycle instead of resetting it.