diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt index ad8f5ca188ee..1947b8d71f41 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/fabric/mounting/SurfaceMountingManager.kt @@ -626,13 +626,14 @@ internal constructor( public fun storeSynchronousMountPropsOverride(reactTag: Int, props: ReadableMap): Unit { if (ReactNativeFeatureFlags.overrideBySynchronousMountPropsAtMountingAndroid()) { val propsMap = getMapFromPropsReadableMap(props) - var synchronousMountProps = tagToSynchronousMountProps[reactTag] - if (synchronousMountProps != null) { - synchronousMountProps.putAll(propsMap) + val synchronousMountProps = tagToSynchronousMountProps[reactTag] ?: mutableMapOf() + removeNullTransformAndOpacityPropsFromPropsReadableMap(props, synchronousMountProps) + synchronousMountProps.putAll(propsMap) + if (synchronousMountProps.isEmpty()) { + tagToSynchronousMountProps.remove(reactTag) } else { - synchronousMountProps = propsMap + tagToSynchronousMountProps[reactTag] = synchronousMountProps } - tagToSynchronousMountProps[reactTag] = synchronousMountProps } } @@ -1332,7 +1333,10 @@ internal constructor( for ((propKey, propValue) in patchMap) { if (outputReadableMap.hasKey(propKey)) { if (propKey == PROP_TRANSFORM) { - assert(outputReadableMap.getType(propKey) == ReadableType.Array && propValue is List<*>) + val outputType = outputReadableMap.getType(propKey) + assert( + (outputType == ReadableType.Array || outputType == ReadableType.Null) && + propValue is List<*>) val array = WritableNativeArray() for (item in propValue as List<*>) { if (item is Map<*, *>) { @@ -1350,7 +1354,10 @@ internal constructor( } outputReadableMap.putArray(propKey, array) } else if (propKey == PROP_OPACITY) { - assert(outputReadableMap.getType(propKey) == ReadableType.Number && propValue is Number) + val outputType = outputReadableMap.getType(propKey) + assert( + (outputType == ReadableType.Number || outputType == ReadableType.Null) && + propValue is Number) outputReadableMap.putDouble(propKey, (propValue as Number).toDouble()) } } @@ -1387,6 +1394,22 @@ internal constructor( return outputMap } + private fun removeNullTransformAndOpacityPropsFromPropsReadableMap( + readableMap: ReadableMap, + outputMap: MutableMap, + ) { + // Native Animated uses synchronous null updates to restore animated-managed props. + // Keep this scoped to props stored by this override path today. + if (readableMap.hasKey(PROP_TRANSFORM) && + readableMap.getType(PROP_TRANSFORM) == ReadableType.Null) { + outputMap.remove(PROP_TRANSFORM) + } + if (readableMap.hasKey(PROP_OPACITY) && + readableMap.getType(PROP_OPACITY) == ReadableType.Null) { + outputMap.remove(PROP_OPACITY) + } + } + // prevents unchecked conversion warn of the type private fun getViewGroupManager(viewState: ViewState): IViewGroupManager { val viewManager = diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/SurfaceMountingManagerSynchronousMountPropsTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/SurfaceMountingManagerSynchronousMountPropsTest.kt index 2c0787d17c84..ed1a1b4b6c7e 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/SurfaceMountingManagerSynchronousMountPropsTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/fabric/SurfaceMountingManagerSynchronousMountPropsTest.kt @@ -10,15 +10,17 @@ package com.facebook.react.fabric import com.facebook.react.ReactRootView +import com.facebook.react.bridge.JavaOnlyArray import com.facebook.react.bridge.JavaOnlyMap +import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReactTestHelper import com.facebook.react.fabric.mounting.MountingManager -import com.facebook.react.fabric.mounting.MountingManager.MountItemExecutor import com.facebook.react.fabric.mounting.SurfaceMountingManager import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewManager import com.facebook.react.uimanager.ViewManagerRegistry +import com.facebook.react.views.view.ReactViewGroup import com.facebook.react.views.view.ReactViewManager import com.facebook.testutils.shadows.ShadowNativeLoader import com.facebook.testutils.shadows.ShadowNativeMap @@ -67,8 +69,8 @@ class SurfaceMountingManagerSynchronousMountPropsTest { themedReactContext = ThemedReactContext(reactContext, reactContext, null, -1) mountingManager = MountingManager( - ViewManagerRegistry(listOf>(ReactViewManager())), - MountItemExecutor {}, + ViewManagerRegistry(listOf>(TestReactViewManager())), + {}, ) } @@ -83,6 +85,25 @@ class SurfaceMountingManagerSynchronousMountPropsTest { smm.addViewAt(surfaceId, tag, 0) } + private class TestReactViewManager : ReactViewManager() { + override fun setTransformProperty( + view: ReactViewGroup, + transforms: ReadableArray?, + transformOrigin: ReadableArray?, + ) { + view.translationY = 0.0f + if (transforms == null) { + return + } + for (i in 0..