Skip to content

fix(android): correct accessibility focus order in inverted FlatList#6934

Merged
OtavioStasiak merged 35 commits intodevelopfrom
test.flatlist-inverted-content
Feb 23, 2026
Merged

fix(android): correct accessibility focus order in inverted FlatList#6934
OtavioStasiak merged 35 commits intodevelopfrom
test.flatlist-inverted-content

Conversation

@OtavioStasiak
Copy link
Copy Markdown
Contributor

@OtavioStasiak OtavioStasiak commented Jan 20, 2026

Proposed changes

create a native module to use a native ScrollView component that uses a11y children in the right order.

Issue(s)

https://rocketchat.atlassian.net/browse/MA-96

How to test or reproduce

  • Open the app;
  • Navigate to RoomView;
  • Turn TalkBack on;
  • Navigate to the list and move down;
  • Scroll up using a three-finger gesture;
  • Try to focus on the older messages;

Screenshots

Types of changes

  • Bugfix (non-breaking change which fixes an issue)
  • Improvement (non-breaking change which improves a current function)
  • New feature (non-breaking change which adds functionality)
  • Documentation update (if none of the other choices apply)

Checklist

  • I have read the CONTRIBUTING doc
  • I have signed the CLA
  • Lint and unit tests pass locally with my changes
  • I have added tests that prove my fix is effective or that my feature works (if applicable)
  • I have added necessary documentation (if applicable)
  • Any dependent changes have been merged and published in downstream modules

Further comments

Summary by CodeRabbit

  • Bug Fixes

    • Fixed Android screen-reader (TalkBack) traversal for inverted (newest-first) lists so accessibility follows visual order.
  • New Features

    • Added Android-native inverted-scroll support and an Android-only scroll wrapper component.
    • Non-iOS list rendering now uses the new inverted-scroll wrapper for correct inverted-list behavior, improved scrolling, and sticky header handling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 20, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds Android-native inverted scroll views, content view, view managers, and a React Native package; registers the package in MainApplication. Introduces an Android-only TSX wrapper component and uses it as the FlatList scroll component on non-iOS platforms. Accessibility traversal order is reversed to match the visual newest-first order.

Changes

Cohort / File(s) Summary
Android Native Views
android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java, android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentView.java
New native view classes (extend ReactScrollView / ReactViewGroup). Add inversion flag and override addChildrenForAccessibility(...) to reverse accessibility child order for inverted (newest-first) lists.
Android View Managers
android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollViewManager.java, android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollContentViewManager.java
New ReactModule view managers exposing REACT_CLASS, getName() and createViewInstance(...) to instantiate the inverted native views.
Android Package & Registration
android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollPackage.java, android/app/src/main/java/chat/rocket/reactnative/MainApplication.kt
New InvertedScrollPackage registers view managers; package added to the app's native package list in MainApplication.kt.
React Native wrapper & usage
app/views/RoomView/List/components/InvertedScrollView.tsx, app/views/RoomView/List/components/List.tsx
New Android-only TSX component wiring native views via requireNativeComponent; forwards/merges refs and scroll methods, handles layout/content-size/sticky headers. List.tsx uses it as renderScrollComponent on non-iOS platforms.

Sequence Diagram(s)

sequenceDiagram
  participant JS as React (FlatList)
  participant RN as React Native Bridge
  participant Native as InvertedScrollView / InvertedScrollContentView
  participant A11y as Android TalkBack

  JS->>RN: renderScrollComponent -> InvertedScrollView
  RN->>Native: createViewInstance(ThemedReactContext)
  Native->>Native: mount children in inverted newest-first order
  A11y->>Native: request accessibility children traversal
  Native-->>A11y: return reversed children list to match visual order
  A11y-->>JS: user navigates (TalkBack follows visual order)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I flipped the feed with nimble feet,
TalkBack hops where new messages meet,
Children now march fresh to old,
Navigation follows what you behold,
A tiny flip — accessibility sweet!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main change: implementing a fix for accessibility focus order in inverted FlatList on Android.
Linked Issues check ✅ Passed The implementation correctly addresses MA-96 by adding native Android components that reverse accessibility children ordering in inverted scroll views, ensuring TalkBack traversal matches visual order.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing inverted FlatList accessibility on Android; no unrelated modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

@OtavioStasiak OtavioStasiak temporarily deployed to approve_e2e_testing January 27, 2026 18:34 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
app/views/RoomView/List/components/InvertedScrollView.tsx (1)

54-92: Method patching logic is sound; minor nit on nullish coalescing.

The useLayoutEffect with [] deps correctly patches methods once after mount. The setNativeProps fallback is now safe (no recursive call — good fix from prior feedback).

One small note on Lines 62–63: options?.x || 0 uses logical OR, which coerces all falsy values (including 0) to the default. Since 0 is also the desired default here, behavior is correct, but ?? 0 is more semantically precise and avoids surprising readers who expect || to swallow valid 0 values.

Suggested diff
-				const x = options?.x || 0;
-				const y = options?.y || 0;
+				const x = options?.x ?? 0;
+				const y = options?.y ?? 0;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/views/RoomView/List/components/InvertedScrollView.tsx` around lines 54 -
92, Update the nullish-coalescing for scroll coordinates in the patched scroll
methods: in the node.scrollTo implementation change the x and y fallbacks from
using logical OR to nullish coalescing (replace uses of options?.x || 0 and
options?.y || 0 with options?.x ?? 0 and options?.y ?? 0) so that explicit 0
values are preserved; leave other logic in scrollTo, scrollToEnd,
flashScrollIndicators, getScrollRef, and the setNativeProps fallback unchanged.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ead814b and d141c30.

📒 Files selected for processing (1)
  • app/views/RoomView/List/components/InvertedScrollView.tsx
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: OtavioStasiak
Repo: RocketChat/Rocket.Chat.ReactNative PR: 6499
File: app/containers/ServerItem/index.tsx:34-36
Timestamp: 2025-12-17T15:56:22.578Z
Learning: In the Rocket.Chat React Native codebase, for radio button components on iOS, include the selection state ("Selected"/"Unselected") in the accessibilityLabel instead of using accessibilityState={{ checked: hasCheck }}, because iOS VoiceOver has known issues with accessibilityRole="radio" + accessibilityState that prevent correct state announcement.
🧬 Code graph analysis (1)
app/views/RoomView/List/components/InvertedScrollView.tsx (2)
jest.setup.js (1)
  • React (179-179)
android/app/src/main/java/chat/rocket/reactnative/scroll/InvertedScrollView.java (1)
  • InvertedScrollView (13-36)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: ESLint and Test / run-eslint-and-test
  • GitHub Check: format
🔇 Additional comments (2)
app/views/RoomView/List/components/InvertedScrollView.tsx (2)

1-49: Types and native component wiring look good.

The type definitions, command constants, and requireNativeComponent usage are well-structured. The IScrollableMethods interface cleanly defines the scroll API surface, and the exported InvertedScrollViewRef intersection type provides a correct public API.


135-152: Render path and content view wiring look correct.

The conditional removeClippedSubviews handling for sticky headers (Line 145), the collapsableChildren flag driven by preserveChildren (Line 147), and the content-size-change proxy via onLayout (Lines 121–129) are all implemented correctly. The null guard at Line 135 is defensive — requireNativeComponent won't return null at runtime, but it doesn't hurt.

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@app/views/RoomView/List/components/InvertedScrollView.tsx`:
- Around line 94-103: The setRef callback currently forwards the native node to
externalRef immediately, but the IScrollableMethods (scrollTo, scrollToEnd,
etc.) are only attached in useLayoutEffect, so consumers who call those methods
synchronously from their own ref callback may see undefined; add a concise
inline comment next to setRef (referencing setRef, internalRef, externalRef,
InvertedScrollViewRef, and useLayoutEffect) explaining that the node is exposed
during commit and that scroll methods are attached in useLayoutEffect so callers
should access them in effects/handlers (not synchronously) to avoid undefined.
- Around line 105-114: The component destructures
maintainVisibleContentPosition, snapToAlignment, and stickyHeaderIndices out of
props and uses them for local flags (preserveChildren, hasStickyHeaders) but
then omits them from restWithoutStyle and never forwards them to the underlying
<ScrollView> in InvertedScrollView component; update the props forwarding so
that maintainVisibleContentPosition, snapToAlignment, and stickyHeaderIndices
are included in the props passed to ScrollView (or merged into restWithoutStyle)
so the native InvertedScrollView/ReactScrollView receives and honors these
behaviors (refer to the props named maintainVisibleContentPosition,
snapToAlignment, stickyHeaderIndices, the local flags preserveChildren and
hasStickyHeaders, and the restWithoutStyle/rest spread used when rendering
ScrollView).

---

Nitpick comments:
In `@app/views/RoomView/List/components/InvertedScrollView.tsx`:
- Around line 54-92: Update the nullish-coalescing for scroll coordinates in the
patched scroll methods: in the node.scrollTo implementation change the x and y
fallbacks from using logical OR to nullish coalescing (replace uses of
options?.x || 0 and options?.y || 0 with options?.x ?? 0 and options?.y ?? 0) so
that explicit 0 values are preserved; leave other logic in scrollTo,
scrollToEnd, flashScrollIndicators, getScrollRef, and the setNativeProps
fallback unchanged.

@github-actions
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown

@github-actions
Copy link
Copy Markdown

iOS Build Available

Rocket.Chat Experimental 4.70.0.108309

@github-actions
Copy link
Copy Markdown

Android Build Available

Rocket.Chat 4.70.0.108308

@github-actions
Copy link
Copy Markdown

iOS Build Available

Rocket.Chat 4.70.0.108307

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants