From aac0485b24427ae83e768035b9451ded533b568c Mon Sep 17 00:00:00 2001 From: HyunWoo Lee Date: Wed, 12 Nov 2025 13:42:47 +0900 Subject: [PATCH 1/4] [kotlinify] Refacetor BaseJavaModule from java to kotlin --- ...{BaseJavaModule.java => BaseJavaModule.kt} | 100 +- .../AccessibilityInfoModule.kt | 132 ++- .../react/modules/network/NetworkingModule.kt | 853 +++++++++--------- .../platform/android/SampleLegacyModule.kt | 21 +- 4 files changed, 545 insertions(+), 561 deletions(-) rename packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/{BaseJavaModule.java => BaseJavaModule.kt} (58%) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt similarity index 58% rename from packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java rename to packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt index ca7baa18081300..35bacf7434ae2f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt @@ -5,20 +5,17 @@ * LICENSE file in the root directory of this source tree. */ -package com.facebook.react.bridge; - -import static com.facebook.infer.annotation.ThreadConfined.ANY; - -import androidx.annotation.Nullable; -import com.facebook.common.logging.FLog; -import com.facebook.infer.annotation.Assertions; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.infer.annotation.ThreadConfined; -import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.common.ReactConstants; -import com.facebook.react.common.annotations.StableReactNativeAPI; -import com.facebook.react.common.build.ReactBuildConfig; -import java.util.Map; +package com.facebook.react.bridge + +import com.facebook.common.logging.FLog +import com.facebook.infer.annotation.Assertions +import com.facebook.infer.annotation.Nullsafe +import com.facebook.infer.annotation.ThreadConfined +import com.facebook.infer.annotation.ThreadConfined.ANY +import com.facebook.proguard.annotations.DoNotStrip +import com.facebook.react.common.ReactConstants +import com.facebook.react.common.annotations.StableReactNativeAPI +import com.facebook.react.common.build.ReactBuildConfig /** * Base class for Catalyst native modules whose implementations are written in Java. Default @@ -50,58 +47,48 @@ */ @Nullsafe(Nullsafe.Mode.LOCAL) @StableReactNativeAPI -public abstract class BaseJavaModule implements NativeModule { - // taken from Libraries/Utilities/MessageQueue.js - public static final String METHOD_TYPE_ASYNC = "async"; - public static final String METHOD_TYPE_PROMISE = "promise"; - public static final String METHOD_TYPE_SYNC = "sync"; +public abstract class BaseJavaModule : NativeModule { + @JvmField + protected var mEventEmitterCallback: CxxCallbackImpl? = null - protected @Nullable CxxCallbackImpl mEventEmitterCallback; + private val mReactApplicationContext: ReactApplicationContext? - private final @Nullable ReactApplicationContext mReactApplicationContext; + public constructor() : this(null) - public BaseJavaModule() { - this(null); - } - - public BaseJavaModule(@Nullable ReactApplicationContext reactContext) { - mReactApplicationContext = reactContext; + public constructor(reactContext: ReactApplicationContext?) { + mReactApplicationContext = reactContext } /** * @return a map of constants this module exports to JS. Supports JSON types. */ - public @Nullable Map getConstants() { - return null; - } + public open val constants: Map? + get() = null - @Override - public void initialize() { + public override fun initialize() { // do nothing } - @Override - public boolean canOverrideExistingModule() { - return false; + public override fun canOverrideExistingModule(): Boolean { + return false } /** * The CatalystInstance is going away with Venice. Therefore, the TurboModule infra introduces the * invalidate() method to allow NativeModules to clean up after themselves. */ - @Override - public void invalidate() {} + public override fun invalidate() {} /** - * Subclasses can use this method to access {@link ReactApplicationContext} passed as a - * constructor. + * Property accessor for Kotlin code using property syntax. Subclasses can use this to access + * {@link ReactApplicationContext} passed as a constructor. */ - protected final ReactApplicationContext getReactApplicationContext() { - return Assertions.assertNotNull( - mReactApplicationContext, - "Tried to get ReactApplicationContext even though NativeModule wasn't instantiated with" - + " one"); - } + protected val reactApplicationContext: ReactApplicationContext + @JvmName("getReactApplicationContext") + get() = Assertions.assertNotNull( + mReactApplicationContext, + "Tried to get ReactApplicationContext even though NativeModule wasn't instantiated with one" + ) /** * Subclasses can use this method to access {@link ReactApplicationContext} passed as a @@ -117,24 +104,31 @@ protected final ReactApplicationContext getReactApplicationContext() { * thread-safe. */ @ThreadConfined(ANY) - protected @Nullable final ReactApplicationContext getReactApplicationContextIfActiveOrWarn() { + protected fun getReactApplicationContextIfActiveOrWarn(): ReactApplicationContext? { if (mReactApplicationContext != null && mReactApplicationContext.hasActiveReactInstance()) { - return mReactApplicationContext; + return mReactApplicationContext } // We want to collect data about how often this happens, but SoftExceptions will cause a crash // in debug mode, which isn't usually desirable. - String msg = "React Native Instance has already disappeared: requested by " + getName(); + val msg = "React Native Instance has already disappeared: requested by $name" if (ReactBuildConfig.DEBUG) { - FLog.w(ReactConstants.TAG, msg); + FLog.w(ReactConstants.TAG, msg) } else { - ReactSoftExceptionLogger.logSoftException(ReactConstants.TAG, new RuntimeException(msg)); + ReactSoftExceptionLogger.logSoftException(ReactConstants.TAG, RuntimeException(msg)) } - return null; + return null } @DoNotStrip - protected void setEventEmitterCallback(CxxCallbackImpl eventEmitterCallback) { - mEventEmitterCallback = eventEmitterCallback; + protected fun setEventEmitterCallback(eventEmitterCallback: CxxCallbackImpl) { + mEventEmitterCallback = eventEmitterCallback + } + + public companion object { + // taken from Libraries/Utilities/MessageQueue.js + public const val METHOD_TYPE_ASYNC: String = "async" + public const val METHOD_TYPE_PROMISE: String = "promise" + public const val METHOD_TYPE_SYNC: String = "sync" } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt index 899034570d6e90..33a6a7bc9f5eff 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/accessibilityinfo/AccessibilityInfoModule.kt @@ -28,9 +28,9 @@ import com.facebook.react.module.annotations.ReactModule */ @ReactModule(name = NativeAccessibilityInfoSpec.NAME) internal class AccessibilityInfoModule(context: ReactApplicationContext) : - NativeAccessibilityInfoSpec(context), LifecycleEventListener { + NativeAccessibilityInfoSpec(context), LifecycleEventListener { private inner class ReactTouchExplorationStateChangeListener : - AccessibilityManager.TouchExplorationStateChangeListener { + AccessibilityManager.TouchExplorationStateChangeListener { override fun onTouchExplorationStateChanged(enabled: Boolean) { updateAndSendTouchExplorationChangeEvent(enabled) } @@ -40,7 +40,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : // `accessibilityState` conflicts with React Native props and confuses developers. Therefore, the // name `accessibilityServiceChange` is used here instead. private inner class ReactAccessibilityServiceChangeListener : - AccessibilityManager.AccessibilityStateChangeListener { + AccessibilityManager.AccessibilityStateChangeListener { override fun onAccessibilityStateChanged(enabled: Boolean) { updateAndSendAccessibilityServiceChangeEvent(enabled) } @@ -48,35 +48,36 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : // Listener that is notified when the global TRANSITION_ANIMATION_SCALE. private val animationScaleObserver: ContentObserver = - object : ContentObserver(UiThreadUtil.getUiThreadHandler()) { - override fun onChange(selfChange: Boolean) { - this.onChange(selfChange, null) - } + object : ContentObserver(UiThreadUtil.getUiThreadHandler()) { + override fun onChange(selfChange: Boolean) { + this.onChange(selfChange, null) + } - override fun onChange(selfChange: Boolean, uri: Uri?) { - if (reactApplicationContext.hasActiveReactInstance()) { - updateAndSendReduceMotionChangeEvent() - } + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (reactApplicationContext.hasActiveReactInstance()) { + updateAndSendReduceMotionChangeEvent() } } + } + // Listener that is notified when the ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED changes. private val highTextContrastObserver: ContentObserver = - object : ContentObserver(UiThreadUtil.getUiThreadHandler()) { - override fun onChange(selfChange: Boolean) { - this.onChange(selfChange, null) - } + object : ContentObserver(UiThreadUtil.getUiThreadHandler()) { + override fun onChange(selfChange: Boolean) { + this.onChange(selfChange, null) + } - override fun onChange(selfChange: Boolean, uri: Uri?) { - if (reactApplicationContext.hasActiveReactInstance()) { - updateAndSendHighTextContrastChangeEvent() - } + override fun onChange(selfChange: Boolean, uri: Uri?) { + if (reactApplicationContext.hasActiveReactInstance()) { + updateAndSendHighTextContrastChangeEvent() } } + } private val accessibilityManager: AccessibilityManager? private val touchExplorationStateChangeListener: ReactTouchExplorationStateChangeListener = - ReactTouchExplorationStateChangeListener() + ReactTouchExplorationStateChangeListener() private val accessibilityServiceChangeListener: ReactAccessibilityServiceChangeListener = - ReactAccessibilityServiceChangeListener() + ReactAccessibilityServiceChangeListener() private val contentResolver: ContentResolver private var reduceMotionEnabled = false private var highTextContrastEnabled = false @@ -89,7 +90,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : init { val appContext = context.applicationContext accessibilityManager = - appContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager? + appContext.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager? contentResolver = reactApplicationContext.contentResolver touchExplorationEnabled = accessibilityManager?.isTouchExplorationEnabled ?: false accessibilityServiceEnabled = accessibilityManager?.isEnabled ?: false @@ -103,11 +104,8 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : // Disabling animations in developer settings will set the animation scale to "0.0" // but setting "reduce motion" / "disable animations" will set the animation scale to "0". val rawValue = - Settings.Global.getString(contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) - - if (rawValue == null) { - return false - } + Settings.Global.getString(contentResolver, Settings.Global.TRANSITION_ANIMATION_SCALE) + ?: return false try { // In some locales, the decimal separator is a comma instead of a dot, @@ -122,14 +120,14 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : private val isInvertColorsEnabledValue: Boolean get() = - try { - Settings.Secure.getInt( - contentResolver, - Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, - ) == 1 - } catch (e: Settings.SettingNotFoundException) { - false - } + try { + Settings.Secure.getInt( + contentResolver, + Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, + ) == 1 + } catch (e: Settings.SettingNotFoundException) { + false + } private val isGrayscaleEnabledValue: Boolean get() { @@ -139,7 +137,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : // for grayscale mode to be detected, the color correction accessibility setting should be // on and the color correction mode should be set to grayscale (0) return Settings.Secure.getInt(contentResolver, colorCorrectionSettingKey) == 1 && - Settings.Secure.getInt(contentResolver, colorModeSettingKey) == 0 + Settings.Secure.getInt(contentResolver, colorModeSettingKey) == 0 } catch (e: Settings.SettingNotFoundException) { return false } @@ -148,9 +146,9 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : private val isHighTextContrastEnabledValue: Boolean get() { return Settings.Secure.getInt( - contentResolver, - ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT, - 0, + contentResolver, + ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT, + 0, ) != 0 } @@ -183,7 +181,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : if (reduceMotionEnabled != isReduceMotionEnabled) { reduceMotionEnabled = isReduceMotionEnabled getReactApplicationContextIfActiveOrWarn() - ?.emitDeviceEvent(REDUCE_MOTION_EVENT_NAME, reduceMotionEnabled) + ?.emitDeviceEvent(REDUCE_MOTION_EVENT_NAME, reduceMotionEnabled) } } @@ -192,7 +190,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : if (invertColorsEnabled != isInvertColorsEnabled) { invertColorsEnabled = isInvertColorsEnabled getReactApplicationContextIfActiveOrWarn() - ?.emitDeviceEvent(INVERT_COLOR_EVENT_NAME, invertColorsEnabled) + ?.emitDeviceEvent(INVERT_COLOR_EVENT_NAME, invertColorsEnabled) } } @@ -201,10 +199,10 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : if (highTextContrastEnabled != isHighTextContrastEnabled) { highTextContrastEnabled = isHighTextContrastEnabled getReactApplicationContextIfActiveOrWarn() - ?.emitDeviceEvent( - HIGH_TEXT_CONTRAST_EVENT_NAME, - highTextContrastEnabled, - ) + ?.emitDeviceEvent( + HIGH_TEXT_CONTRAST_EVENT_NAME, + highTextContrastEnabled, + ) } } @@ -212,10 +210,10 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : if (touchExplorationEnabled != enabled) { touchExplorationEnabled = enabled val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() - if (reactApplicationContext != null) { - getReactApplicationContext() - .emitDeviceEvent(TOUCH_EXPLORATION_EVENT_NAME, touchExplorationEnabled) - } + reactApplicationContext?.emitDeviceEvent( + TOUCH_EXPLORATION_EVENT_NAME, + touchExplorationEnabled + ) } } @@ -223,10 +221,10 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : if (accessibilityServiceEnabled != enabled) { accessibilityServiceEnabled = enabled val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() - if (reactApplicationContext != null) { - getReactApplicationContext() - .emitDeviceEvent(ACCESSIBILITY_SERVICE_EVENT_NAME, accessibilityServiceEnabled) - } + reactApplicationContext?.emitDeviceEvent( + ACCESSIBILITY_SERVICE_EVENT_NAME, + accessibilityServiceEnabled + ) } } @@ -235,23 +233,19 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : if (grayscaleModeEnabled != isGrayscaleModeEnabled) { grayscaleModeEnabled = isGrayscaleModeEnabled getReactApplicationContextIfActiveOrWarn() - ?.emitDeviceEvent(GRAYSCALE_MODE_EVENT_NAME, grayscaleModeEnabled) + ?.emitDeviceEvent(GRAYSCALE_MODE_EVENT_NAME, grayscaleModeEnabled) } } override fun onHostResume() { - accessibilityManager?.addTouchExplorationStateChangeListener( - touchExplorationStateChangeListener - ) + accessibilityManager?.addTouchExplorationStateChangeListener(touchExplorationStateChangeListener) accessibilityManager?.addAccessibilityStateChangeListener(accessibilityServiceChangeListener) val transitionUri = Settings.Global.getUriFor(Settings.Global.TRANSITION_ANIMATION_SCALE) contentResolver.registerContentObserver(transitionUri, false, animationScaleObserver) val highTextContrastUri = - Settings.Secure.getUriFor(ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT) + Settings.Secure.getUriFor(ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT) contentResolver.registerContentObserver(highTextContrastUri, false, highTextContrastObserver) - updateAndSendTouchExplorationChangeEvent( - accessibilityManager?.isTouchExplorationEnabled == true - ) + updateAndSendTouchExplorationChangeEvent(accessibilityManager?.isTouchExplorationEnabled == true) updateAndSendAccessibilityServiceChangeEvent(accessibilityManager?.isEnabled == true) updateAndSendReduceMotionChangeEvent() updateAndSendHighTextContrastChangeEvent() @@ -261,7 +255,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : override fun onHostPause() { accessibilityManager?.removeTouchExplorationStateChangeListener( - touchExplorationStateChangeListener + touchExplorationStateChangeListener ) accessibilityManager?.removeAccessibilityStateChangeListener(accessibilityServiceChangeListener) contentResolver.unregisterContentObserver(animationScaleObserver) @@ -270,9 +264,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : override fun initialize() { reactApplicationContext.addLifecycleEventListener(this) - updateAndSendTouchExplorationChangeEvent( - accessibilityManager?.isTouchExplorationEnabled == true - ) + updateAndSendTouchExplorationChangeEvent(accessibilityManager?.isTouchExplorationEnabled == true) updateAndSendAccessibilityServiceChangeEvent(accessibilityManager?.isEnabled == true) updateAndSendReduceMotionChangeEvent() updateAndSendHighTextContrastChangeEvent() @@ -307,10 +299,10 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : return } recommendedTimeout = - accessibilityManager?.getRecommendedTimeoutMillis( - originalTimeout.toInt(), - AccessibilityManager.FLAG_CONTENT_CONTROLS, - ) ?: 0 + accessibilityManager?.getRecommendedTimeoutMillis( + originalTimeout.toInt(), + AccessibilityManager.FLAG_CONTENT_CONTROLS, + ) ?: 0 successCallback.invoke(recommendedTimeout) } @@ -321,7 +313,7 @@ internal class AccessibilityInfoModule(context: ReactApplicationContext) : private const val TOUCH_EXPLORATION_EVENT_NAME = "touchExplorationDidChange" private const val ACCESSIBILITY_SERVICE_EVENT_NAME = "accessibilityServiceDidChange" private const val ACCESSIBILITY_HIGH_TEXT_CONTRAST_ENABLED_CONSTANT = - "high_text_contrast_enabled" // constant is marked with @hide + "high_text_contrast_enabled" // constant is marked with @hide private const val INVERT_COLOR_EVENT_NAME = "invertColorDidChange" private const val GRAYSCALE_MODE_EVENT_NAME = "grayscaleModeDidChange" } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.kt index a762999c2017b0..6cf6df5d2984f9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/network/NetworkingModule.kt @@ -48,10 +48,10 @@ import okio.Okio /** Implements the XMLHttpRequest JavaScript interface. */ @ReactModule(name = NativeNetworkingAndroidSpec.NAME) public class NetworkingModule( - reactContext: ReactApplicationContext, - defaultUserAgent: String?, - client: OkHttpClient, - networkInterceptorCreators: List?, + reactContext: ReactApplicationContext, + defaultUserAgent: String?, + client: OkHttpClient, + networkInterceptorCreators: List?, ) : NativeNetworkingAndroidSpec(reactContext) { /** @@ -65,7 +65,8 @@ public class NetworkingModule( /** * Fetch the URI and return a tuple containing the JS body payload and the raw response body. */ - @Throws(IOException::class) public fun fetch(uri: Uri): Pair + @Throws(IOException::class) + public fun fetch(uri: Uri): Pair } /** Allows adding custom handling to build the [RequestBody] from the JS body payload. */ @@ -83,7 +84,8 @@ public class NetworkingModule( public fun supports(responseType: String): Boolean /** Returns the JS body payload for the [ResponseBody]. */ - @Throws(IOException::class) public fun toResponseData(data: ByteArray): WritableMap + @Throws(IOException::class) + public fun toResponseData(data: ByteArray): WritableMap } private val client: OkHttpClient @@ -108,12 +110,7 @@ public class NetworkingModule( this.client = resolvedClient val cookieJar = resolvedClient.cookieJar() - cookieJarContainer = - if (cookieJar is CookieJarContainer) { - cookieJar - } else { - null - } + cookieJarContainer = cookieJar as? CookieJarContainer this.defaultUserAgent = defaultUserAgent } @@ -124,14 +121,14 @@ public class NetworkingModule( * @param client the [OkHttpClient] to be used for networking */ internal constructor( - context: ReactApplicationContext, - defaultUserAgent: String?, - client: OkHttpClient, + context: ReactApplicationContext, + defaultUserAgent: String?, + client: OkHttpClient, ) : this(context, defaultUserAgent, client, null) /** @param context the ReactContext of the application */ public constructor( - context: ReactApplicationContext + context: ReactApplicationContext ) : this(context, null, OkHttpClientProvider.createClient(context.applicationContext), null) /** @@ -140,13 +137,13 @@ public class NetworkingModule( * would be called to attach the interceptors to the client. */ public constructor( - context: ReactApplicationContext, - networkInterceptorCreators: List?, + context: ReactApplicationContext, + networkInterceptorCreators: List?, ) : this( - context, - null, - OkHttpClientProvider.createClient(context.applicationContext), - networkInterceptorCreators, + context, + null, + OkHttpClientProvider.createClient(context.applicationContext), + networkInterceptorCreators, ) /** @@ -155,17 +152,17 @@ public class NetworkingModule( * caller does not provide one explicitly */ public constructor( - context: ReactApplicationContext, - defaultUserAgent: String?, + context: ReactApplicationContext, + defaultUserAgent: String?, ) : this( - context, - defaultUserAgent, - OkHttpClientProvider.createClient(context.applicationContext), - null, + context, + defaultUserAgent, + OkHttpClientProvider.createClient(context.applicationContext), + null, ) @Deprecated( - """To be removed in a future release. See + """To be removed in a future release. See https://github.com/facebook/react-native/pull/37798#pullrequestreview-1518338914""" ) public interface CustomClientBuilder : com.facebook.react.modules.network.CustomClientBuilder @@ -211,52 +208,51 @@ public class NetworkingModule( } private fun extractOrGenerateDevToolsRequestId( - data: ReadableMap?, + data: ReadableMap?, ): String = - (if ( - data != null && - data.hasKey(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) && - data.getType(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) == ReadableType.String - ) - data.getString(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) - else null) ?: UUID.randomUUID().toString() + (if (data != null && + data.hasKey(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) && + data.getType(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) == ReadableType.String + ) + data.getString(REQUEST_DATA_KEY_DEVTOOLS_REQUEST_ID) + else null) ?: UUID.randomUUID().toString() override fun sendRequest( - method: String, - url: String, - requestIdAsDouble: Double, - headers: ReadableArray?, - data: ReadableMap?, - responseType: String, - useIncrementalUpdates: Boolean, - timeoutAsDouble: Double, - withCredentials: Boolean, + method: String, + url: String, + requestIdAsDouble: Double, + headers: ReadableArray?, + data: ReadableMap?, + responseType: String, + useIncrementalUpdates: Boolean, + timeoutAsDouble: Double, + withCredentials: Boolean, ) { val requestId = requestIdAsDouble.toInt() val timeout = timeoutAsDouble.toInt() val devToolsRequestId = extractOrGenerateDevToolsRequestId(data) try { sendRequestInternalReal( - method, - url, - requestId, - headers, - data, - responseType, - useIncrementalUpdates, - timeout, - withCredentials, - devToolsRequestId, + method, + url, + requestId, + headers, + data, + responseType, + useIncrementalUpdates, + timeout, + withCredentials, + devToolsRequestId, ) } catch (th: Throwable) { FLog.e(TAG, "Failed to send url request: $url", th) NetworkEventUtil.onRequestError( - getReactApplicationContextIfActiveOrWarn(), - requestId, - devToolsRequestId, - th.message, - th, + getReactApplicationContextIfActiveOrWarn(), + requestId, + devToolsRequestId, + th.message, + th, ) } } @@ -264,42 +260,42 @@ public class NetworkingModule( @Deprecated("""sendRequestInternal is internal and will be made private in a future release.""") /** @param timeout value of 0 results in no timeout */ public fun sendRequestInternal( - method: String, - url: String?, - requestId: Int, - headers: ReadableArray?, - data: ReadableMap?, - responseType: String, - useIncrementalUpdates: Boolean, - timeout: Int, - withCredentials: Boolean, + method: String, + url: String?, + requestId: Int, + headers: ReadableArray?, + data: ReadableMap?, + responseType: String, + useIncrementalUpdates: Boolean, + timeout: Int, + withCredentials: Boolean, ) { sendRequestInternalReal( - method, - url, - requestId, - headers, - data, - responseType, - useIncrementalUpdates, - timeout, - withCredentials, - extractOrGenerateDevToolsRequestId(data), + method, + url, + requestId, + headers, + data, + responseType, + useIncrementalUpdates, + timeout, + withCredentials, + extractOrGenerateDevToolsRequestId(data), ) } /** @param timeout value of 0 results in no timeout */ private fun sendRequestInternalReal( - method: String, - url: String?, - requestId: Int, - headers: ReadableArray?, - data: ReadableMap?, - responseType: String, - useIncrementalUpdates: Boolean, - timeout: Int, - withCredentials: Boolean, - devToolsRequestId: String, + method: String, + url: String?, + requestId: Int, + headers: ReadableArray?, + data: ReadableMap?, + responseType: String, + useIncrementalUpdates: Boolean, + timeout: Int, + withCredentials: Boolean, + devToolsRequestId: String, ) { val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() try { @@ -313,42 +309,42 @@ public class NetworkingModule( // fix: UriHandlers which are not using file:// scheme fail in whatwg-fetch at this line // https://github.com/JakeChampion/fetch/blob/main/fetch.js#L547 val response = - Response.Builder() - .protocol(Protocol.HTTP_1_1) - .request(Request.Builder().url(url.orEmpty()).build()) - .code(200) - .message("OK") - .build() + Response.Builder() + .protocol(Protocol.HTTP_1_1) + .request(Request.Builder().url(url.orEmpty()).build()) + .code(200) + .message("OK") + .build() NetworkEventUtil.onResponseReceived( - reactApplicationContext, - requestId, - devToolsRequestId, - url, - response, + reactApplicationContext, + requestId, + devToolsRequestId, + url, + response, ) NetworkEventUtil.onDataReceived( - reactApplicationContext, - requestId, - devToolsRequestId, - res, - rawBody, + reactApplicationContext, + requestId, + devToolsRequestId, + res, + rawBody, ) NetworkEventUtil.onRequestSuccess( - reactApplicationContext, - requestId, - devToolsRequestId, - encodedDataLength.toLong(), + reactApplicationContext, + requestId, + devToolsRequestId, + encodedDataLength.toLong(), ) return } } } catch (e: IOException) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - e.message, - e, + reactApplicationContext, + requestId, + devToolsRequestId, + e.message, + e, ) return } @@ -358,11 +354,11 @@ public class NetworkingModule( requestBuilder = Request.Builder().url(url.orEmpty()) } catch (e: Exception) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - e.message, - e, + reactApplicationContext, + requestId, + devToolsRequestId, + e.message, + e, ) return } @@ -386,32 +382,32 @@ public class NetworkingModule( val originalResponse = chain.proceed(chain.request()) val originalResponseBody = checkNotNull(originalResponse.body()) val responseBody = - ProgressResponseBody( - originalResponseBody, - object : ProgressListener { - var last: Long = System.nanoTime() - - override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) { - val now = System.nanoTime() - if (!done && !shouldDispatch(now, last)) { - return - } - if (responseType == "text") { - // For 'text' responses we continuously send response data with progress - // info to - // JS below, so no need to do anything here. - return - } - NetworkEventUtil.onDataReceivedProgress( - reactApplicationContext, - requestId, - bytesWritten, - contentLength, - ) - last = now - } - }, - ) + ProgressResponseBody( + originalResponseBody, + object : ProgressListener { + var last: Long = System.nanoTime() + + override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) { + val now = System.nanoTime() + if (!done && !shouldDispatch(now, last)) { + return + } + if (responseType == "text") { + // For 'text' responses we continuously send response data with progress + // info to + // JS below, so no need to do anything here. + return + } + NetworkEventUtil.onDataReceivedProgress( + reactApplicationContext, + requestId, + bytesWritten, + contentLength, + ) + last = now + } + }, + ) originalResponse.newBuilder().body(responseBody).build() } } @@ -428,11 +424,11 @@ public class NetworkingModule( val requestHeaders = extractHeaders(headers, data) if (requestHeaders == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Unrecognized headers format", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Unrecognized headers format", + null, ) return } @@ -454,16 +450,17 @@ public class NetworkingModule( var requestBody: RequestBody? when { data == null || method.lowercase() == "get" || method.lowercase() == "head" -> - requestBody = RequestBodyUtil.getEmptyBody(method) + requestBody = RequestBodyUtil.getEmptyBody(method) + handler != null -> requestBody = handler.toRequestBody(data, contentType) data.hasKey(REQUEST_BODY_KEY_STRING) -> { if (contentType == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Payload is set but no content-type header specified", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Payload is set but no content-type header specified", + null, ) return } @@ -476,11 +473,11 @@ public class NetworkingModule( } if (requestBody == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Failed to gzip request body", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Failed to gzip request body", + null, ) return } @@ -489,18 +486,18 @@ public class NetworkingModule( // appending the character set to the Content-Type header when otherwise unspecified // https://github.com/facebook/react-native/issues/8237 val charset = - if (contentMediaType == null) { - StandardCharsets.UTF_8 - } else { - checkNotNull(contentMediaType.charset(StandardCharsets.UTF_8)) - } + if (contentMediaType == null) { + StandardCharsets.UTF_8 + } else { + checkNotNull(contentMediaType.charset(StandardCharsets.UTF_8)) + } if (body == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Received request but body was empty", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Received request but body was empty", + null, ) return } @@ -508,14 +505,15 @@ public class NetworkingModule( requestBody = RequestBody.create(contentMediaType, body.toByteArray(charset)) } } + data.hasKey(REQUEST_BODY_KEY_BASE64) -> { if (contentType == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Payload is set but no content-type header specified", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Payload is set but no content-type header specified", + null, ) return } @@ -525,63 +523,65 @@ public class NetworkingModule( val contentMediaType = MediaType.parse(contentType) if (contentMediaType == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Invalid content type specified: $contentType", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Invalid content type specified: $contentType", + null, ) return } val base64DecodedString = ByteString.decodeBase64(base64String) if (base64DecodedString == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Request body base64 string was invalid", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Request body base64 string was invalid", + null, ) return } @Suppress("DEPRECATION") requestBody = RequestBody.create(contentMediaType, base64DecodedString) } + data.hasKey(REQUEST_BODY_KEY_URI) -> { if (contentType == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Payload is set but no content-type header specified", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Payload is set but no content-type header specified", + null, ) return } val uri = data.getString(REQUEST_BODY_KEY_URI) if (uri == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Request body URI field was set but null", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Request body URI field was set but null", + null, ) return } - val fileInputStream = RequestBodyUtil.getFileInputStream(getReactApplicationContext(), uri) + val fileInputStream = RequestBodyUtil.getFileInputStream(this.reactApplicationContext, uri) if (fileInputStream == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Could not retrieve file for uri $uri", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Could not retrieve file for uri $uri", + null, ) return } requestBody = RequestBodyUtil.create(MediaType.parse(contentType), fileInputStream) } + data.hasKey(REQUEST_BODY_KEY_FORMDATA) -> { if (contentType == null) { contentType = "multipart/form-data" @@ -589,18 +589,19 @@ public class NetworkingModule( val parts = data.getArray(REQUEST_BODY_KEY_FORMDATA) if (parts == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Received request but form data was empty", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Received request but form data was empty", + null, ) return } val multipartBuilder = - constructMultipartBody(parts, contentType, requestId, devToolsRequestId) ?: return + constructMultipartBody(parts, contentType, requestId, devToolsRequestId) ?: return requestBody = multipartBuilder.build() } + else -> { // Nothing in data payload, at least nothing we could understand anyway. requestBody = RequestBodyUtil.getEmptyBody(method) @@ -614,201 +615,198 @@ public class NetworkingModule( NetworkEventUtil.onCreateRequest(devToolsRequestId, request) client - .newCall(request) - .enqueue( - object : Callback { - override fun onFailure(call: Call, e: IOException) { - if (shuttingDown) { - return - } - removeRequest(requestId) - val errorMessage = - e.message ?: ("Error while executing request: ${e.javaClass.simpleName}") + .newCall(request) + .enqueue( + object : Callback { + override fun onFailure(call: Call, e: IOException) { + if (shuttingDown) { + return + } + removeRequest(requestId) + val errorMessage = + e.message ?: ("Error while executing request: ${e.javaClass.simpleName}") + NetworkEventUtil.onRequestError( + reactApplicationContext, + requestId, + devToolsRequestId, + errorMessage, + e, + ) + } + + @Throws(IOException::class) + override fun onResponse(call: Call, response: Response) { + if (shuttingDown) { + return + } + removeRequest(requestId) + // Before we touch the body send headers to JS + NetworkEventUtil.onResponseReceived( + reactApplicationContext, + requestId, + devToolsRequestId, + url, + response, + ) + + try { + // OkHttp implements something called transparent gzip, which mean that it will + // automatically add the Accept-Encoding gzip header and handle decoding + // internally. + // The issue is that it won't handle decoding if the user provides a + // Accept-Encoding + // header. This is also undesirable considering that iOS does handle the decoding + // even + // when the header is provided. To make sure this works in all cases, handle gzip + // body + // here also. This works fine since OKHttp will remove the Content-Encoding header + // if + // it used transparent gzip. + // See + // https://github.com/square/okhttp/blob/5b37cda9e00626f43acf354df145fd452c3031f1/okhttp/src/main/java/okhttp3/internal/http/BridgeInterceptor.java#L76-L111 + var responseBody: ResponseBody? = response.body() + if (responseBody == null) { NetworkEventUtil.onRequestError( + reactApplicationContext, + requestId, + devToolsRequestId, + "Response body is null", + null, + ) + return + } + if ("gzip".equals(response.header("Content-Encoding"), ignoreCase = true)) { + val gzipSource = GzipSource(responseBody.source()) + val parsedContentType = response.header("Content-Type")?.let(MediaType::parse) + responseBody = + @Suppress("DEPRECATION") + ResponseBody.create( + parsedContentType, + -1L, + Okio.buffer(gzipSource), + ) + } + // Check if a handler is registered + for (responseHandler in responseHandlers) { + if (responseHandler.supports(responseType)) { + val responseData = responseBody.bytes() + val res = responseHandler.toResponseData(responseData) + NetworkEventUtil.onDataReceived( reactApplicationContext, requestId, devToolsRequestId, - errorMessage, - e, - ) - } - - @Throws(IOException::class) - override fun onResponse(call: Call, response: Response) { - if (shuttingDown) { - return - } - removeRequest(requestId) - // Before we touch the body send headers to JS - NetworkEventUtil.onResponseReceived( + res, + responseData, + ) + NetworkEventUtil.onRequestSuccess( reactApplicationContext, requestId, devToolsRequestId, - url, - response, + responseBody.contentLength(), + ) + return + } + } + + // If JS wants progress updates during the download, and it requested a text + // response, + // periodically send response data updates to JS. + if (useIncrementalUpdates && responseType == "text") { + readWithProgress(requestId, devToolsRequestId, responseBody) + NetworkEventUtil.onRequestSuccess( + reactApplicationContext, + requestId, + devToolsRequestId, + responseBody.contentLength(), ) + return + } + // Otherwise send the data in one big chunk, in the format that JS requested. + var responseString: String? = "" + if (responseType == "text") { try { - // OkHttp implements something called transparent gzip, which mean that it will - // automatically add the Accept-Encoding gzip header and handle decoding - // internally. - // The issue is that it won't handle decoding if the user provides a - // Accept-Encoding - // header. This is also undesirable considering that iOS does handle the decoding - // even - // when the header is provided. To make sure this works in all cases, handle gzip - // body - // here also. This works fine since OKHttp will remove the Content-Encoding header - // if - // it used transparent gzip. - // See - // https://github.com/square/okhttp/blob/5b37cda9e00626f43acf354df145fd452c3031f1/okhttp/src/main/java/okhttp3/internal/http/BridgeInterceptor.java#L76-L111 - var responseBody: ResponseBody? = response.body() - if (responseBody == null) { - NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Response body is null", - null, - ) - return - } - if ("gzip".equals(response.header("Content-Encoding"), ignoreCase = true)) { - val gzipSource = GzipSource(responseBody.source()) - val parsedContentType = response.header("Content-Type")?.let(MediaType::parse) - responseBody = - @Suppress("DEPRECATION") - ResponseBody.create( - parsedContentType, - -1L, - Okio.buffer(gzipSource), - ) - } - // To satisfy the compiler, this is already checked above - checkNotNull(responseBody) - // Check if a handler is registered - for (responseHandler in responseHandlers) { - if (responseHandler.supports(responseType)) { - val responseData = responseBody.bytes() - val res = responseHandler.toResponseData(responseData) - NetworkEventUtil.onDataReceived( - reactApplicationContext, - requestId, - devToolsRequestId, - res, - responseData, - ) - NetworkEventUtil.onRequestSuccess( - reactApplicationContext, - requestId, - devToolsRequestId, - responseBody.contentLength(), - ) - return - } - } - - // If JS wants progress updates during the download, and it requested a text - // response, - // periodically send response data updates to JS. - if (useIncrementalUpdates && responseType == "text") { - readWithProgress(requestId, devToolsRequestId, responseBody) - NetworkEventUtil.onRequestSuccess( - reactApplicationContext, - requestId, - devToolsRequestId, - responseBody.contentLength(), - ) - return - } - - // Otherwise send the data in one big chunk, in the format that JS requested. - var responseString: String? = "" - if (responseType == "text") { - try { - responseString = responseBody.string() - } catch (e: IOException) { - if (response.request().method().equals("HEAD", ignoreCase = true)) { - // The request is an `HEAD` and the body is empty, - // the OkHttp will produce an exception. - // Ignore the exception to not invalidate the request in the - // Javascript layer. - // Introduced to fix issue #7463. - } else { - NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - e.message, - e, - ) - } - } - } else if (responseType == "base64") { - responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP) - } - NetworkEventUtil.onDataReceived( - reactApplicationContext, - requestId, - devToolsRequestId, - responseString, - responseType, - ) - NetworkEventUtil.onRequestSuccess( - reactApplicationContext, - requestId, - devToolsRequestId, - responseBody.contentLength(), - ) + responseString = responseBody.string() } catch (e: IOException) { - NetworkEventUtil.onRequestError( + if (response.request().method().equals("HEAD", ignoreCase = true)) { + // The request is an `HEAD` and the body is empty, + // the OkHttp will produce an exception. + // Ignore the exception to not invalidate the request in the + // Javascript layer. + // Introduced to fix issue #7463. + } else { + NetworkEventUtil.onRequestError( reactApplicationContext, requestId, devToolsRequestId, e.message, e, - ) + ) + } } + } else if (responseType == "base64") { + responseString = Base64.encodeToString(responseBody.bytes(), Base64.NO_WRAP) } + NetworkEventUtil.onDataReceived( + reactApplicationContext, + requestId, + devToolsRequestId, + responseString, + responseType, + ) + NetworkEventUtil.onRequestSuccess( + reactApplicationContext, + requestId, + devToolsRequestId, + responseBody.contentLength(), + ) + } catch (e: IOException) { + NetworkEventUtil.onRequestError( + reactApplicationContext, + requestId, + devToolsRequestId, + e.message, + e, + ) } - ) + } + }) } private fun wrapRequestBodyWithProgressEmitter( - requestBody: RequestBody?, - requestId: Int, + requestBody: RequestBody?, + requestId: Int, ): RequestBody? { if (requestBody == null) { return null } val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() return RequestBodyUtil.createProgressRequest( - requestBody, - object : ProgressListener { - var last: Long = System.nanoTime() - - override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) { - val now = System.nanoTime() - if (done || shouldDispatch(now, last)) { - NetworkEventUtil.onDataSend( - reactApplicationContext, - requestId, - bytesWritten, - contentLength, - ) - last = now - } + requestBody, + object : ProgressListener { + var last: Long = System.nanoTime() + + override fun onProgress(bytesWritten: Long, contentLength: Long, done: Boolean) { + val now = System.nanoTime() + if (done || shouldDispatch(now, last)) { + NetworkEventUtil.onDataSend( + reactApplicationContext, + requestId, + bytesWritten, + contentLength, + ) + last = now } - }, + } + }, ) } @Throws(IOException::class) private fun readWithProgress( - requestId: Int, - devToolsRequestId: String, - responseBody: ResponseBody, + requestId: Int, + devToolsRequestId: String, + responseBody: ResponseBody, ) { var totalBytesRead: Long = -1 var contentLength: Long = -1 @@ -821,13 +819,13 @@ public class NetworkingModule( } val charset = - if (responseBody.contentType() == null) { - StandardCharsets.UTF_8 - } else { - checkNotNull(responseBody.contentType()?.charset(StandardCharsets.UTF_8)) { - "Null character set for Content-Type: ${responseBody.contentType()}" - } + if (responseBody.contentType() == null) { + StandardCharsets.UTF_8 + } else { + checkNotNull(responseBody.contentType()?.charset(StandardCharsets.UTF_8)) { + "Null character set for Content-Type: ${responseBody.contentType()}" } + } val streamDecoder = ProgressiveStringDecoder(charset) val inputStream = responseBody.byteStream() try { @@ -836,12 +834,12 @@ public class NetworkingModule( val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() while ((inputStream.read(buffer).also { read = it }) != -1) { NetworkEventUtil.onIncrementalDataReceived( - reactApplicationContext, - requestId, - devToolsRequestId, - streamDecoder.decodeNext(buffer, read), - totalBytesRead, - contentLength, + reactApplicationContext, + requestId, + devToolsRequestId, + streamDecoder.decodeNext(buffer, read), + totalBytesRead, + contentLength, ) } } finally { @@ -887,21 +885,21 @@ public class NetworkingModule( public override fun removeListeners(count: Double): Unit = Unit private fun constructMultipartBody( - body: ReadableArray, - contentType: String, - requestId: Int, - devToolsRequestId: String, + body: ReadableArray, + contentType: String, + requestId: Int, + devToolsRequestId: String, ): MultipartBody.Builder? { val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() val multipartBuilder = MultipartBody.Builder() val mediaType = MediaType.parse(contentType) if (mediaType == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Invalid media type.", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Invalid media type.", + null, ) return null } @@ -911,11 +909,11 @@ public class NetworkingModule( val bodyPart = body.getMap(i) if (bodyPart == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Unrecognized FormData part.", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Unrecognized FormData part.", + null, ) return null } @@ -925,11 +923,11 @@ public class NetworkingModule( var headers = extractHeaders(headersArray, null) if (headers == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Missing or invalid header format for FormData part.", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Missing or invalid header format for FormData part.", + null, ) return null } @@ -942,57 +940,56 @@ public class NetworkingModule( headers = headers.newBuilder().removeAll(CONTENT_TYPE_HEADER_NAME).build() } - if ( - bodyPart.hasKey(REQUEST_BODY_KEY_STRING) && - bodyPart.getString(REQUEST_BODY_KEY_STRING) != null + if (bodyPart.hasKey(REQUEST_BODY_KEY_STRING) && + bodyPart.getString(REQUEST_BODY_KEY_STRING) != null ) { val bodyValue = bodyPart.getString(REQUEST_BODY_KEY_STRING).orEmpty() @Suppress("DEPRECATION") multipartBuilder.addPart(headers, RequestBody.create(partContentType, bodyValue)) - } else if ( - bodyPart.hasKey(REQUEST_BODY_KEY_URI) && bodyPart.getString(REQUEST_BODY_KEY_URI) != null + } else if (bodyPart.hasKey(REQUEST_BODY_KEY_URI) && + bodyPart.getString(REQUEST_BODY_KEY_URI) != null ) { if (partContentType == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Binary FormData part needs a content-type header.", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Binary FormData part needs a content-type header.", + null, ) return null } val fileContentUriStr = bodyPart.getString(REQUEST_BODY_KEY_URI) if (fileContentUriStr == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Body must have a valid file uri", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Body must have a valid file uri", + null, ) return null } val fileInputStream = - RequestBodyUtil.getFileInputStream(getReactApplicationContext(), fileContentUriStr) + RequestBodyUtil.getFileInputStream(this.reactApplicationContext, fileContentUriStr) if (fileInputStream == null) { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Could not retrieve file for uri $fileContentUriStr", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Could not retrieve file for uri $fileContentUriStr", + null, ) return null } multipartBuilder.addPart(headers, RequestBodyUtil.create(partContentType, fileInputStream)) } else { NetworkEventUtil.onRequestError( - reactApplicationContext, - requestId, - devToolsRequestId, - "Unrecognized FormData part.", - null, + reactApplicationContext, + requestId, + devToolsRequestId, + "Unrecognized FormData part.", + null, ) } } @@ -1053,7 +1050,7 @@ public class NetworkingModule( @JvmStatic public fun setCustomClientBuilder( - ccb: com.facebook.react.modules.network.CustomClientBuilder? + ccb: com.facebook.react.modules.network.CustomClientBuilder? ) { customClientBuilder = ccb } diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt index 2780c93ef7e3ec..4c6e6a318affb0 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt @@ -244,17 +244,18 @@ public class SampleLegacyModule(private val context: ReactApplicationContext) : } } - override fun getConstants(): Map { - val result: MutableMap = mutableMapOf() - val activity = context.currentActivity - if (activity != null) { - result["const2"] = 390 + override val constants: Map + get() { + val result: MutableMap = mutableMapOf() + val activity = context.currentActivity + if (activity != null) { + result["const2"] = 390 + } + result["const1"] = true + result["const3"] = "something" + log("constantsToExport", "", result) + return result } - result["const1"] = true - result["const3"] = "something" - log("constantsToExport", "", result) - return result - } private fun log(method: String, input: Any?, output: Any?) { toast?.cancel() From aa8f8e4b6462f9f8d33b87bd84f40861df5b3da6 Mon Sep 17 00:00:00 2001 From: HyunWoo Lee Date: Wed, 12 Nov 2025 22:30:56 +0900 Subject: [PATCH 2/4] fix: code review applied --- .../react/animated/NativeAnimatedModule.kt | 12 +++++------ .../facebook/react/bridge/BaseJavaModule.kt | 3 +-- .../react/bridge/JavaModuleWrapper.kt | 2 +- .../react/modules/debug/DevSettingsModule.kt | 2 +- .../platform/android/SampleLegacyModule.kt | 21 +++++++++---------- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt index 6c5a505c9218e2..2644a7936891db 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.kt @@ -231,7 +231,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) : // emit the event to JS to resync the trees val onAnimationEndedData = buildReadableMap { putArray("tags") { tags.forEach { add(it) } } } - val reactApplicationContext = reactApplicationContextIfActiveOrWarn + val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() reactApplicationContext?.emitDeviceEvent("onUserDrivenAnimationEnded", onAnimationEndedData) } @@ -352,7 +352,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) : */ get() { if (nodesManagerRef.get() == null) { - val reactApplicationContext = reactApplicationContextIfActiveOrWarn + val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() if (reactApplicationContext != null) { nodesManagerRef.compareAndSet(null, NativeAnimatedNodesManager(reactApplicationContext)) @@ -435,7 +435,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) : return } - val reactApplicationContext = reactApplicationContextIfActiveOrWarn + val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() if (reactApplicationContext != null) { val uiManager = UIManagerHelper.getUIManager(reactApplicationContext, uiManagerType) if (uiManager != null) { @@ -543,7 +543,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) : put("offset", offset) } - val reactApplicationContext = reactApplicationContextIfActiveOrWarn + val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() reactApplicationContext?.emitDeviceEvent("onAnimatedValueUpdate", onAnimatedValueData) } @@ -981,7 +981,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) : addUnbatchedOperation( object : UIThreadOperation() { override fun execute(animatedNodesManager: NativeAnimatedNodesManager) { - val reactApplicationContext = reactApplicationContextIfActiveOrWarn + val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() var viewTag = -1 var i = 0 @@ -1013,7 +1013,7 @@ public class NativeAnimatedModule(reactContext: ReactApplicationContext) : put("offset", offset) } - val reactApplicationContext = reactApplicationContextIfActiveOrWarn + val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() reactApplicationContext?.emitDeviceEvent( "onAnimatedValueUpdate", onAnimatedValueData, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt index 35bacf7434ae2f..12fddaeabb5c64 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt @@ -62,8 +62,7 @@ public abstract class BaseJavaModule : NativeModule { /** * @return a map of constants this module exports to JS. Supports JSON types. */ - public open val constants: Map? - get() = null + public open fun getConstants(): Map? = null public override fun initialize() { // do nothing diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt index d24e6e9a4efd1d..3c4e98cd351506 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JavaModuleWrapper.kt @@ -110,7 +110,7 @@ internal class JavaModuleWrapper( val baseJavaModule = module Systrace.beginSection(TRACE_TAG_REACT, "module.getConstants") - val map = baseJavaModule.constants + val map = baseJavaModule.getConstants() Systrace.endSection(TRACE_TAG_REACT) Systrace.beginSection(TRACE_TAG_REACT, "create WritableNativeMap") diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt index 9c04b07d08277a..9c8049428d7e98 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.kt @@ -51,7 +51,7 @@ public class DevSettingsModule( override fun addMenuItem(title: String) { devSupportManager.addCustomDevOption(title) { val data = buildReadableMap { put("title", title) } - val reactApplicationContext = reactApplicationContextIfActiveOrWarn + val reactApplicationContext = getReactApplicationContextIfActiveOrWarn() reactApplicationContext?.emitDeviceEvent("didPressMenuItem", data) } } diff --git a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt index 4c6e6a318affb0..2780c93ef7e3ec 100644 --- a/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt +++ b/packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.kt @@ -244,18 +244,17 @@ public class SampleLegacyModule(private val context: ReactApplicationContext) : } } - override val constants: Map - get() { - val result: MutableMap = mutableMapOf() - val activity = context.currentActivity - if (activity != null) { - result["const2"] = 390 - } - result["const1"] = true - result["const3"] = "something" - log("constantsToExport", "", result) - return result + override fun getConstants(): Map { + val result: MutableMap = mutableMapOf() + val activity = context.currentActivity + if (activity != null) { + result["const2"] = 390 } + result["const1"] = true + result["const3"] = "something" + log("constantsToExport", "", result) + return result + } private fun log(method: String, input: Any?, output: Any?) { toast?.cancel() From f6755ea2fe2077abb09346b22303cb2461b88c87 Mon Sep 17 00:00:00 2001 From: HyunWoo Lee Date: Wed, 12 Nov 2025 22:55:36 +0900 Subject: [PATCH 3/4] fix: remove warning --- .../src/main/java/com/facebook/react/bridge/BaseJavaModule.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt index 12fddaeabb5c64..a4932753d9adbe 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.kt @@ -68,6 +68,7 @@ public abstract class BaseJavaModule : NativeModule { // do nothing } + @Deprecated("canOverrideExistingModule is not used in the New Architecture") public override fun canOverrideExistingModule(): Boolean { return false } From e44b55f983624773b4fe584cfead850c5e13f54a Mon Sep 17 00:00:00 2001 From: HyunWoo Lee Date: Wed, 12 Nov 2025 23:45:56 +0900 Subject: [PATCH 4/4] fix: remove warnings --- .../animated/NativeAnimatedNodeTraversalTest.kt | 6 +++--- .../react/uimanager/ReactPropConstantsTest.kt | 2 +- .../react/uimanager/UIManagerModuleConstantsTest.kt | 12 ++++++------ 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt index dfb2afefec9af8..d3579bdef8b0ea 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/animated/NativeAnimatedNodeTraversalTest.kt @@ -79,13 +79,13 @@ class NativeAnimatedNodeTraversalTest { uiManagerMock = mock() eventDispatcherMock = mock() whenever(uiManagerMock.eventDispatcher).thenAnswer { eventDispatcherMock } - whenever(uiManagerMock.constants).thenAnswer { + whenever(uiManagerMock.getConstants()).thenAnswer { mapOf("customDirectEventTypes" to emptyMap()) } whenever(uiManagerMock.directEventNamesResolver).thenAnswer { object : UIManagerModule.CustomEventNamesResolver { override fun resolveCustomEventName(eventName: String): String { - val constants: Map = uiManagerMock.constants ?: emptyMap() + val constants: Map = uiManagerMock.getConstants() ?: emptyMap() val directEventTypes: Any? = constants["customDirectEventTypes"] if (directEventTypes != null && directEventTypes is Map<*, *>) { val customEventType = directEventTypes[eventName] @@ -1044,7 +1044,7 @@ class NativeAnimatedNodeTraversalTest { fun testNativeAnimatedEventCustomMapping() { val viewTag: Int = 1000 - whenever(uiManagerMock.constants).thenAnswer { + whenever(uiManagerMock.getConstants()).thenAnswer { mapOf( "customDirectEventTypes" to mapOf("onScroll" to mapOf("registrationName" to "onScroll")) ) diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt index bee7ae8b9adf22..23300b963969a2 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/ReactPropConstantsTest.kt @@ -100,7 +100,7 @@ class ReactPropConstantsTest { val reactContext = BridgeReactContext(RuntimeEnvironment.getApplication()) val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) val constants: Map<*, *> = - valueAtPath(uiManagerModule.constants as Map<*, *>, "SomeView", "NativeProps") + valueAtPath(uiManagerModule.getConstants() as Map<*, *>, "SomeView", "NativeProps") Assertions.assertThat(constants) .isEqualTo( diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt index f7b02fd746c869..92d47cd0bd3b05 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/uimanager/UIManagerModuleConstantsTest.kt @@ -72,7 +72,7 @@ class UIManagerModuleConstantsTest { val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) val viewManagerConstants = - uiManagerModule.constants?.get(VIEW_MANAGER_NAME) as Map? + uiManagerModule.getConstants()?.get(VIEW_MANAGER_NAME) as Map? Assertions.assertThat(viewManagerConstants) .containsKey(BUBBLING_EVENTS_TYPES_KEY) @@ -88,7 +88,7 @@ class UIManagerModuleConstantsTest { val viewManagers = listOf(manager) val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) - val viewManagerConstants = uiManagerModule.constants?.get(VIEW_MANAGER_NAME) as Map + val viewManagerConstants = uiManagerModule.getConstants()?.get(VIEW_MANAGER_NAME) as Map Assertions.assertThat(viewManagerConstants).containsKey(BUBBLING_EVENTS_TYPES_KEY) val bubblingEventTypes = viewManagerConstants[BUBBLING_EVENTS_TYPES_KEY] as Map @@ -104,7 +104,7 @@ class UIManagerModuleConstantsTest { val viewManagers = listOf(manager) val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) - val viewManagerConstants = uiManagerModule.constants?.get(VIEW_MANAGER_NAME) as Map + val viewManagerConstants = uiManagerModule.getConstants()?.get(VIEW_MANAGER_NAME) as Map Assertions.assertThat(viewManagerConstants).containsKey(DIRECT_EVENTS_TYPES_KEY) val directEventTypes = viewManagerConstants[DIRECT_EVENTS_TYPES_KEY] as Map @@ -120,7 +120,7 @@ class UIManagerModuleConstantsTest { val viewManagers = listOf(manager) val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) - val constants = uiManagerModule.constants + val constants = uiManagerModule.getConstants() Assertions.assertThat(constants).containsKey(VIEW_MANAGER_NAME) Assertions.assertThat(constants!![VIEW_MANAGER_NAME] as Map) @@ -138,7 +138,7 @@ class UIManagerModuleConstantsTest { val viewManagers = listOf(manager) val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) - val constants = uiManagerModule.constants.orEmpty() + val constants = uiManagerModule.getConstants().orEmpty() Assertions.assertThat( valueAtPath(constants, VIEW_MANAGER_NAME, "NativeProps", "fooProp") as String? ) @@ -180,7 +180,7 @@ class UIManagerModuleConstantsTest { val viewManagers = listOf(managerX, managerY) val uiManagerModule = UIManagerModule(reactContext, viewManagers, 0) - val constants = uiManagerModule.constants + val constants = uiManagerModule.getConstants() val viewManagerConstants = constants!!["ManagerX"] as Map Assertions.assertThat(viewManagerConstants[DIRECT_EVENTS_TYPES_KEY] as Map) .containsKey("onTwirl")