From 4b9350061fa3d186fdd3a973e1b46f60a7ac03b9 Mon Sep 17 00:00:00 2001 From: Petter Hesselberg Date: Sun, 29 Sep 2019 19:21:10 -0700 Subject: [PATCH] Petterh/support parcelable array args (#26379) Summary: `ReactRootView.startReactApplication` takes a `Bundle` argument called `initialProperties`. This is translated to a `ReadableMap` via `Arguments.fromBundle`. If the bundle contains an array of bundles, this gets translated by `Arguments.fromArray`. If the bundle was passed from one activity to another via intent extras, however, there is a problem. After the bundle has been marshaled and unmarshaled, the array of `Bundle`s come out the other end as an arrap of `Parcelable`s, although each array element remains a `Bundle`. This results in an "Unknown array type" exception. This PR fixes this by adding support for `Parcelable` arrays – provided they contain only members of type `Bundle`. ## Changelog [Android] [Fixed] - Don't throw "Unknown array type" exception when passing serialized bundle arrays in ReactRootView.startReactApplication's initialProperties parameter Pull Request resolved: https://github.com/facebook/react-native/pull/26379 Test Plan: Added test class `ArgumentsTest`. The test method `testFromMarshaledBundle` fails when used on the old version of `Arguments`. Differential Revision: D17661203 Pulled By: cpojer fbshipit-source-id: 63612d78f49bdf9cc53f6f21ae883dba6cebce84 --- .../facebook/react/bridge/ArgumentsTest.java | 89 +++++++++++++++++++ .../com/facebook/react/bridge/Arguments.java | 10 +++ 2 files changed, 99 insertions(+) create mode 100644 ReactAndroid/src/androidTest/java/com/facebook/react/bridge/ArgumentsTest.java diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/bridge/ArgumentsTest.java b/ReactAndroid/src/androidTest/java/com/facebook/react/bridge/ArgumentsTest.java new file mode 100644 index 00000000000000..bd0932b6304a9f --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/bridge/ArgumentsTest.java @@ -0,0 +1,89 @@ +package com.facebook.react.bridge; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.runner.AndroidJUnit4; + +import com.facebook.soloader.SoLoader; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static com.facebook.react.bridge.Arguments.fromBundle; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +@RunWith(AndroidJUnit4.class) +public class ArgumentsTest { + + @Before + public void setUp() { + Context context = InstrumentationRegistry.getInstrumentation().getTargetContext(); + SoLoader.init(context, false); + } + + @Test + public void testFromBundle() { + verifyBundle(createBundle()); + } + + /** + * When passing a bundle via {@link Intent} extras, it gets parceled and unparceled. + * Any array of bundles will return as an array of {@link Parcelable} instead. This test + * verifies that {@link Arguments#fromArray} handles this situation correctly. + */ + @Test + public void testFromMarshaledBundle() { + verifyBundle(marshalAndUnmarshalBundle(createBundle())); + } + + private void verifyBundle(@NonNull Bundle bundle) { + WritableMap map = fromBundle(bundle); + assertNotNull(map); + + assertEquals(ReadableType.Array, map.getType("children")); + ReadableArray children = map.getArray("children"); + assertNotNull(children); + assertEquals(1, children.size()); + + assertEquals(ReadableType.Map, children.getType(0)); + ReadableMap child = children.getMap(0); + assertNotNull(child); + assertEquals("exampleChild", child.getString("exampleChildKey")); + } + + @NonNull + private Bundle marshalAndUnmarshalBundle(@NonNull Bundle bundle) { + Parcel parcel = null; + try { + parcel = Parcel.obtain(); + bundle.writeToParcel(parcel, 0); + + byte[] bytes = parcel.marshall(); + parcel.unmarshall(bytes, 0, bytes.length); + parcel.setDataPosition(0); + return Bundle.CREATOR.createFromParcel(parcel); + } finally { + if (parcel != null) { + parcel.recycle(); + } + } + } + + @NonNull + private Bundle createBundle() { + Bundle bundle = new Bundle(); + Bundle[] children = new Bundle[1]; + children[0] = new Bundle(); + children[0].putString("exampleChildKey", "exampleChild"); + bundle.putSerializable("children", children); + return bundle; + } +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/bridge/Arguments.java b/ReactAndroid/src/main/java/com/facebook/react/bridge/Arguments.java index e17e3c1aca1166..9d4dc86014a88b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/bridge/Arguments.java +++ b/ReactAndroid/src/main/java/com/facebook/react/bridge/Arguments.java @@ -7,6 +7,8 @@ package com.facebook.react.bridge; import android.os.Bundle; +import android.os.Parcelable; + import androidx.annotation.Nullable; import java.lang.reflect.Array; import java.util.AbstractList; @@ -218,6 +220,14 @@ public static WritableArray fromArray(Object array) { for (boolean v : (boolean[]) array) { catalystArray.pushBoolean(v); } + } else if (array instanceof Parcelable[]) { + for (Parcelable v : (Parcelable[]) array) { + if (v instanceof Bundle) { + catalystArray.pushMap(fromBundle((Bundle) v)); + } else { + throw new IllegalArgumentException("Unexpected array member type " + v.getClass()); + } + } } else { throw new IllegalArgumentException("Unknown array type " + array.getClass()); }