From d65943f20c48889ce088a15e16d775288748d961 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bniewski?= Date: Thu, 18 Jun 2026 13:21:02 +0200 Subject: [PATCH 1/6] fix --- .../cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 1de44e58614..5e973210b3f 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -820,6 +820,10 @@ void ReanimatedModuleProxy::performOperations() { void ReanimatedModuleProxy::performNonLayoutOperations() { ReanimatedSystraceSection s("ReanimatedModuleProxy::performNonLayoutOperations"); + if constexpr (!shouldUseSynchronousUpdatesInPerformOperations()) { + return; + } + UpdatesBatch updatesBatch; { auto lock = updatesRegistryManager_->lock(); From 25f5bb5ab734ce81867e3e0fa1cf4d9b0c2c5450 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bniewski?= Date: Fri, 19 Jun 2026 07:35:22 +0200 Subject: [PATCH 2/6] Fix2 --- .../NativeModules/ReanimatedModuleProxy.cpp | 4 ---- .../java/com/swmansion/reanimated/NativeProxy.kt | 15 ++++++++++++++- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp index 5e973210b3f..1de44e58614 100644 --- a/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp +++ b/packages/react-native-reanimated/Common/cpp/reanimated/NativeModules/ReanimatedModuleProxy.cpp @@ -820,10 +820,6 @@ void ReanimatedModuleProxy::performOperations() { void ReanimatedModuleProxy::performNonLayoutOperations() { ReanimatedSystraceSection s("ReanimatedModuleProxy::performNonLayoutOperations"); - if constexpr (!shouldUseSynchronousUpdatesInPerformOperations()) { - return; - } - UpdatesBatch updatesBatch; { auto lock = updatesRegistryManager_->lock(); diff --git a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt index ae77d2cbd7a..0a524820ad7 100644 --- a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +++ b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt @@ -222,13 +222,26 @@ open class NativeProxy { return true } + private val mountingManager: Any by lazy { + FabricUIManager::class.java.getDeclaredField("mMountingManager").run { + isAccessible = true + get(mFabricUIManager) + } + } + + private val updatePropsSynchronouslyMethod by lazy { + mountingManager.javaClass.methods.first { + it.name.startsWith("updatePropsSynchronously") && it.parameterTypes.size == 2 + }.apply { isAccessible = true } + } + @DoNotStrip fun synchronouslyUpdateUIProps( intBuffer: IntArray, doubleBuffer: DoubleArray, ) { SynchronousPropsBufferParser.parse(intBuffer, doubleBuffer) { viewTag, props -> - mFabricUIManager.synchronouslyUpdateViewOnUIThread(viewTag, props) + updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props) } } From d74fb4b04cb9134fc0930b82a76443cd12a57826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bniewski?= Date: Fri, 19 Jun 2026 08:27:53 +0200 Subject: [PATCH 3/6] Reformat --- .../src/main/java/com/swmansion/reanimated/NativeProxy.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt index 0a524820ad7..e29c53a6e69 100644 --- a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +++ b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt @@ -230,9 +230,10 @@ open class NativeProxy { } private val updatePropsSynchronouslyMethod by lazy { - mountingManager.javaClass.methods.first { - it.name.startsWith("updatePropsSynchronously") && it.parameterTypes.size == 2 - }.apply { isAccessible = true } + mountingManager.javaClass.methods + .first { + it.name.startsWith("updatePropsSynchronously") && it.parameterTypes.size == 2 + }.apply { isAccessible = true } } @DoNotStrip From c07d6f71a8e1e14df811e6d63d959cefc9f32dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bniewski?= Date: Fri, 19 Jun 2026 09:58:51 +0200 Subject: [PATCH 4/6] Add guards --- .../com/swmansion/reanimated/NativeProxy.kt | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt index e29c53a6e69..73774630111 100644 --- a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +++ b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt @@ -3,6 +3,7 @@ package com.swmansion.reanimated import android.content.ContentResolver import android.os.SystemClock import android.provider.Settings +import android.util.Log import com.facebook.jni.HybridData import com.facebook.proguard.annotations.DoNotStrip import com.facebook.react.bridge.NativeModule @@ -222,6 +223,11 @@ open class NativeProxy { return true } + // TODO(#9681): Temporary workaround. Since RN 0.86, overrideBySynchronousMountPropsAtMountingAndroid + // defaults on, so RN's only public synchronous-update API (synchronouslyUpdateViewOnUIThread) seeds + // the tagToSynchronousMountProps cache that then clamps later commits and freezes animations. We call + // MountingManager.updatePropsSynchronously directly (apply without seeding) via reflection because + // MountingManager is internal. Remove once RN exposes a non-seeding synchronous-update API. private val mountingManager: Any by lazy { FabricUIManager::class.java.getDeclaredField("mMountingManager").run { isAccessible = true @@ -236,13 +242,25 @@ open class NativeProxy { }.apply { isAccessible = true } } + private val getViewExistsMethod by lazy { + mountingManager.javaClass.methods + .first { + it.name.startsWith("getViewExists") && it.parameterTypes.size == 1 + }.apply { isAccessible = true } + } + @DoNotStrip fun synchronouslyUpdateUIProps( intBuffer: IntArray, doubleBuffer: DoubleArray, ) { SynchronousPropsBufferParser.parse(intBuffer, doubleBuffer) { viewTag, props -> - updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props) + if (getViewExistsMethod.invoke(mountingManager, viewTag) == true) { + try { + updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props) + } catch (ignored: Exception) { + } + } } } From 756ae9066345011fecfe8ae0d894146fe5c579b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bniewski?= Date: Fri, 19 Jun 2026 10:11:57 +0200 Subject: [PATCH 5/6] Remove redundant if --- .../java/com/swmansion/reanimated/NativeProxy.kt | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt index 73774630111..0046ca24730 100644 --- a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +++ b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt @@ -242,24 +242,16 @@ open class NativeProxy { }.apply { isAccessible = true } } - private val getViewExistsMethod by lazy { - mountingManager.javaClass.methods - .first { - it.name.startsWith("getViewExists") && it.parameterTypes.size == 1 - }.apply { isAccessible = true } - } - @DoNotStrip fun synchronouslyUpdateUIProps( intBuffer: IntArray, doubleBuffer: DoubleArray, ) { SynchronousPropsBufferParser.parse(intBuffer, doubleBuffer) { viewTag, props -> - if (getViewExistsMethod.invoke(mountingManager, viewTag) == true) { - try { - updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props) - } catch (ignored: Exception) { - } + try { + updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props) + } catch (e: Exception) { + Log.w("Reanimated", "synchronouslyUpdateUIProps failed for tag $viewTag", e) } } } From 619cd0b66e58790653a0737496f897487452b355 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Wi=C5=9Bniewski?= Date: Fri, 19 Jun 2026 11:14:21 +0200 Subject: [PATCH 6/6] Add version guard --- .../android/build.gradle.kts | 7 ++++++ .../com/swmansion/reanimated/NativeProxy.kt | 25 ++++++++++++------- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/packages/react-native-reanimated/android/build.gradle.kts b/packages/react-native-reanimated/android/build.gradle.kts index 7c57979bc64..0975509613c 100644 --- a/packages/react-native-reanimated/android/build.gradle.kts +++ b/packages/react-native-reanimated/android/build.gradle.kts @@ -108,6 +108,12 @@ if (project != rootProject) { val packageDir: File = project.projectDir.parentFile val reactNativeRootDir: File = resolveReactNativeDirectory() val REACT_NATIVE_VERSION: String = getReactNativeVersion() +val IS_REACT_NATIVE_86_OR_NEWER: Boolean = run { + val parts = REACT_NATIVE_VERSION.split(".") + val major = parts.getOrNull(0)?.toIntOrNull() ?: 0 + val minor = parts.getOrNull(1)?.toIntOrNull() ?: 0 + major > 0 || minor >= 86 +} val REANIMATED_VERSION: String = getReanimatedVersion() val IS_REANIMATED_EXAMPLE_APP: Boolean = safeAppExtGet("isReanimatedExampleApp", false)?.toString()?.toBoolean() ?: false val REANIMATED_PROFILING: Boolean = safeAppExtGet("enableReanimatedProfiling", false)?.toString()?.toBoolean() ?: false @@ -171,6 +177,7 @@ android { buildConfigField("String", "REANIMATED_VERSION_JAVA", "\"$REANIMATED_VERSION\"") buildConfigField("boolean", "IS_INTERNAL_BUILD", "false") buildConfigField("int", "EXOPACKAGE_FLAGS", "0") + buildConfigField("boolean", "IS_REACT_NATIVE_86_OR_NEWER", IS_REACT_NATIVE_86_OR_NEWER.toString()) @Suppress("UnstableApiUsage") externalNativeBuild { diff --git a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt index 0046ca24730..48e37714232 100644 --- a/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt +++ b/packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/NativeProxy.kt @@ -223,11 +223,14 @@ open class NativeProxy { return true } - // TODO(#9681): Temporary workaround. Since RN 0.86, overrideBySynchronousMountPropsAtMountingAndroid - // defaults on, so RN's only public synchronous-update API (synchronouslyUpdateViewOnUIThread) seeds - // the tagToSynchronousMountProps cache that then clamps later commits and freezes animations. We call - // MountingManager.updatePropsSynchronously directly (apply without seeding) via reflection because - // MountingManager is internal. Remove once RN exposes a non-seeding synchronous-update API. + // TODO(#9681): Temporary workaround for RN >= 0.86. Since RN 0.86, + // overrideBySynchronousMountPropsAtMountingAndroid defaults on, so RN's only public + // synchronous-update API (synchronouslyUpdateViewOnUIThread) seeds the tagToSynchronousMountProps + // cache that then clamps later commits and freezes animations. On RN >= 0.86 we instead call + // MountingManager.updatePropsSynchronously directly (apply without cache seeding) via reflection, since + // MountingManager is internal. On older RN the flag is off, so we keep the original RN path + // unchanged (gated by BuildConfig.IS_REACT_NATIVE_86_OR_NEWER, derived from the RN version at + // build time). Remove once RN exposes a non-seeding synchronous-update API. private val mountingManager: Any by lazy { FabricUIManager::class.java.getDeclaredField("mMountingManager").run { isAccessible = true @@ -248,10 +251,14 @@ open class NativeProxy { doubleBuffer: DoubleArray, ) { SynchronousPropsBufferParser.parse(intBuffer, doubleBuffer) { viewTag, props -> - try { - updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props) - } catch (e: Exception) { - Log.w("Reanimated", "synchronouslyUpdateUIProps failed for tag $viewTag", e) + if (BuildConfig.IS_REACT_NATIVE_86_OR_NEWER) { + try { + updatePropsSynchronouslyMethod.invoke(mountingManager, viewTag, props) + } catch (e: Exception) { + Log.w("Reanimated", "synchronouslyUpdateUIProps failed for tag $viewTag", e) + } + } else { + mFabricUIManager.synchronouslyUpdateViewOnUIThread(viewTag, props) } } }