Skip to content

Commit 71b17e2

Browse files
committed
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 Reviewed By: mdvacca, cipolleschi Differential Revision: D60966684 fbshipit-source-id: 2e63bc53ede5277a9c12f1b19f05f6099f5f35f9
1 parent 5e6fed3 commit 71b17e2

File tree

8 files changed

+214
-6
lines changed

8 files changed

+214
-6
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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+
private class DynamicNative(
22+
@Suppress("NoHungarianNotation") @field:DoNotStrip private val mHybridData: HybridData?
23+
) : Dynamic {
24+
25+
override fun getType(): ReadableType = getTypeNative()
26+
27+
override fun isNull(): Boolean = isNullNative()
28+
29+
private external fun getTypeNative(): ReadableType
30+
31+
private external fun isNullNative(): Boolean
32+
33+
external override fun asBoolean(): Boolean
34+
35+
// The native representation is holding the value as Double. We do the Int conversion here.
36+
override fun asInt(): Int = asDouble().toInt()
37+
38+
external override fun asDouble(): Double
39+
40+
external override fun asString(): String
41+
42+
external override fun asArray(): ReadableArray
43+
44+
external override fun asMap(): ReadableMap
45+
46+
override fun recycle() {
47+
// Noop - nothing to recycle since there is no pooling
48+
}
49+
}

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: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
jdouble JDynamicNative::asDouble() {
33+
return payload_.asDouble();
34+
}
35+
36+
jni::local_ref<ReadableArray> JDynamicNative::asArray() {
37+
return jni::adopt_local(reinterpret_cast<ReadableArray::javaobject>(
38+
ReadableNativeArray::newObjectCxxArgs(payload_).release()));
39+
}
40+
41+
jni::local_ref<ReadableMap> JDynamicNative::asMap() {
42+
return jni::adopt_local(reinterpret_cast<ReadableMap::javaobject>(
43+
ReadableNativeMap::createWithContents(std::move(payload_)).release()));
44+
}
45+
46+
} // namespace facebook::react
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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("asDouble", JDynamicNative::asDouble),
36+
makeNativeMethod("asBoolean", JDynamicNative::asBoolean),
37+
makeNativeMethod("asString", JDynamicNative::asString),
38+
makeNativeMethod("asArray", JDynamicNative::asArray),
39+
makeNativeMethod("asMap", JDynamicNative::asMap)});
40+
}
41+
42+
private:
43+
friend HybridBase;
44+
45+
jni::local_ref<ReadableType> getTypeNative();
46+
jni::local_ref<jstring> asString();
47+
jboolean asBoolean();
48+
jdouble asDouble();
49+
jboolean isNullNative();
50+
jni::local_ref<ReadableArray> asArray();
51+
jni::local_ref<ReadableMap> asMap();
52+
53+
folly::dynamic payload_;
54+
};
55+
56+
} // 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
@@ -15,6 +15,7 @@
1515
#include "CatalystInstanceImpl.h"
1616
#include "CxxModuleWrapperBase.h"
1717
#include "JCallback.h"
18+
#include "JDynamicNative.h"
1819
#include "JInspector.h"
1920
#include "JReactMarker.h"
2021
#include "JavaScriptExecutorHolder.h"
@@ -88,6 +89,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
8889
NativeMap::registerNatives();
8990
ReadableNativeMap::registerNatives();
9091
WritableNativeMap::registerNatives();
92+
JDynamicNative::registerNatives();
9193
JReactMarker::registerNatives();
9294
JInspector::registerNatives();
9395
});

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
@@ -19,6 +19,7 @@
1919
#include <jsi/JSIDynamic.h>
2020
#include <react/bridging/Bridging.h>
2121
#include <react/debug/react_native_assert.h>
22+
#include <react/jni/JDynamicNative.h>
2223
#include <react/jni/NativeMap.h>
2324
#include <react/jni/ReadableNativeMap.h>
2425
#include <react/jni/WritableNativeMap.h>
@@ -370,7 +371,9 @@ JNIArgs convertJSIArgsToJNIArgs(
370371
continue;
371372
}
372373

373-
if (arg->isNull() || arg->isUndefined()) {
374+
// Dynamic encapsulates the Null type so we don't want to return null here.
375+
if ((arg->isNull() && type != "Lcom/facebook/react/bridge/Dynamic;") ||
376+
arg->isUndefined()) {
374377
jarg->l = nullptr;
375378
} else if (type == "Ljava/lang/Double;") {
376379
if (!arg->isNumber()) {
@@ -433,6 +436,10 @@ JNIArgs convertJSIArgsToJNIArgs(
433436
auto jParams =
434437
ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
435438
jarg->l = makeGlobalIfNecessary(jParams.release());
439+
} else if (type == "Lcom/facebook/react/bridge/Dynamic;") {
440+
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
441+
auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue);
442+
jarg->l = makeGlobalIfNecessary(jParams.release());
436443
} else {
437444
throw JavaTurboModuleInvalidArgumentTypeException(
438445
type, argIndex, methodName);

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

Lines changed: 40 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,44 @@ 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+
if (type == ReadableType.Null) {
166+
log("getDynamic as Null", dynamic, dynamic);
167+
resultMap.putString("type", "Null");
168+
resultMap.putNull("value");
169+
} else if (type == ReadableType.Boolean) {
170+
boolean result = dynamic.asBoolean();
171+
log("getDynamic as Boolean", dynamic, result);
172+
resultMap.putString("type", "Boolean");
173+
resultMap.putBoolean("value", result);
174+
} else if (type == ReadableType.Number) {
175+
int result = dynamic.asInt();
176+
log("getDynamic as Number", dynamic, result);
177+
resultMap.putString("type", "Number");
178+
resultMap.putInt("value", result);
179+
} else if (type == ReadableType.String) {
180+
String result = dynamic.asString();
181+
log("getDynamic as String", dynamic, result);
182+
resultMap.putString("type", "String");
183+
resultMap.putString("value", result);
184+
} else if (type == ReadableType.Array) {
185+
ReadableArray result = dynamic.asArray();
186+
log("getDynamic as Array", dynamic, result);
187+
resultMap.putString("type", "Array");
188+
resultMap.putArray("value", result);
189+
} else if (type == ReadableType.Map) {
190+
ReadableMap result = dynamic.asMap();
191+
log("getDynamic as Map", dynamic, result);
192+
resultMap.putString("type", "Map");
193+
resultMap.putMap("value", result);
194+
}
195+
return resultMap;
196+
}
197+
158198
@DoNotStrip
159199
@SuppressWarnings("unused")
160200
@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
@@ -141,6 +141,17 @@ class SampleLegacyModuleExample extends React.Component<{||}, State> {
141141
getSampleLegacyModule()?.getObject({a: 1, b: 'foo', c: null}),
142142
getValue: () =>
143143
getSampleLegacyModule()?.getValue(5, 'test', {a: 1, b: 'foo'}),
144+
getDynamicWithNull: () => getSampleLegacyModule()?.getDynamic(null),
145+
getDynamicWithBoolean: () =>
146+
getSampleLegacyModule()?.getDynamic(true),
147+
getDynamicWithNumber: () =>
148+
getSampleLegacyModule()?.getDynamic(42.24),
149+
getDynamicWithString: () =>
150+
getSampleLegacyModule()?.getDynamic('The answer is 42'),
151+
getDynamicWithArray: () =>
152+
getSampleLegacyModule()?.getDynamic(['the', 'answer', 'is', '42']),
153+
getDynamicWithMap: () =>
154+
getSampleLegacyModule()?.getDynamic({answer: '42'}),
144155
callback: () =>
145156
getSampleLegacyModule()?.getValueWithCallback(callbackValue =>
146157
this._setResult('callback', callbackValue),

0 commit comments

Comments
 (0)