Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for handling com.facebook.react.bridge.Dynamic as parameter type in TurboModules #45944

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -161,14 +161,11 @@ private static String convertParamClassToJniType(
|| 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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "CxxModuleWrapperBase.h"
#include "InspectorNetworkRequestListener.h"
#include "JCallback.h"
#include "JDynamicNative.h"
#include "JInspector.h"
#include "JReactMarker.h"
#include "JavaScriptExecutorHolder.h"
Expand Down Expand Up @@ -90,6 +91,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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <react/bridging/Bridging.h>
#include <react/debug/react_native_assert.h>
#include <react/featureflags/ReactNativeFeatureFlags.h>
#include <react/jni/JDynamicNative.h>
#include <react/jni/NativeMap.h>
#include <react/jni/ReadableNativeMap.h>
#include <react/jni/WritableNativeMap.h>
Expand Down Expand Up @@ -364,7 +365,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()) {
Expand Down Expand Up @@ -427,6 +430,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@
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;
Expand Down Expand Up @@ -155,6 +157,44 @@ public WritableMap getUnsafeObject(ReadableMap arg) {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
Loading