Skip to content

[cherry-pick][4.1] Fix commits during the Android draw pass#9124

Open
bartlomiejbloniarz wants to merge 2 commits into4.1-stablefrom
@bartlomiejbloniarz/android-draw-cherry-pick-4.1
Open

[cherry-pick][4.1] Fix commits during the Android draw pass#9124
bartlomiejbloniarz wants to merge 2 commits into4.1-stablefrom
@bartlomiejbloniarz/android-draw-cherry-pick-4.1

Conversation

@bartlomiejbloniarz
Copy link
Contributor

Cherry picks #9078 and #9072

Separates the synchronous UI props update logic into its own
`applySynchronousUpdates` method.

Adds `partitionUpdates` and
`shouldUseSynchronousUpdatesInPerformOperations` helpers to support the
extracted method.
This PR intends to solve the most annoying crash that we currently face
on Android. The crash is caused by Reanimated calling
`ShadowTree::commit` when handling events that were emitted while in the
Android drawing pass.

Performing a commit can cause other changes queued by React Native to
also get applied. This can lead to the view hierarchy being modified,
which is not allowed while in the draw pass.

To fix this, we have a workaround where if we are in the draw pass, we
instead only apply non-layout changes (that can be applied without a
ShadowTree commit), and schedule the layout changes to happen when we
are outside of the draw call. We want to apply the non-layout updates
there to maintain things like transform driven sticky headers.

closes #8422 🤞

I added a new Android Draw Pass example, it would also make sense to
test the sticky header and other event related examples
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Cherry-picks upstream fixes to prevent Reanimated/Fabric commits from occurring during the Android draw pass (a common crash source), by detecting draw-pass execution and applying only “safe” non-layout/synchronous UI prop updates while deferring layout-affecting commits to the next frame.

Changes:

  • Add Android draw-pass detection (DrawPassDetector) and route event-driven updates through a draw-pass-aware operations path.
  • Introduce “non-layout” operations path end-to-end (Java → JNI → C++), and extract synchronous UI-props logic into applySynchronousUpdates.
  • Add getPendingUpdates() and a new example screen to reproduce/validate the Android crash scenario.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NodesManager.java Adds draw-pass-aware execution path and non-layout operations call during draw.
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.java Exposes new JNI method and uses draw-pass-aware operations when flushing.
packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/DrawPassDetector.java New helper to detect whether current UI-thread turn is in a draw pass.
packages/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h Declares performNonLayoutOperations on the JNI proxy.
packages/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp Implements and registers the new JNI method.
packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.h Adds performNonLayoutOperations and extracted applySynchronousUpdates declaration.
packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp Refactors synchronous updates, partitions updates, and adds non-layout operations path.
packages/react-native-reanimated/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.h Adds getPendingUpdates() API.
packages/react-native-reanimated/Common/cpp/reanimated/Fabric/updates/UpdatesRegistry.cpp Implements getPendingUpdates() to snapshot pending updates without committing.
apps/common-app/src/apps/reanimated/examples/index.ts Registers the new Android draw-pass example.
apps/common-app/src/apps/reanimated/examples/StickyHeaderExample.tsx Tweaks animated style to include a layout-affecting prop (width) for testing.
apps/common-app/src/apps/reanimated/examples/AndroidDrawPassExample.tsx New repro example for the Android draw-pass crash/fix behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

Comment on lines +801 to +808
void ReanimatedModuleProxy::performNonLayoutOperations() {
ReanimatedSystraceSection s(
"ReanimatedModuleProxy::performNonLayoutOperations");

UpdatesBatch updatesBatch = animatedPropsRegistry_->getPendingUpdates();

applySynchronousUpdates(updatesBatch, true);
}
"[Reanimated] Border radius string must be a percentage");
}
intBuffer.push_back(CMD_UNIT_PERCENT);
doubleBuffer.push_back(std::stof(valueStr.substr(0, -1)));
Comment on lines +136 to +145
void performOperationsRespectingDrawPass() {
mDrawPassDetector.initialize();
if (isInDrawPass()) {
performNonLayoutOperations();
startUpdatingOnAnimationFrame();
return;
}

performOperations();
}
Comment on lines +30 to +38
void initialize() {
Activity activity = mContext.getCurrentActivity();
if (activity == null) {
return;
}

View decorView = activity.getWindow().getDecorView();
if (decorView == mDecorView) {
return;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants