From 25abb0acdfb781e5e6e08d7ceb5bb1eab4aa7dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateo=20Guzm=C3=A1n?= Date: Mon, 12 May 2025 04:48:28 -0700 Subject: [PATCH] Migrate `PromiseImpl` to Kotlin (#51241) Summary: Migrate com.facebook.react.bridge.PromiseImpl to Kotlin. ## Changelog: [INTERNAL] - Migrate com.facebook.react.bridge.PromiseImpl to Kotlin Pull Request resolved: https://github.com/facebook/react-native/pull/51241 Test Plan: ```bash yarn test-android yarn android ``` Reviewed By: rshest Differential Revision: D74567102 Pulled By: cortinico fbshipit-source-id: 1a7a36a88c0499e5bc35dc37d276219aaf823c90 --- .../ReactAndroid/api/ReactAndroid.api | 6 +- .../facebook/react/bridge/PromiseImpl.java | 257 ------------------ .../com/facebook/react/bridge/PromiseImpl.kt | 238 ++++++++++++++++ 3 files changed, 243 insertions(+), 258 deletions(-) delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index fd58a386af7..bbfe2ccf38f 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1032,7 +1032,8 @@ public abstract interface class com/facebook/react/bridge/Promise { public abstract fun resolve (Ljava/lang/Object;)V } -public class com/facebook/react/bridge/PromiseImpl : com/facebook/react/bridge/Promise { +public final class com/facebook/react/bridge/PromiseImpl : com/facebook/react/bridge/Promise { + public static final field Companion Lcom/facebook/react/bridge/PromiseImpl$Companion; public fun (Lcom/facebook/react/bridge/Callback;Lcom/facebook/react/bridge/Callback;)V public fun reject (Ljava/lang/String;)V public fun reject (Ljava/lang/String;Lcom/facebook/react/bridge/WritableMap;)V @@ -1047,6 +1048,9 @@ public class com/facebook/react/bridge/PromiseImpl : com/facebook/react/bridge/P public fun resolve (Ljava/lang/Object;)V } +public final class com/facebook/react/bridge/PromiseImpl$Companion { +} + public abstract class com/facebook/react/bridge/ReactApplicationContext : com/facebook/react/bridge/ReactContext { public fun (Landroid/content/Context;)V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java deleted file mode 100644 index 97340676fd2..00000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.java +++ /dev/null @@ -1,257 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.bridge; - -import androidx.annotation.Nullable; -import com.facebook.infer.annotation.Nullsafe; -import com.facebook.proguard.annotations.DoNotStrip; - -/* - * Implementation of {@link Promise} that represents a JavaScript Promise which can be passed to the - * native module as a method parameter. - * - * Methods annotated with {@link ReactMethod} that use a {@link Promise} as the last parameter - * will be marked as "promise" and will return a promise when invoked from JavaScript. - */ -@Nullsafe(Nullsafe.Mode.LOCAL) -@DoNotStrip -public class PromiseImpl implements Promise { - // Number of stack frames to parse and return to mReject.invoke - // for ERROR_MAP_KEY_NATIVE_STACK - private static final int ERROR_STACK_FRAME_LIMIT = 50; - - private static final String ERROR_DEFAULT_CODE = "EUNSPECIFIED"; - private static final String ERROR_DEFAULT_MESSAGE = "Error not specified."; - - // Keys for mReject's WritableMap - private static final String ERROR_MAP_KEY_CODE = "code"; - private static final String ERROR_MAP_KEY_MESSAGE = "message"; - private static final String ERROR_MAP_KEY_NAME = "name"; - private static final String ERROR_MAP_KEY_USER_INFO = "userInfo"; - private static final String ERROR_MAP_KEY_NATIVE_STACK = "nativeStackAndroid"; - - // Keys for ERROR_MAP_KEY_NATIVE_STACK's StackFrame maps - private static final String STACK_FRAME_KEY_CLASS = "class"; - private static final String STACK_FRAME_KEY_FILE = "file"; - private static final String STACK_FRAME_KEY_LINE_NUMBER = "lineNumber"; - private static final String STACK_FRAME_KEY_METHOD_NAME = "methodName"; - - private @Nullable Callback mResolve; - private @Nullable Callback mReject; - - @DoNotStrip - public PromiseImpl(@Nullable Callback resolve, @Nullable Callback reject) { - mResolve = resolve; - mReject = reject; - } - - /** - * Successfully resolve the Promise with an optional value. - * - * @param value Object - */ - @Override - public void resolve(@Nullable Object value) { - if (mResolve != null) { - mResolve.invoke(value); - mResolve = null; - mReject = null; - } - } - - /** - * Report an error without an exception using a custom code and error message. - * - * @param code String - * @param message String - */ - @Override - public void reject(String code, @Nullable String message) { - reject(code, message, /*Throwable*/ null, /*WritableMap*/ null); - } - - /** - * Report an exception with a custom code. - * - * @param code String - * @param throwable Throwable - */ - @Override - public void reject(String code, @Nullable Throwable throwable) { - reject(code, /*Message*/ null, throwable, /*WritableMap*/ null); - } - - /** - * Report an exception with a custom code and error message. - * - * @param code String - * @param message String - * @param throwable Throwable - */ - @Override - public void reject(String code, @Nullable String message, @Nullable Throwable throwable) { - reject(code, message, throwable, /*WritableMap*/ null); - } - - /** - * Report an exception, with default error code. Useful in catch-all scenarios where it's unclear - * why the error occurred. - * - * @param throwable Throwable - */ - @Override - public void reject(Throwable throwable) { - reject(/*Code*/ null, /*Message*/ null, throwable, /*WritableMap*/ null); - } - - /* --------------------------- - * With userInfo WritableMap - * --------------------------- */ - - /** - * Report an exception, with default error code, with userInfo. Useful in catch-all scenarios - * where it's unclear why the error occurred. - * - * @param throwable Throwable - * @param userInfo WritableMap - */ - @Override - public void reject(Throwable throwable, WritableMap userInfo) { - reject(/*Code*/ null, /*Message*/ null, throwable, userInfo); - } - - /** - * Reject with a code and userInfo WritableMap. - * - * @param code String - * @param userInfo WritableMap - */ - @Override - public void reject(String code, WritableMap userInfo) { - reject(code, /*Message*/ null, /*Throwable*/ null, userInfo); - } - - /** - * Report an exception with a custom code and userInfo. - * - * @param code String - * @param throwable Throwable - * @param userInfo WritableMap - */ - @Override - public void reject(String code, @Nullable Throwable throwable, WritableMap userInfo) { - reject(code, /*Message*/ null, throwable, userInfo); - } - - /** - * Report an error with a custom code, error message and userInfo, an error not caused by an - * exception. - * - * @param code String - * @param message String - * @param userInfo WritableMap - */ - @Override - public void reject(String code, @Nullable String message, WritableMap userInfo) { - reject(code, message, /*Throwable*/ null, userInfo); - } - - /** - * Report an exception with a custom code, error message and userInfo. - * - * @param code String - * @param message String - * @param throwable Throwable - * @param userInfo WritableMap - */ - @Override - public void reject( - @Nullable String code, - @Nullable String message, - @Nullable Throwable throwable, - @Nullable WritableMap userInfo) { - if (mReject == null) { - mResolve = null; - return; - } - - WritableNativeMap errorInfo = new WritableNativeMap(); - - if (code == null) { - errorInfo.putString(ERROR_MAP_KEY_CODE, ERROR_DEFAULT_CODE); - } else { - errorInfo.putString(ERROR_MAP_KEY_CODE, code); - } - - // Use the custom message if provided otherwise use the throwable message. - if (message != null) { - errorInfo.putString(ERROR_MAP_KEY_MESSAGE, message); - } else if (throwable != null) { - String throwableMessage = throwable.getMessage(); - // Fallback to the trowable name, so we record some useful information - if (throwableMessage == null || throwableMessage.isEmpty()) { - throwableMessage = throwable.getClass().getCanonicalName(); - } - errorInfo.putString(ERROR_MAP_KEY_MESSAGE, throwableMessage); - } else { - // The JavaScript side expects a map with at least an error message. - // /Libraries/BatchedBridge/NativeModules.js -> createErrorFromErrorData - // TYPE: (errorData: { message: string }) - errorInfo.putString(ERROR_MAP_KEY_MESSAGE, ERROR_DEFAULT_MESSAGE); - } - - // For consistency with iOS ensure userInfo key exists, even if we null it. - // iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError - if (userInfo != null) { - errorInfo.putMap(ERROR_MAP_KEY_USER_INFO, userInfo); - } else { - errorInfo.putNull(ERROR_MAP_KEY_USER_INFO); - } - - // Attach a nativeStackAndroid array if a throwable was passed - // this matches iOS behavior - iOS adds a `nativeStackIOS` property - // iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError - if (throwable != null) { - errorInfo.putString(ERROR_MAP_KEY_NAME, throwable.getClass().getCanonicalName()); - - StackTraceElement[] stackTrace = throwable.getStackTrace(); - WritableNativeArray nativeStackAndroid = new WritableNativeArray(); - - // Build an an Array of StackFrames to match JavaScript: - // iOS: /Libraries/Core/Devtools/parseErrorStack.js -> StackFrame - for (int i = 0; i < stackTrace.length && i < ERROR_STACK_FRAME_LIMIT; i++) { - StackTraceElement frame = stackTrace[i]; - WritableMap frameMap = new WritableNativeMap(); - // NOTE: no column number exists StackTraceElement - frameMap.putString(STACK_FRAME_KEY_CLASS, frame.getClassName()); - frameMap.putString(STACK_FRAME_KEY_FILE, frame.getFileName()); - frameMap.putInt(STACK_FRAME_KEY_LINE_NUMBER, frame.getLineNumber()); - frameMap.putString(STACK_FRAME_KEY_METHOD_NAME, frame.getMethodName()); - nativeStackAndroid.pushMap(frameMap); - } - - errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, nativeStackAndroid); - } else { - errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, new WritableNativeArray()); - } - - mReject.invoke(errorInfo); - mResolve = null; - mReject = null; - } - - /* ------------ - * Deprecated - * ------------ */ - - @Override - @Deprecated - public void reject(String message) { - reject(/*Code*/ null, message, /*Throwable*/ null, /*WritableMap*/ null); - } -} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt new file mode 100644 index 00000000000..f0070dd70c7 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/PromiseImpl.kt @@ -0,0 +1,238 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge + +import com.facebook.proguard.annotations.DoNotStrip + +/** + * Implementation of [Promise] that represents a JavaScript Promise which can be passed to the + * native module as a method parameter. Methods annotated with [ReactMethod] that use a [Promise] as + * the last parameter will be marked as "promise" and will return a promise when invoked from + * JavaScript. + */ +@DoNotStrip +public class PromiseImpl(private var resolve: Callback?, private var reject: Callback?) : Promise { + /** + * Successfully resolve the [Promise] with an optional value. + * + * @param value Object + */ + override fun resolve(value: Any?) { + resolve?.let { res -> + res.invoke(value) + resolve = null + reject = null + } + } + + /** + * Report an error without an exception using a custom code and error message. + * + * @param code String + * @param message String + */ + override fun reject(code: String, message: String?) { + reject(code, message, null, null) + } + + /** + * Report an exception with a custom code. + * + * @param code String + * @param throwable Throwable + */ + override fun reject(code: String, throwable: Throwable?) { + reject(code, null, throwable, null) + } + + /** + * Report an exception with a custom code and error message. + * + * @param code String + * @param message String + * @param throwable Throwable + */ + override fun reject(code: String, message: String?, throwable: Throwable?) { + reject(code, message, throwable, null) + } + + /** + * Report an exception, with default error code. Useful in catch-all scenarios where it's unclear + * why the error occurred. + * + * @param throwable Throwable + */ + override fun reject(throwable: Throwable) { + reject(null, null, throwable, null) + } + + /* --------------------------- + * With userInfo WritableMap + * --------------------------- */ + + /** + * Report an exception, with default error code, with userInfo. Useful in catch-all scenarios + * where it's unclear why the error occurred. + * + * @param throwable Throwable + * @param userInfo WritableMap + */ + override fun reject(throwable: Throwable, userInfo: WritableMap) { + reject(null, null, throwable, userInfo) + } + + /** + * Reject with a code and userInfo WritableMap. + * + * @param code String + * @param userInfo WritableMap + */ + override fun reject(code: String, userInfo: WritableMap) { + reject(code, null, null, userInfo) + } + + /** + * Report an exception with a custom code and userInfo. + * + * @param code String + * @param throwable Throwable + * @param userInfo WritableMap + */ + override fun reject(code: String, throwable: Throwable?, userInfo: WritableMap) { + reject(code, null, throwable, userInfo) + } + + /** + * Report an error with a custom code, error message and userInfo, an error not caused by an + * exception. + * + * @param code String + * @param message String + * @param userInfo WritableMap + */ + override fun reject(code: String, message: String?, userInfo: WritableMap) { + reject(code, message, null, userInfo) + } + + /** + * Report an exception with a custom code, error message and userInfo. + * + * @param code String + * @param message String + * @param throwable Throwable + * @param userInfo WritableMap + */ + override fun reject( + code: String?, + message: String?, + throwable: Throwable?, + userInfo: WritableMap? + ) { + if (reject == null) { + resolve = null + return + } + + val errorInfo = WritableNativeMap() + + if (code == null) { + errorInfo.putString(ERROR_MAP_KEY_CODE, ERROR_DEFAULT_CODE) + } else { + errorInfo.putString(ERROR_MAP_KEY_CODE, code) + } + + // Use the custom message if provided otherwise use the throwable message. + if (message != null) { + errorInfo.putString(ERROR_MAP_KEY_MESSAGE, message) + } else if (throwable != null) { + var throwableMessage = throwable.message + // Fallback to the throwable name, so we record some useful information + if (throwableMessage.isNullOrEmpty()) { + throwableMessage = throwable.javaClass.canonicalName + } + errorInfo.putString(ERROR_MAP_KEY_MESSAGE, throwableMessage) + } else { + // The JavaScript side expects a map with at least an error message. + // /Libraries/BatchedBridge/NativeModules.js -> createErrorFromErrorData + // TYPE: (errorData: { message: string }) + errorInfo.putString(ERROR_MAP_KEY_MESSAGE, ERROR_DEFAULT_MESSAGE) + } + + // For consistency with iOS ensure userInfo key exists, even if we null it. + // iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError + if (userInfo != null) { + errorInfo.putMap(ERROR_MAP_KEY_USER_INFO, userInfo) + } else { + errorInfo.putNull(ERROR_MAP_KEY_USER_INFO) + } + + // Attach a nativeStackAndroid array if a throwable was passed + // this matches iOS behavior - iOS adds a `nativeStackIOS` property + // iOS: /React/Base/RCTUtils.m -> RCTJSErrorFromCodeMessageAndNSError + if (throwable != null) { + errorInfo.putString(ERROR_MAP_KEY_NAME, throwable.javaClass.canonicalName) + + val stackTrace = throwable.stackTrace + val nativeStackAndroid = WritableNativeArray() + + // Build an an Array of StackFrames to match JavaScript: + // iOS: /Libraries/Core/Devtools/parseErrorStack.js -> StackFrame + var i = 0 + while (i < stackTrace.size && i < ERROR_STACK_FRAME_LIMIT) { + val frame = stackTrace[i] + val frameMap: WritableMap = WritableNativeMap() + // NOTE: no column number exists StackTraceElement + frameMap.putString(STACK_FRAME_KEY_CLASS, frame.className) + frameMap.putString(STACK_FRAME_KEY_FILE, frame.fileName) + frameMap.putInt(STACK_FRAME_KEY_LINE_NUMBER, frame.lineNumber) + frameMap.putString(STACK_FRAME_KEY_METHOD_NAME, frame.methodName) + nativeStackAndroid.pushMap(frameMap) + i++ + } + + errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, nativeStackAndroid) + } else { + errorInfo.putArray(ERROR_MAP_KEY_NATIVE_STACK, WritableNativeArray()) + } + + reject?.invoke(errorInfo) + resolve = null + reject = null + } + + /* ------------ + * Deprecated + * ------------ */ + + @Deprecated("Use reject(code, message) instead.", ReplaceWith("reject(code, message)")) + override fun reject(message: String) { + reject(null, message, null, null) + } + + public companion object { + // Number of stack frames to parse and return to reject.invoke + // for ERROR_MAP_KEY_NATIVE_STACK + private const val ERROR_STACK_FRAME_LIMIT = 50 + + private const val ERROR_DEFAULT_CODE = "EUNSPECIFIED" + private const val ERROR_DEFAULT_MESSAGE = "Error not specified." + + // Keys for reject's WritableMap + private const val ERROR_MAP_KEY_CODE = "code" + private const val ERROR_MAP_KEY_MESSAGE = "message" + private const val ERROR_MAP_KEY_NAME = "name" + private const val ERROR_MAP_KEY_USER_INFO = "userInfo" + private const val ERROR_MAP_KEY_NATIVE_STACK = "nativeStackAndroid" + + // Keys for ERROR_MAP_KEY_NATIVE_STACK's StackFrame maps + private const val STACK_FRAME_KEY_CLASS = "class" + private const val STACK_FRAME_KEY_FILE = "file" + private const val STACK_FRAME_KEY_LINE_NUMBER = "lineNumber" + private const val STACK_FRAME_KEY_METHOD_NAME = "methodName" + } +}