[0.75] Add support for handling com.facebook.react.bridge.Dynamic as parameter for TurboModules (#46066)

This commit is contained in:
Nicola Corti
2024-08-19 14:18:40 +01:00
committed by GitHub
parent b5b57731d3
commit 45cd81706d
8 changed files with 216 additions and 6 deletions
@@ -0,0 +1,51 @@
/*
* 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.jni.HybridData
import com.facebook.proguard.annotations.DoNotStrip
import com.facebook.proguard.annotations.DoNotStripAny
/**
* An implementation of [Dynamic] that has a C++ implementation.
*
* This is used to support Legacy Native Modules that have not been migrated to the new architecture
* and are using [Dynamic] as a parameter type.
*/
@DoNotStripAny
private class DynamicNative(
@Suppress("NoHungarianNotation") @field:DoNotStrip private val mHybridData: HybridData?
) : Dynamic {
override val type: ReadableType
get() = getTypeNative()
override val isNull: Boolean
get() = isNullNative()
private external fun getTypeNative(): ReadableType
private external fun isNullNative(): Boolean
external override fun asBoolean(): Boolean
// The native representation is holding the value as Double. We do the Int conversion here.
override fun asInt(): Int = asDouble().toInt()
external override fun asDouble(): Double
external override fun asString(): String
external override fun asArray(): ReadableArray
external override fun asMap(): ReadableMap
override fun recycle() {
// Noop - nothing to recycle since there is no pooling
}
}
@@ -161,14 +161,11 @@ class TurboModuleInteropUtils {
|| paramClass == Callback.class
|| paramClass == Promise.class
|| paramClass == ReadableMap.class
|| paramClass == ReadableArray.class) {
|| paramClass == ReadableArray.class
|| paramClass == Dynamic.class) {
return convertClassToJniType(paramClass);
}
if (paramClass == Dynamic.class) {
// TODO(T145105887): Output warnings that TurboModules doesn't yet support Dynamic arguments
}
throw new ParsingException(
moduleName,
methodName,
@@ -0,0 +1,46 @@
/*
* 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.
*/
#include "JDynamicNative.h"
#include "ReadableNativeArray.h"
#include "ReadableNativeMap.h"
using namespace facebook::jni;
namespace facebook::react {
jboolean JDynamicNative::isNullNative() {
return payload_.isNull();
}
jni::local_ref<ReadableType> JDynamicNative::getTypeNative() {
return ReadableType::getType(payload_.type());
}
jni::local_ref<jstring> JDynamicNative::asString() {
return jni::make_jstring(payload_.asString());
}
jboolean JDynamicNative::asBoolean() {
return payload_.asBool();
}
jdouble JDynamicNative::asDouble() {
return payload_.asDouble();
}
jni::local_ref<ReadableArray> JDynamicNative::asArray() {
return jni::adopt_local(reinterpret_cast<ReadableArray::javaobject>(
ReadableNativeArray::newObjectCxxArgs(payload_).release()));
}
jni::local_ref<ReadableMap> JDynamicNative::asMap() {
return jni::adopt_local(reinterpret_cast<ReadableMap::javaobject>(
ReadableNativeMap::createWithContents(std::move(payload_)).release()));
}
} // namespace facebook::react
@@ -0,0 +1,56 @@
/*
* 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.
*/
#pragma once
#include "NativeCommon.h"
#include "ReadableNativeArray.h"
#include "ReadableNativeMap.h"
#include <fbjni/fbjni.h>
#include <folly/dynamic.h>
#include <folly/json.h>
namespace facebook::react {
struct JDynamic : public jni::JavaClass<JDynamic> {
constexpr static auto kJavaDescriptor = "Lcom/facebook/react/bridge/Dynamic;";
};
class JDynamicNative : public jni::HybridClass<JDynamicNative, JDynamic> {
public:
constexpr static auto kJavaDescriptor =
"Lcom/facebook/react/bridge/DynamicNative;";
JDynamicNative(folly::dynamic payload) : payload_(std::move(payload)) {}
static void registerNatives() {
javaClassStatic()->registerNatives(
{makeNativeMethod("isNullNative", JDynamicNative::isNullNative),
makeNativeMethod("getTypeNative", JDynamicNative::getTypeNative),
makeNativeMethod("asDouble", JDynamicNative::asDouble),
makeNativeMethod("asBoolean", JDynamicNative::asBoolean),
makeNativeMethod("asString", JDynamicNative::asString),
makeNativeMethod("asArray", JDynamicNative::asArray),
makeNativeMethod("asMap", JDynamicNative::asMap)});
}
private:
friend HybridBase;
jni::local_ref<ReadableType> getTypeNative();
jni::local_ref<jstring> asString();
jboolean asBoolean();
jdouble asDouble();
jboolean isNullNative();
jni::local_ref<ReadableArray> asArray();
jni::local_ref<ReadableMap> asMap();
folly::dynamic payload_;
};
} // namespace facebook::react
@@ -15,6 +15,7 @@
#include "CatalystInstanceImpl.h"
#include "CxxModuleWrapperBase.h"
#include "JCallback.h"
#include "JDynamicNative.h"
#include "JInspector.h"
#include "JReactMarker.h"
#include "JavaScriptExecutorHolder.h"
@@ -89,6 +90,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
NativeMap::registerNatives();
ReadableNativeMap::registerNatives();
WritableNativeMap::registerNatives();
JDynamicNative::registerNatives();
JReactMarker::registerNatives();
JInspector::registerNatives();
ReactInstanceManagerInspectorTarget::registerNatives();
@@ -19,6 +19,7 @@
#include <jsi/JSIDynamic.h>
#include <react/bridging/Bridging.h>
#include <react/debug/react_native_assert.h>
#include <react/jni/JDynamicNative.h>
#include <react/jni/NativeMap.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeMap.h>
@@ -373,7 +374,9 @@ JNIArgs convertJSIArgsToJNIArgs(
continue;
}
if (arg->isNull() || arg->isUndefined()) {
// Dynamic encapsulates the Null type so we don't want to return null here.
if ((arg->isNull() && type != "Lcom/facebook/react/bridge/Dynamic;") ||
arg->isUndefined()) {
jarg->l = nullptr;
} else if (type == "Ljava/lang/Double;") {
if (!arg->isNumber()) {
@@ -436,6 +439,10 @@ JNIArgs convertJSIArgsToJNIArgs(
auto jParams =
ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
jarg->l = makeGlobalIfNecessary(jParams.release());
} else if (type == "Lcom/facebook/react/bridge/Dynamic;") {
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue);
jarg->l = makeGlobalIfNecessary(jParams.release());
} else {
throw JavaTurboModuleInvalidArgumentTypeException(
type, argIndex, methodName);
@@ -13,12 +13,14 @@ import android.widget.Toast;
import com.facebook.proguard.annotations.DoNotStrip;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.Dynamic;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableType;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeArray;
@@ -155,6 +157,44 @@ public class SampleLegacyModule extends ReactContextBaseJavaModule {
return map;
}
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
public WritableMap getDynamic(Dynamic dynamic) {
WritableNativeMap resultMap = new WritableNativeMap();
ReadableType type = dynamic.getType();
if (type == ReadableType.Null) {
log("getDynamic as Null", dynamic, dynamic);
resultMap.putString("type", "Null");
resultMap.putNull("value");
} else if (type == ReadableType.Boolean) {
boolean result = dynamic.asBoolean();
log("getDynamic as Boolean", dynamic, result);
resultMap.putString("type", "Boolean");
resultMap.putBoolean("value", result);
} else if (type == ReadableType.Number) {
int result = dynamic.asInt();
log("getDynamic as Number", dynamic, result);
resultMap.putString("type", "Number");
resultMap.putInt("value", result);
} else if (type == ReadableType.String) {
String result = dynamic.asString();
log("getDynamic as String", dynamic, result);
resultMap.putString("type", "String");
resultMap.putString("value", result);
} else if (type == ReadableType.Array) {
ReadableArray result = dynamic.asArray();
log("getDynamic as Array", dynamic, result);
resultMap.putString("type", "Array");
resultMap.putArray("value", result);
} else if (type == ReadableType.Map) {
ReadableMap result = dynamic.asMap();
log("getDynamic as Map", dynamic, result);
resultMap.putString("type", "Map");
resultMap.putMap("value", result);
}
return resultMap;
}
@DoNotStrip
@SuppressWarnings("unused")
@ReactMethod(isBlockingSynchronousMethod = true)
@@ -142,6 +142,17 @@ class SampleLegacyModuleExample extends React.Component<{||}, State> {
getSampleLegacyModule()?.getObject({a: 1, b: 'foo', c: null}),
getValue: () =>
getSampleLegacyModule()?.getValue(5, 'test', {a: 1, b: 'foo'}),
getDynamicWithNull: () => getSampleLegacyModule()?.getDynamic(null),
getDynamicWithBoolean: () =>
getSampleLegacyModule()?.getDynamic(true),
getDynamicWithNumber: () =>
getSampleLegacyModule()?.getDynamic(42.24),
getDynamicWithString: () =>
getSampleLegacyModule()?.getDynamic('The answer is 42'),
getDynamicWithArray: () =>
getSampleLegacyModule()?.getDynamic(['the', 'answer', 'is', '42']),
getDynamicWithMap: () =>
getSampleLegacyModule()?.getDynamic({answer: '42'}),
callback: () =>
getSampleLegacyModule()?.getValueWithCallback(callbackValue =>
this._setResult('callback', callbackValue),