Skip to content

Commit c69d39f

Browse files
committed
RUM-18048: Fix Fabric text mapping in minified Android builds
1 parent a9111a7 commit c69d39f

3 files changed

Lines changed: 21 additions & 43 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -1,17 +1,15 @@
11
package com.datadog.reactnative.sessionreplay.utils.text
22

3-
import android.text.Spannable
43
import android.text.style.ForegroundColorSpan
5-
import android.view.View
64
import android.widget.TextView
7-
import com.datadog.android.api.InternalLogger
85
import com.datadog.android.sessionreplay.model.MobileSegment
96
import com.datadog.reactnative.sessionreplay.utils.DrawableUtils
107
import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
118
import com.facebook.react.bridge.ReactContext
9+
import com.facebook.react.views.text.ReactTextView
1210
import java.util.Locale
1311

14-
internal class FabricTextViewUtils(private val reactContext: ReactContext, private val logger: InternalLogger, drawableUtils: DrawableUtils): TextViewUtils(reactContext, drawableUtils) {
12+
internal class FabricTextViewUtils(reactContext: ReactContext, drawableUtils: DrawableUtils): TextViewUtils(reactContext, drawableUtils) {
1513

1614
override fun resolveTextStyle(
1715
textWireframe: MobileSegment.Wireframe.TextWireframe,
@@ -31,7 +29,9 @@ internal class FabricTextViewUtils(private val reactContext: ReactContext, priva
3129
}
3230

3331
private fun getTextColor(view: TextView, textWireframe: MobileSegment.Wireframe.TextWireframe): String {
34-
val spanned = getFieldFromView(view, SPANNED_FIELD_NAME) as? Spannable
32+
// Use the public accessor so R8 can rewrite the reference when it obfuscates ReactTextView.
33+
// Looking up the private mSpanned field by name breaks in minified applications.
34+
val spanned = (view as? ReactTextView)?.spanned
3535
val spans = spanned?.getSpans(0, spanned.length, ForegroundColorSpan::class.java)
3636
val fontColor = spans?.firstOrNull()?.foregroundColor?.let { formatAsRgba(it) } ?: textWireframe.textStyle.color
3737

@@ -47,28 +47,4 @@ internal class FabricTextViewUtils(private val reactContext: ReactContext, priva
4747
val fontFamily = textWireframe.textStyle.family
4848
return resolveFontFamily(fontFamily.lowercase(Locale.US))
4949
}
50-
51-
internal fun getFieldFromView(view: View, value: String): Any? {
52-
try {
53-
val field = view.javaClass.getDeclaredField(value)
54-
field.isAccessible = true
55-
return field.get(view)
56-
} catch (@Suppress("TooGenericExceptionCaught") e: Exception) {
57-
when (e) {
58-
is NoSuchFieldException -> handleError(e, RESOLVE_FABRICFIELD_ERROR)
59-
is NullPointerException -> handleError(e, NULL_FABRICFIELD_ERROR)
60-
else -> handleError(e, RESOLVE_FABRICFIELD_ERROR)
61-
}
62-
return null
63-
}
64-
}
65-
66-
private fun handleError(e: Exception, message: String) {
67-
logger.log(
68-
level = InternalLogger.Level.WARN,
69-
targets = listOf(InternalLogger.Target.MAINTAINER, InternalLogger.Target.TELEMETRY),
70-
messageBuilder = { message },
71-
throwable = e
72-
)
73-
}
74-
}
50+
}

packages/react-native-session-replay/android/src/main/kotlin/com/datadog/reactnative/sessionreplay/utils/text/TextViewUtils.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -141,23 +141,17 @@ internal abstract class TextViewUtils(private val reactContext: ReactContext, pr
141141

142142
@VisibleForTesting
143143
companion object {
144-
internal const val SPANNED_FIELD_NAME = "mSpanned"
145-
146144
private const val ROBOTO_TYPEFACE_NAME = "roboto"
147145
private const val SERIF_FAMILY_NAME = "serif"
148146
private const val SANS_SERIF_FAMILY_NAME = "roboto, sans-serif"
149147
internal const val MONOSPACE_FAMILY_NAME = "monospace"
150148

151-
internal const val RESOLVE_FABRICFIELD_ERROR = "Unable to resolve field from fabric view"
152-
internal const val NULL_FABRICFIELD_ERROR = "Null value found when trying to resolve field from fabric view"
153-
154-
155149
fun create(reactContext: ReactContext, logger: InternalLogger): TextViewUtils {
156150
return when (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
157-
true -> FabricTextViewUtils(reactContext, logger, ReactViewBackgroundDrawableUtils())
151+
true -> FabricTextViewUtils(reactContext, ReactViewBackgroundDrawableUtils())
158152
false -> LegacyTextViewUtils(reactContext, logger, ReactViewBackgroundDrawableUtils())
159153
}
160154
}
161155
}
162156

163-
}
157+
}

packages/react-native-session-replay/android/src/test/kotlin/com/datadog/reactnative/sessionreplay/utils/text/TextViewUtilsTest.kt

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.datadog.reactnative.sessionreplay.utils.formatAsRgba
2323
import com.datadog.reactnative.sessionreplay.utils.text.TextViewUtils.Companion.MONOSPACE_FAMILY_NAME
2424
import com.datadog.reactnative.tools.unit.forge.ForgeConfigurator
2525
import com.facebook.react.bridge.ReactContext
26+
import com.facebook.react.views.text.ReactTextView
2627
import com.facebook.react.views.text.internal.span.CustomStyleSpan
2728
import com.facebook.react.views.view.ReactViewBackgroundDrawable
2829
import fr.xgouchet.elmyr.Forge
@@ -42,7 +43,6 @@ import org.mockito.Mockito.mock
4243
import org.mockito.Mockito.spy
4344
import org.mockito.junit.jupiter.MockitoExtension
4445
import org.mockito.junit.jupiter.MockitoSettings
45-
import org.mockito.kotlin.any
4646
import org.mockito.kotlin.eq
4747
import org.mockito.kotlin.whenever
4848
import org.mockito.quality.Strictness
@@ -60,6 +60,9 @@ internal class TextViewUtilsTest {
6060
@Mock
6161
lateinit var mockTextView: TextView
6262

63+
@Mock
64+
lateinit var mockReactTextView: ReactTextView
65+
6366
@Mock
6467
lateinit var mockReactViewBackgroundDrawable: ReactViewBackgroundDrawable
6568

@@ -116,12 +119,11 @@ internal class TextViewUtilsTest {
116119
val realFabricUtils =
117120
FabricTextViewUtils(
118121
mockReactContext,
119-
mockLogger,
120122
mockDrawableUtils
121123
)
122124

123125
testedUtils = spy(realUtils)
124-
fabricTestedUtils = spy(realFabricUtils)
126+
fabricTestedUtils = realFabricUtils
125127
}
126128

127129
@Test
@@ -455,14 +457,20 @@ internal class TextViewUtilsTest {
455457
whenever(mockForegroundColorSpan.foregroundColor).thenReturn(-1)
456458

457459
val spannable = mock(Spannable::class.java)
458-
doReturn(spannable).whenever(fabricTestedUtils).getFieldFromView(any(), any())
460+
whenever(mockReactTextView.spanned).thenReturn(spannable)
461+
whenever(mockReactTextView.background).thenReturn(null)
462+
whenever(mockReactTextView.textSize).thenReturn(16f)
459463

460464
whenever(spannable.getSpans(anyInt(), anyInt(), eq(ForegroundColorSpan::class.java)))
461465
.thenReturn(
462466
arrayOf(mockForegroundColorSpan)
463467
)
464468

465-
val result = fabricTestedUtils.addReactNativeProperties(fakeWireframe, mockTextView, 0f)
469+
val result = fabricTestedUtils.addReactNativeProperties(
470+
fakeWireframe,
471+
mockReactTextView,
472+
0f
473+
)
466474
assertThat(result.textStyle.color).isEqualTo("#ffffffff")
467475
}
468476

0 commit comments

Comments
 (0)