Skip to content

Commit 6230866

Browse files
cortinicofacebook-github-bot
authored andcommitted
Add support for handling com.facebook.react.bridge.Dynamic as parameter type in TurboModules (#45944)
Summary: Pull Request resolved: #45944 This diff adds support having (Legacy) Native Modules with functions with parameters of type `Dynamic`. This is currently blocking some libraries making it harder for them to migrate to New Architecture. I've implemented it by adding a `DynamicNative` implementation of `Dynamic` which holds a reference of the payload as a `folly::dynamic`. Changelog: [Android] [Added] - Add support for handling `com.facebook.react.bridge.Dynamic` as parameter type in TurboModules Differential Revision: D60966684
1 parent beebf4a commit 6230866

File tree

8 files changed

+234
-6
lines changed

8 files changed

+234
-6
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
package com.facebook.react.bridge
9+
10+
import com.facebook.jni.HybridData
11+
import com.facebook.proguard.annotations.DoNotStrip
12+
import com.facebook.proguard.annotations.DoNotStripAny
13+
14+
/**
15+
* An implementation of [Dynamic] that has a C++ implementation.
16+
*
17+
* This is used to support Legacy Native Modules that have not been migrated to the new architecture
18+
* and are using [Dynamic] as a parameter type.
19+
*/
20+
@DoNotStripAny
21+
public open class DynamicNative(
22+
@Suppress("NoHungarianNotation") @field:DoNotStrip private val mHybridData: HybridData?
23+
) : Dynamic {
24+
25+
override val type: ReadableType
26+
get() = getTypeNative()
27+
28+
override val isNull: Boolean
29+
get() = isNullNative()
30+
31+
private external fun getTypeNative(): ReadableType
32+
33+
private external fun isNullNative(): Boolean
34+
35+
external override fun asBoolean(): Boolean
36+
37+
external override fun asInt(): Int
38+
39+
external override fun asDouble(): Double
40+
41+
external override fun asString(): String
42+
43+
external override fun asArray(): ReadableArray
44+
45+
external override fun asMap(): ReadableMap
46+
47+
override fun recycle() {
48+
// Noop - nothing to recycle since there is no pooling
49+
}
50+
}

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/internal/turbomodule/core/TurboModuleInteropUtils.java

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,14 +161,11 @@ private static String convertParamClassToJniType(
161161
|| paramClass == Callback.class
162162
|| paramClass == Promise.class
163163
|| paramClass == ReadableMap.class
164-
|| paramClass == ReadableArray.class) {
164+
|| paramClass == ReadableArray.class
165+
|| paramClass == Dynamic.class) {
165166
return convertClassToJniType(paramClass);
166167
}
167168

168-
if (paramClass == Dynamic.class) {
169-
// TODO(T145105887): Output warnings that TurboModules doesn't yet support Dynamic arguments
170-
}
171-
172169
throw new ParsingException(
173170
moduleName,
174171
methodName,
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#include "JDynamicNative.h"
9+
#include "ReadableNativeArray.h"
10+
#include "ReadableNativeMap.h"
11+
12+
using namespace facebook::jni;
13+
14+
namespace facebook::react {
15+
16+
jboolean JDynamicNative::isNullNative() {
17+
return payload_.isNull();
18+
}
19+
20+
jni::local_ref<ReadableType> JDynamicNative::getTypeNative() {
21+
return ReadableType::getType(payload_.type());
22+
}
23+
24+
jni::local_ref<jstring> JDynamicNative::asString() {
25+
return jni::make_jstring(payload_.asString());
26+
}
27+
28+
jboolean JDynamicNative::asBoolean() {
29+
return payload_.asBool();
30+
}
31+
32+
jint JDynamicNative::asInt() {
33+
return payload_.asInt();
34+
}
35+
36+
jdouble JDynamicNative::asDouble() {
37+
return payload_.asDouble();
38+
}
39+
40+
jni::local_ref<ReadableArray> JDynamicNative::asArray() {
41+
return jni::adopt_local(reinterpret_cast<ReadableArray::javaobject>(
42+
ReadableNativeArray::newObjectCxxArgs(payload_).release()));
43+
}
44+
45+
jni::local_ref<ReadableMap> JDynamicNative::asMap() {
46+
return jni::adopt_local(reinterpret_cast<ReadableMap::javaobject>(
47+
ReadableNativeMap::createWithContents(std::move(payload_)).release()));
48+
}
49+
50+
} // namespace facebook::react
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
#pragma once
9+
10+
#include "NativeCommon.h"
11+
#include "ReadableNativeArray.h"
12+
#include "ReadableNativeMap.h"
13+
14+
#include <fbjni/fbjni.h>
15+
#include <folly/dynamic.h>
16+
#include <folly/json.h>
17+
18+
namespace facebook::react {
19+
20+
struct JDynamic : public jni::JavaClass<JDynamic> {
21+
constexpr static auto kJavaDescriptor = "Lcom/facebook/react/bridge/Dynamic;";
22+
};
23+
24+
class JDynamicNative : public jni::HybridClass<JDynamicNative, JDynamic> {
25+
public:
26+
constexpr static auto kJavaDescriptor =
27+
"Lcom/facebook/react/bridge/DynamicNative;";
28+
29+
JDynamicNative(folly::dynamic payload) : payload_(std::move(payload)) {}
30+
31+
static void registerNatives() {
32+
javaClassStatic()->registerNatives(
33+
{makeNativeMethod("isNullNative", JDynamicNative::isNullNative),
34+
makeNativeMethod("getTypeNative", JDynamicNative::getTypeNative),
35+
makeNativeMethod("asInt", JDynamicNative::asInt),
36+
makeNativeMethod("asDouble", JDynamicNative::asDouble),
37+
makeNativeMethod("asBoolean", JDynamicNative::asBoolean),
38+
makeNativeMethod("asString", JDynamicNative::asString),
39+
makeNativeMethod("asArray", JDynamicNative::asArray),
40+
makeNativeMethod("asMap", JDynamicNative::asMap)});
41+
}
42+
43+
private:
44+
friend HybridBase;
45+
46+
jni::local_ref<ReadableType> getTypeNative();
47+
jni::local_ref<jstring> asString();
48+
jboolean asBoolean();
49+
jint asInt();
50+
jdouble asDouble();
51+
jboolean isNullNative();
52+
jni::local_ref<ReadableArray> asArray();
53+
jni::local_ref<ReadableMap> asMap();
54+
55+
folly::dynamic payload_;
56+
};
57+
58+
} // namespace facebook::react

packages/react-native/ReactAndroid/src/main/jni/react/jni/OnLoad.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "CxxModuleWrapperBase.h"
1717
#include "InspectorNetworkRequestListener.h"
1818
#include "JCallback.h"
19+
#include "JDynamicNative.h"
1920
#include "JInspector.h"
2021
#include "JReactMarker.h"
2122
#include "JavaScriptExecutorHolder.h"
@@ -90,6 +91,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
9091
NativeMap::registerNatives();
9192
ReadableNativeMap::registerNatives();
9293
WritableNativeMap::registerNatives();
94+
JDynamicNative::registerNatives();
9395
JReactMarker::registerNatives();
9496
JInspector::registerNatives();
9597
ReactInstanceManagerInspectorTarget::registerNatives();

packages/react-native/ReactCommon/react/nativemodule/core/platform/android/ReactCommon/JavaTurboModule.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#include <react/bridging/Bridging.h>
2222
#include <react/debug/react_native_assert.h>
2323
#include <react/featureflags/ReactNativeFeatureFlags.h>
24+
#include <react/jni/JDynamicNative.h>
2425
#include <react/jni/NativeMap.h>
2526
#include <react/jni/ReadableNativeMap.h>
2627
#include <react/jni/WritableNativeMap.h>
@@ -364,7 +365,9 @@ JNIArgs convertJSIArgsToJNIArgs(
364365
continue;
365366
}
366367

367-
if (arg->isNull() || arg->isUndefined()) {
368+
// Dynamic encapsulates the Null type so we don't want to return null here.
369+
if ((arg->isNull() && type != "Lcom/facebook/react/bridge/Dynamic;") ||
370+
arg->isUndefined()) {
368371
jarg->l = nullptr;
369372
} else if (type == "Ljava/lang/Double;") {
370373
if (!arg->isNumber()) {
@@ -427,6 +430,10 @@ JNIArgs convertJSIArgsToJNIArgs(
427430
auto jParams =
428431
ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
429432
jarg->l = makeGlobalIfNecessary(jParams.release());
433+
} else if (type == "Lcom/facebook/react/bridge/Dynamic;") {
434+
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
435+
auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue);
436+
jarg->l = makeGlobalIfNecessary(jParams.release());
430437
} else {
431438
throw JavaTurboModuleInvalidArgumentTypeException(
432439
type, argIndex, methodName);

packages/react-native/ReactCommon/react/nativemodule/samples/platform/android/SampleLegacyModule.java

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@
1313
import com.facebook.proguard.annotations.DoNotStrip;
1414
import com.facebook.react.bridge.Arguments;
1515
import com.facebook.react.bridge.Callback;
16+
import com.facebook.react.bridge.Dynamic;
1617
import com.facebook.react.bridge.Promise;
1718
import com.facebook.react.bridge.ReactApplicationContext;
1819
import com.facebook.react.bridge.ReactContextBaseJavaModule;
1920
import com.facebook.react.bridge.ReactMethod;
2021
import com.facebook.react.bridge.ReadableArray;
2122
import com.facebook.react.bridge.ReadableMap;
23+
import com.facebook.react.bridge.ReadableType;
2224
import com.facebook.react.bridge.WritableArray;
2325
import com.facebook.react.bridge.WritableMap;
2426
import com.facebook.react.bridge.WritableNativeArray;
@@ -155,6 +157,57 @@ public WritableMap getUnsafeObject(ReadableMap arg) {
155157
return map;
156158
}
157159

160+
@SuppressWarnings("unused")
161+
@ReactMethod(isBlockingSynchronousMethod = true)
162+
public WritableMap getDynamic(Dynamic dynamic) {
163+
WritableNativeMap resultMap = new WritableNativeMap();
164+
ReadableType type = dynamic.getType();
165+
switch (type) {
166+
case Null -> {
167+
log("getDynamic as Null", dynamic, dynamic);
168+
resultMap.putString("type", "Null");
169+
resultMap.putNull("value");
170+
break;
171+
}
172+
case Boolean -> {
173+
boolean result = dynamic.asBoolean();
174+
log("getDynamic as Boolean", dynamic, result);
175+
resultMap.putString("type", "Boolean");
176+
resultMap.putBoolean("value", result);
177+
break;
178+
}
179+
case Number -> {
180+
double result = dynamic.asDouble();
181+
log("getDynamic as Number", dynamic, result);
182+
resultMap.putString("type", "Number");
183+
resultMap.putDouble("value", result);
184+
break;
185+
}
186+
case String -> {
187+
String result = dynamic.asString();
188+
log("getDynamic as String", dynamic, result);
189+
resultMap.putString("type", "String");
190+
resultMap.putString("value", result);
191+
break;
192+
}
193+
case Array -> {
194+
ReadableArray result = dynamic.asArray();
195+
log("getDynamic as Array", dynamic, result);
196+
resultMap.putString("type", "Array");
197+
resultMap.putArray("value", result);
198+
break;
199+
}
200+
case Map -> {
201+
ReadableMap result = dynamic.asMap();
202+
log("getDynamic as Map", dynamic, result);
203+
resultMap.putString("type", "Map");
204+
resultMap.putMap("value", result);
205+
break;
206+
}
207+
}
208+
return resultMap;
209+
}
210+
158211
@DoNotStrip
159212
@SuppressWarnings("unused")
160213
@ReactMethod(isBlockingSynchronousMethod = true)

packages/rn-tester/js/examples/TurboModule/SampleLegacyModuleExample.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,17 @@ class SampleLegacyModuleExample extends React.Component<{||}, State> {
142142
getSampleLegacyModule()?.getObject({a: 1, b: 'foo', c: null}),
143143
getValue: () =>
144144
getSampleLegacyModule()?.getValue(5, 'test', {a: 1, b: 'foo'}),
145+
getDynamicWithNull: () => getSampleLegacyModule()?.getDynamic(null),
146+
getDynamicWithBoolean: () =>
147+
getSampleLegacyModule()?.getDynamic(true),
148+
getDynamicWithNumber: () =>
149+
getSampleLegacyModule()?.getDynamic(42.24),
150+
getDynamicWithString: () =>
151+
getSampleLegacyModule()?.getDynamic('The answer is 42'),
152+
getDynamicWithArray: () =>
153+
getSampleLegacyModule()?.getDynamic(['the', 'answer', 'is', '42']),
154+
getDynamicWithMap: () =>
155+
getSampleLegacyModule()?.getDynamic({answer: '42'}),
145156
callback: () =>
146157
getSampleLegacyModule()?.getValueWithCallback(callbackValue =>
147158
this._setResult('callback', callbackValue),

0 commit comments

Comments
 (0)