Skip to content

Commit

Permalink
Add support for handling com.facebook.react.bridge.Dynamic as param…
Browse files Browse the repository at this point in the history
…eter 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
  • Loading branch information
cortinico authored and facebook-github-bot committed Aug 12, 2024
1 parent 471445e commit d01f1b3
Show file tree
Hide file tree
Showing 8 changed files with 216 additions and 6 deletions.
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

0 comments on commit d01f1b3

Please sign in to comment.