From dfca164bfd2c8b2f8dc5191a97f3a50b1c839ef2 Mon Sep 17 00:00:00 2001 From: Ahmed Sbai Date: Mon, 3 Aug 2026 03:17:59 +0200 Subject: [PATCH] Fix crash when setting a percentage borderRadius on Image (Android) Percentage border radii arrive from JS as strings ('50%'), but ReactImageManager's borderRadius @ReactPropGroup setter still typed the prop as Float, so the reflection-based property updater crashed with "java.lang.String cannot be cast to java.lang.Double" under the new architecture. Accept a Dynamic and parse it with LengthPercentage.setFromDynamic, mirroring the migration ReactViewManager received in 0.75. The Float overload is kept as a deprecated pass-through for backward compatibility. Rendering needs no changes since the manager already delegates to BackgroundStyleApplicator, which resolves percentages. Fixes #53977 Changelog: [ANDROID] [FIXED] - Fix crash when setting a percentage borderRadius on Image --- .../ReactAndroid/api/ReactAndroid.api | 1 + .../react/views/image/ReactImageManager.kt | 18 ++++++++---- .../views/image/ReactImagePropertyTest.kt | 29 +++++++++++++++++++ .../js/examples/Image/ImageExample.js | 9 ++++++ 4 files changed, 51 insertions(+), 6 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index b2095f8b4290..09961fb35236 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -5312,6 +5312,7 @@ public final class com/facebook/react/views/image/ReactImageManager : com/facebo public final fun setBlurRadius (Lcom/facebook/react/views/image/ReactImageView;F)V public final fun setBorderColor (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/Integer;)V public final fun setBorderRadius (Lcom/facebook/react/views/image/ReactImageView;IF)V + public final fun setBorderRadius (Lcom/facebook/react/views/image/ReactImageView;ILcom/facebook/react/bridge/Dynamic;)V public final fun setBorderWidth (Lcom/facebook/react/views/image/ReactImageView;F)V public final fun setDefaultSource (Lcom/facebook/react/views/image/ReactImageView;Ljava/lang/String;)V public final fun setFadeDuration (Lcom/facebook/react/views/image/ReactImageView;I)V diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt index 7322d6ee5cb6..a3e184249811 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/image/ReactImageManager.kt @@ -12,13 +12,14 @@ import android.graphics.PorterDuff import com.facebook.common.logging.FLog import com.facebook.drawee.backends.pipeline.Fresco import com.facebook.drawee.controller.AbstractDraweeControllerBuilder +import com.facebook.react.bridge.Dynamic +import com.facebook.react.bridge.DynamicFromObject import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap import com.facebook.react.common.ReactConstants import com.facebook.react.module.annotations.ReactModule import com.facebook.react.uimanager.BackgroundStyleApplicator import com.facebook.react.uimanager.LengthPercentage -import com.facebook.react.uimanager.LengthPercentageType import com.facebook.react.uimanager.SimpleViewManager import com.facebook.react.uimanager.ThemedReactContext import com.facebook.react.uimanager.ViewProps @@ -171,13 +172,18 @@ public constructor( ViewProps.BORDER_BOTTOM_RIGHT_RADIUS, ViewProps.BORDER_BOTTOM_LEFT_RADIUS, ], - defaultFloat = Float.NaN, + ) + public fun setBorderRadius(view: ReactImageView, index: Int, rawBorderRadius: Dynamic) { + val borderRadius = LengthPercentage.setFromDynamic(rawBorderRadius) + BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], borderRadius) + } + + @Deprecated( + "Don't use setBorderRadius(view, index, Float) as it was deprecated in React Native 0.87.0.", + ReplaceWith("setBorderRadius(view, index, DynamicFromObject(borderRadius))"), ) public fun setBorderRadius(view: ReactImageView, index: Int, borderRadius: Float) { - val radius = - if (borderRadius.isNaN()) null - else LengthPercentage(borderRadius, LengthPercentageType.POINT) - BackgroundStyleApplicator.setBorderRadius(view, BorderRadiusProp.values()[index], radius) + setBorderRadius(view, index, DynamicFromObject(borderRadius)) } @ReactProp(name = ViewProps.RESIZE_MODE) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt index 4c4501f13be6..1f167787a3f3 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/image/ReactImagePropertyTest.kt @@ -23,9 +23,13 @@ import com.facebook.react.bridge.JavaOnlyMap import com.facebook.react.bridge.ReactTestHelper.createMockCatalystInstance import com.facebook.react.common.ReactConstants import com.facebook.react.internal.featureflags.ReactNativeFeatureFlagsForTests +import com.facebook.react.uimanager.BackgroundStyleApplicator import com.facebook.react.uimanager.DisplayMetricsHolder +import com.facebook.react.uimanager.LengthPercentage +import com.facebook.react.uimanager.LengthPercentageType import com.facebook.react.uimanager.ReactStylesDiffMap import com.facebook.react.uimanager.ThemedReactContext +import com.facebook.react.uimanager.style.BorderRadiusProp import com.facebook.react.util.RNLog import com.facebook.react.views.imagehelper.ImageSource import com.facebook.soloader.SoLoader @@ -142,6 +146,31 @@ class ReactImagePropertyTest { .isEqualTo(view.imageSource) } + @Test + fun testBorderRadius() { + val viewManager = ReactImageManager() + val view = viewManager.createViewInstance(themeContext) + + // Percentage border radii arrive as strings and must not crash the property updater + viewManager.updateProperties(view, buildStyles("borderRadius", "50%")) + assertThat(BackgroundStyleApplicator.getBorderRadius(view, BorderRadiusProp.BORDER_RADIUS)) + .isEqualTo(LengthPercentage(50f, LengthPercentageType.PERCENT)) + + viewManager.updateProperties(view, buildStyles("borderRadius", 10.0)) + assertThat(BackgroundStyleApplicator.getBorderRadius(view, BorderRadiusProp.BORDER_RADIUS)) + .isEqualTo(LengthPercentage(10f, LengthPercentageType.POINT)) + + viewManager.updateProperties(view, buildStyles("borderTopLeftRadius", "25%")) + assertThat( + BackgroundStyleApplicator.getBorderRadius(view, BorderRadiusProp.BORDER_TOP_LEFT_RADIUS) + ) + .isEqualTo(LengthPercentage(25f, LengthPercentageType.PERCENT)) + + viewManager.updateProperties(view, buildStyles("borderRadius", null)) + assertThat(BackgroundStyleApplicator.getBorderRadius(view, BorderRadiusProp.BORDER_RADIUS)) + .isNull() + } + @Test fun testResizeMode() { val viewManager = ReactImageManager() diff --git a/packages/rn-tester/js/examples/Image/ImageExample.js b/packages/rn-tester/js/examples/Image/ImageExample.js index 55d4a8e7da26..2e75d03c5e48 100644 --- a/packages/rn-tester/js/examples/Image/ImageExample.js +++ b/packages/rn-tester/js/examples/Image/ImageExample.js @@ -1186,6 +1186,11 @@ const styles = StyleSheet.create({ borderColor: 'red', backgroundColor: 'yellow', }, + borderRadiusPercentage: { + borderWidth: 4, + borderRadius: '50%', + borderColor: 'green', + }, boxShadow: { margin: 10, }, @@ -1448,6 +1453,10 @@ exports.examples = [ style={[styles.base, styles.borderRadius5]} source={fullImage} /> + ); },