Repository navigation
Skip the unchanged scroll state update when a ScrollView mounts on Android - #59719
Open
hryhoriiK97 wants to merge 1 commit into
Open
hryhoriiK97 wants to merge 1 commit into
hryhoriiK97 wants to merge 1 commit into
Conversation
…droid onLayout calls scrollTo, which calls updateFabricScrollState. Its dedupe compares against lastStateUpdateScroll, which starts at (-1, -1), so the first offset always gets through, even when it is already in the state. That runs a full commit that changes nothing. Before a view's first update, read the offset from the state into lastStateUpdateScroll and let the existing dedupe decide. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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:
On Android, every
ScrollViewcommits one state update on mount that changes nothing.onLayoutcallsscrollTo, which callsReactScrollViewHelper.updateFabricScrollState. That method dedupes againstlastStateUpdateScroll, but this starts at(-1, -1), so the first offset always gets through, even when it's the(0, 0)already in the state. The update then runs a full commit on the JS thread (clone, layout and differ) and sends anUpdateStateitem back to Java. On iOS,_updateStateWithContentOffsetalready skips an update when the offset hasn't changed.Now, before a view's first update,
updateFabricScrollStatereads the offset from the state intolastStateUpdateScroll, and the existing dedupe does the rest. This costs one state read per view, not one per scroll frame.I didn't start
lastStateUpdateScrollat(0, 0), because a view created for a state that already holds another offset would then skip the update that corrects the state.Changelog:
[ANDROID] [CHANGED] -
ScrollViewno longer commits an unchanged scroll state when it mountsTest Plan:
Unit tests: the new
ReactScrollViewHelperStateTestcovers a first update that matches the state (not sent), one that differs from it (sent), one without state data (sent), and later updates (sent once per offset, with one state read). The two tests that expect a skipped update fail without this change.25 tests, 0 failures.
yarn format-kotlinmakes no further changes.Behavior: on a Galaxy A16, a temporary Playground screen read a marker's position with
measureInWindow, and both builds reported the same positions at every step:contentOffset, vertical and horizontalscrollTo, vertical and horizontaldisplay: noneon the parentMeasurements on a Galaxy A16 (Android 16): two release RNTester APKs built from this commit's base, one with and one without this change, with default feature flags, compiled with
cmd package compile -m speed. A temporary Playground screen mounts and unmounts the scenario 12 times. JS thread CPU comes from/proc/<pid>/task/<tid>/stat. The APKs were installed in alternating blocks, A/B/B/A twice, which gives 4 fresh processes per arm for each scenario. The table shows medians per mount and unmount:ScrollViews, perScrollViewScrollViewwith 5 horizontal ones and 40 cardsScrollViewwith 300 cardsFlatListwith 40 cardsThat's about 0.5–0.7 ms of JS time per
ScrollViewon this phone, so it only shows on screens with several of them. The 100-ScrollViewcase is artificial and shows the cost of each commit. In the second scenario the ranges overlap slightly (85.0–88.3 ms and 79.2–85.8 ms). UI thread time didn't change in any scenario.Not tested on a device:
enableViewRecyclingForScrollViewandenableViewCulling. Thedisplay: nonetoggle may not recreate the view, so the case of a state that already holds another offset is covered only by the unit test. WithenableViewCullingoruseTraitHiddenOnAndroidon,loadFabricScrollStatealready setslastStateUpdateScroll, so the new read doesn't run after that.🤖 Generated with Claude Code