Skip to content

Commit 45cd817

Browse files
authored
[0.75] Add support for handling com.facebook.react.bridge.Dynamic as parameter for TurboModules (#46066)
1 parent b5b5773 commit 45cd817

File tree

8 files changed

+216
-6
lines changed

8 files changed

+216
-6
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
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 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+
// The native representation is holding the value as Double. We do the Int conversion here.
38+
override fun asInt(): Int = asDouble().toInt()
39+
40+
external override fun asDouble(): Double
41+
42+
external override fun asString(): String
43+
44+
external override fun asArray(): ReadableArray
45+
46+
external override fun asMap(): ReadableMap
47+
48+
override fun recycle() {
49+
// Noop - nothing to recycle since there is no pooling
50+
}
51+
}

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"
@@ -89,6 +90,7 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {
8990
NativeMap::registerNatives();
9091
ReadableNativeMap::registerNatives();
9192
WritableNativeMap::registerNatives();
93+
JDynamicNative::registerNatives();
9294
JReactMarker::registerNatives();
9395
JInspector::registerNatives();
9496
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
@@ -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>
@@ -373,7 +374,9 @@ JNIArgs convertJSIArgsToJNIArgs(
373374
continue;
374375
}
375376

376-
if (arg->isNull() || arg->isUndefined()) {
377+
// Dynamic encapsulates the Null type so we don't want to return null here.
378+
if ((arg->isNull() && type != "Lcom/facebook/react/bridge/Dynamic;") ||
379+
arg->isUndefined()) {
377380
jarg->l = nullptr;
378381
} else if (type == "Ljava/lang/Double;") {
379382
if (!arg->isNumber()) {
@@ -436,6 +439,10 @@ JNIArgs convertJSIArgsToJNIArgs(
436439
auto jParams =
437440
ReadableNativeMap::createWithContents(std::move(dynamicFromValue));
438441
jarg->l = makeGlobalIfNecessary(jParams.release());
442+
} else if (type == "Lcom/facebook/react/bridge/Dynamic;") {
443+
auto dynamicFromValue = jsi::dynamicFromValue(rt, *arg);
444+
auto jParams = JDynamicNative::newObjectCxxArgs(dynamicFromValue);
445+
jarg->l = makeGlobalIfNecessary(jParams.release());
439446
} else {
440447
throw JavaTurboModuleInvalidArgumentTypeException(
441448
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
@@ -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)