diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index df503ba0270324..9de132e1490bb6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -15,6 +15,7 @@ import android.os.Build; import android.os.Bundle; import android.view.KeyEvent; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Callback; @@ -33,6 +34,7 @@ public class ReactActivityDelegate { private @Nullable PermissionListener mPermissionListener; private @Nullable Callback mPermissionsCallback; private ReactDelegate mReactDelegate; + private boolean mConcurrentRootEnabled; @Deprecated public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) { @@ -45,10 +47,27 @@ public ReactActivityDelegate(ReactActivity activity, @Nullable String mainCompon mMainComponentName = mainComponentName; } + /** + * Public API to populate the launch options that will be passed to React. Here you can customize + * the values that will be passed as `initialProperties` to the Renderer. + * + * @return Either null or a key-value map as a Bundle + */ protected @Nullable Bundle getLaunchOptions() { return null; } + private @NonNull Bundle composeLaunchOptions() { + Bundle composedLaunchOptions = getLaunchOptions(); + if (isConcurrentRootEnabled()) { + if (composedLaunchOptions == null) { + composedLaunchOptions = new Bundle(); + } + composedLaunchOptions.putBoolean("concurrentRoot", true); + } + return composedLaunchOptions; + } + protected ReactRootView createRootView() { return new ReactRootView(getContext()); } @@ -74,9 +93,10 @@ public String getMainComponentName() { protected void onCreate(Bundle savedInstanceState) { String mainComponentName = getMainComponentName(); + Bundle launchOptions = composeLaunchOptions(); mReactDelegate = new ReactDelegate( - getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) { + getPlainActivity(), getReactNativeHost(), mainComponentName, launchOptions) { @Override protected ReactRootView createRootView() { return ReactActivityDelegate.this.createRootView(); @@ -190,4 +210,16 @@ protected Context getContext() { protected Activity getPlainActivity() { return ((Activity) getContext()); } + + /** + * Override this method to enable Concurrent Root on the surface for this Activity. See: + * https://reactjs.org/blog/2022/03/29/react-v18.html + * + *

This requires to be rendering on Fabric (i.e. on the New Architecture). + * + * @return Wether you want to enable Concurrent Root for this surface or not. + */ + protected boolean isConcurrentRootEnabled() { + return false; + } } diff --git a/ReactAndroid/src/test/java/com/facebook/react/ReactActivityDelegateTest.kt b/ReactAndroid/src/test/java/com/facebook/react/ReactActivityDelegateTest.kt new file mode 100644 index 00000000000000..3cad72ef2afdd1 --- /dev/null +++ b/ReactAndroid/src/test/java/com/facebook/react/ReactActivityDelegateTest.kt @@ -0,0 +1,62 @@ +/* + * 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 + +import android.os.Bundle +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class ReactActivityDelegateTest { + + @Test + fun delegateWithConcurrentRoot_populatesInitialPropsCorrectly() { + val delegate = + object : ReactActivityDelegate(null, "test-delegate") { + override fun isConcurrentRootEnabled() = true + public val inspectLaunchOptions: Bundle? + get() = getLaunchOptions() + } + + assertNotNull(delegate.inspectLaunchOptions) + assertTrue(delegate.inspectLaunchOptions!!.containsKey("concurrentRoot")) + assertTrue(delegate.inspectLaunchOptions!!.getBoolean("concurrentRoot")) + } + + @Test + fun delegateWithoutConcurrentRoot_hasNullInitialProperties() { + val delegate = + object : ReactActivityDelegate(null, "test-delegate") { + override fun isConcurrentRootEnabled() = false + public val inspectLaunchOptions: Bundle? + get() = getLaunchOptions() + } + + assertNull(delegate.inspectLaunchOptions) + } + + @Test + fun delegateWithConcurrentRoot_composesInitialPropertiesCorrectly() { + val delegate = + object : ReactActivityDelegate(null, "test-delegate") { + override fun isConcurrentRootEnabled() = true + override fun getLaunchOptions(): Bundle = + Bundle().apply { putString("test-property", "test-value") } + public val inspectLaunchOptions: Bundle? + get() = getLaunchOptions() + } + + assertNotNull(delegate.inspectLaunchOptions) + assertTrue(delegate.inspectLaunchOptions!!.containsKey("concurrentRoot")) + assertTrue(delegate.inspectLaunchOptions!!.getBoolean("concurrentRoot")) + assertTrue(delegate.inspectLaunchOptions!!.containsKey("test-property")) + assertEquals("test-value", delegate.inspectLaunchOptions!!.getString("test-property")) + } +} diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterActivity.java b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterActivity.java index 14b819bf56ff0d..75b0dadaa0347b 100644 --- a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterActivity.java +++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterActivity.java @@ -51,6 +51,11 @@ protected void onCreate(Bundle savedInstanceState) { protected Bundle getLaunchOptions() { return mInitialProps; } + + @Override + protected boolean isConcurrentRootEnabled() { + return true; + } } @Override diff --git a/template/android/app/src/main/java/com/helloworld/MainActivity.java b/template/android/app/src/main/java/com/helloworld/MainActivity.java index eae5dada6e830c..405d80a8d97205 100644 --- a/template/android/app/src/main/java/com/helloworld/MainActivity.java +++ b/template/android/app/src/main/java/com/helloworld/MainActivity.java @@ -37,5 +37,12 @@ protected ReactRootView createRootView() { reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED); return reactRootView; } + + @Override + protected boolean isConcurrentRootEnabled() { + // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18). + // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html + return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; + } } }