diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java index 13fa94908fe17c..e76b3931510442 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/CatalystNativeJSToJavaParametersTestCase.java @@ -8,14 +8,9 @@ package com.facebook.react.tests; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - import com.facebook.react.bridge.BaseJavaModule; import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.ExecutorToken; import com.facebook.react.bridge.InvalidIteratorException; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NoSuchKeyException; @@ -23,6 +18,7 @@ import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.ReadableMapKeySetIterator; +import com.facebook.react.bridge.ReadableNativeArray; import com.facebook.react.bridge.ReadableNativeMap; import com.facebook.react.bridge.ReadableType; import com.facebook.react.bridge.UiThreadUtil; @@ -38,6 +34,16 @@ import com.facebook.react.uimanager.ViewManager; import com.facebook.react.views.view.ReactViewManager; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + /** * Integration test to verify passing various types of parameters from JS to Java works */ @@ -299,9 +305,28 @@ public void testGetTypeFromArray() { assertEquals(ReadableType.Null, array.getType(6)); } + public void testCustomTypeViaArgumentExtractor() { + mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class).returnCustomType(); + waitForBridgeAndUIIdle(); + + List calls = mRecordingTestModule.getCustomTypeCalls(); + assertEquals(1, calls.size()); + CustomType obj = calls.get(0); + + assertEquals(obj.foo, 1); + assertEquals(obj.bar, "string"); + assertEquals(obj.baz, true); + assertEquals(obj.obj.innerBool, true); + assertEquals(obj.obj.innerStr, "other string"); + assertEquals(obj.arr.size(), 3); + assertEquals(obj.arr.getInt(0), 1); + assertEquals(obj.arr.getString(1), "foo"); + assertEquals(obj.arr.getString(2), "bar"); + } + public void testGetTypeFromMap() { mCatalystInstance.getJSModule(TestJSToJavaParametersModule.class) - .returnMapWithStringDoubleIntMapArrayBooleanNull(); + .returnMapWithStringDoubleIntMapArrayBooleanNull(); waitForBridgeAndUIIdle(); List calls = mRecordingTestModule.getMapCalls(); @@ -668,11 +693,51 @@ private void mapGetByType(ReadableMap map, String key, String typeToAskFor) { } } - private class RecordingTestModule extends BaseJavaModule { + static class CustomType { + private final int foo; + private final String bar; + private final boolean baz; + private final Obj obj; + private final ReadableArray arr; + + CustomType(ReadableMap map) { + foo = map.getInt("foo"); + bar = map.getString("bar"); + baz = map.getBoolean("baz"); + obj = new CustomType.Obj(map.getMap("obj")); + arr = map.getArray("arr"); + } + + static class Obj { + private final String innerStr; + private final boolean innerBool; + + Obj(ReadableMap obj) { + this.innerStr = obj.getString("innerStr"); + this.innerBool = obj.getBoolean("innerBool"); + } + } + } - private final List mBasicTypesCalls = new ArrayList(); - private final List mArrayCalls = new ArrayList(); - private final List mMapCalls = new ArrayList(); + private class RecordingTestModule extends BaseJavaModule { + private final List mBasicTypesCalls = new ArrayList<>(); + private final List mArrayCalls = new ArrayList<>(); + private final List mMapCalls = new ArrayList<>(); + private final List mCustomTypeCalls = new ArrayList<>(); + + @Override protected List getCustomExtractors() { + return Collections.singletonList(new CustomArgumentExtractor() { + @Override public boolean supportsType(Class type) { + return CustomType.class.isAssignableFrom(type); + } + + @Nullable @Override public CustomType extractArgument( + CatalystInstance catalystInstance, ExecutorToken executorToken, + ReadableNativeArray jsArguments, int atIndex) { + return new CustomType(jsArguments.getMap(0)); + } + }); + } @Override public String getName() { @@ -689,6 +754,11 @@ public void receiveArray(ReadableArray array) { mArrayCalls.add(array); } + @ReactMethod + public void receiveCustomType(CustomType type) { + mCustomTypeCalls.add(type); + } + @ReactMethod public void receiveMap(ReadableMap map) { mMapCalls.add(map); @@ -705,5 +775,9 @@ public List getArrayCalls() { public List getMapCalls() { return mMapCalls; } + + public List getCustomTypeCalls() { + return mCustomTypeCalls; + } } } diff --git a/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js b/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js index 57380f3bcba5d7..ef858539e8d09c 100644 --- a/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js +++ b/ReactAndroid/src/androidTest/js/TestJSToJavaParametersModule.js @@ -107,6 +107,18 @@ var TestJSToJavaParametersModule = { '\u017C\u00F3\u0142\u0107 g\u0119\u015Bl\u0105 \u6211 \uD83D\uDE0E ja\u017A\u0107' ]); }, + returnCustomType: function() { + Recording.receiveCustomType({ + foo: 1, + bar: "string", + baz: true, + obj: { + innerStr: 'other string', + innerBool: true + }, + arr: [1,'foo','bar'], + }); + }, }; BatchedBridge.registerCallableModule( diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java index 2ea0cb4d1fc5b3..80d8608460198e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/BaseJavaModule.java @@ -9,16 +9,19 @@ package com.facebook.react.bridge; -import javax.annotation.Nullable; +import com.facebook.infer.annotation.Assertions; +import com.facebook.systrace.Systrace; +import com.facebook.systrace.SystraceMessage; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.lang.reflect.Type; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; -import com.facebook.infer.annotation.Assertions; -import com.facebook.systrace.Systrace; -import com.facebook.systrace.SystraceMessage; +import javax.annotation.Nullable; import static com.facebook.infer.annotation.Assertions.assertNotNull; import static com.facebook.systrace.Systrace.TRACE_TAG_REACT_JAVA_BRIDGE; @@ -38,7 +41,9 @@ * 2/ {@link String} mapped from JS string * 3/ {@link ReadableArray} mapped from JS Array * 4/ {@link ReadableMap} mapped from JS Object - * 5/ {@link Callback} mapped from js function and can be used only as a last parameter or in the + * 5/ Any {@link Type} that can be handled by a {@link CustomArgumentExtractor} that's returned + * by {@link BaseJavaModule#getCustomExtractors()}. + * 6/ {@link Callback} mapped from js function and can be used only as a last parameter or in the * case when it express success & error callback pair as two last arguments respecively. * * All methods exposed as native to JS with {@link ReactMethod} annotation must return @@ -49,11 +54,11 @@ */ public abstract class BaseJavaModule implements NativeModule { // taken from Libraries/Utilities/MessageQueue.js - static final public String METHOD_TYPE_ASYNC = "async"; - static final public String METHOD_TYPE_PROMISE= "promise"; - static final public String METHOD_TYPE_SYNC = "sync"; + private static final String METHOD_TYPE_ASYNC = "async"; + private static final String METHOD_TYPE_PROMISE= "promise"; + public static final String METHOD_TYPE_SYNC = "sync"; - private static abstract class ArgumentExtractor { + public static abstract class ArgumentExtractor { public int getJSArgumentsNeeded() { return 1; } @@ -62,6 +67,14 @@ public int getJSArgumentsNeeded() { CatalystInstance catalystInstance, ExecutorToken executorToken, ReadableNativeArray jsArguments, int atIndex); } + protected List getCustomExtractors() { + return Collections.emptyList(); + } + + public abstract class CustomArgumentExtractor extends ArgumentExtractor { + public abstract boolean supportsType(Class type); + } + static final private ArgumentExtractor ARGUMENT_EXTRACTOR_BOOLEAN = new ArgumentExtractor() { @Override @@ -159,7 +172,7 @@ public Promise extractArgument( public class JavaMethod implements NativeMethod { - private Method mMethod; + private final Method mMethod; private final ArgumentExtractor[] mArgumentExtractors; private final String mSignature; private final Object[] mArguments; @@ -167,7 +180,7 @@ public class JavaMethod implements NativeMethod { private final int mJSArgumentsNeeded; private final String mTraceName; - public JavaMethod(Method method) { + JavaMethod(Method method) { mMethod = method; Class[] parameterTypes = method.getParameterTypes(); mArgumentExtractors = buildArgumentExtractors(parameterTypes); @@ -259,13 +272,27 @@ private ArgumentExtractor[] buildArgumentExtractors(Class[] paramTypes) { } else if (argumentClass == ReadableArray.class) { argumentExtractors[i] = ARGUMENT_EXTRACTOR_ARRAY; } else { - throw new RuntimeException( + ArgumentExtractor argumentExtractor = findArgumentExtractorForType(argumentClass); + if (argumentExtractor != null) { + argumentExtractors[i] = argumentExtractor; + } else { + throw new RuntimeException( "Got unknown argument class: " + argumentClass.getSimpleName()); + } } } return argumentExtractors; } + private ArgumentExtractor findArgumentExtractorForType(Class argumentClass) { + for (CustomArgumentExtractor customArgumentExtractor : getCustomExtractors()) { + if (customArgumentExtractor.supportsType(argumentClass)) { + return customArgumentExtractor; + } + } + return null; + } + private int calculateJSArgumentsNeeded() { int n = 0; for (ArgumentExtractor extractor : mArgumentExtractors) { @@ -349,10 +376,10 @@ public String getType() { public class SyncJavaHook implements SyncNativeHook { - private Method mMethod; + private final Method mMethod; private final String mSignature; - public SyncJavaHook(Method method) { + SyncJavaHook(Method method) { mMethod = method; mSignature = buildSignature(method); } @@ -483,8 +510,7 @@ private static char paramTypeToChar(Class paramClass) { } else if (paramClass == ReadableArray.class) { return 'A'; } else { - throw new RuntimeException( - "Got unknown param class: " + paramClass.getSimpleName()); + return 'M'; } }