Skip to content

Fix one-frame jump after maintainVisibleContentPosition adjustment on Android - #58187

Open
Critteros wants to merge 1 commit into
react:mainfrom
Critteros:fix-mvcp-one-frame-jump-android
Open

Fix one-frame jump after maintainVisibleContentPosition adjustment on Android#58187
Critteros wants to merge 1 commit into
react:mainfrom
Critteros:fix-mvcp-one-frame-jump-android

Conversation

@Critteros

@Critteros Critteros commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Summary:

Fixes #58186.

On Android, a ScrollView with maintainVisibleContentPosition shows a one-frame jump when items are prepended. The visible rows move down by the height of the new item for one frame, then move back. In a chat app this happens on every incoming message

Fabric moves the children and MaintainVisibleScrollPositionHelper corrects the scroll offset in the same UI thread pass. The moved children invalidate themselves, so the frame is drawn right away. View.scrollTo only posts an invalidation for the next frame. The current frame replays the ScrollView display list with the previous offset, and the content is drawn shifted by the adjustment

The bug needs two conditions. The anchor and the items move in one mount. Nothing in that mount changes the size of the content container, so no layout pass invalidates the ScrollView. This is the case for lists that set the container height through an Animated.Value, such as LegendList

This change invalidates the ScrollView right after scrollToPreservingMomentum in MaintainVisibleScrollPositionHelper.updateScrollPositionInternal, so the display list is recorded again in the same frame.

Changelog:

[ANDROID] [FIXED] - Fix one-frame content jump when maintainVisibleContentPosition adjusts the scroll offset during a mount

Test Plan:

Added MaintainVisibleScrollPositionHelperTest. It builds the view tree from the reproduction with a ReactScrollView, wires maintainVisibleContentPosition through setMaintainVisibleContentPosition, and runs the two-mount prepend. It asserts that the scroll offset is corrected and that the ScrollView is flagged for display list recording (PFLAG_INVALIDATED) in the same mount.

./gradlew :packages:react-native:ReactAndroid:testDebugUnitTest --tests "com.facebook.react.views.scroll.MaintainVisibleScrollPositionHelperTest"

Also tested in the reproduction app from #58186 (Expo SDK 57, React Native 0.86.3, LegendList with maintainVisibleContentPosition):

  1. Checked out the v0.86.3 tag and cherry-picked this commit on top
  2. Published the Android artifacts with ./gradlew publishAllToMavenTempLocal. This writes react-android:0.86.3 to /tmp/maven-local
  3. Pointed the reproduction app at that repository. Note: the Expo template declares its own allprojects { repositories { mavenCentral() } } in android/build.gradle. This block runs before the React Native Gradle plugin adds its repositories, so react.internal.mavenLocalRepo alone is not sufficient. The local repository must be the first entry in that block
  4. Ran the app on an Android emulator and prepended messages. Before the change, each prepend shows a one-frame jump of the visible rows. With the change, the rows stay in place

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Aug 28, 2026
@Critteros
Critteros marked this pull request as ready for review August 28, 2026 15:54
@facebook-github-tools facebook-github-tools Bot added the Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team. label Aug 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Shared with Meta Applied via automation to indicate that an Issue or Pull Request has been shared with the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Android] One-frame jump on prepend with maintainVisibleContentPosition

1 participant