From 087617068167dde812ee2d276fce10dfe12a862d Mon Sep 17 00:00:00 2001 From: Lulu Wu Date: Sat, 24 Jun 2023 08:06:55 -0700 Subject: [PATCH] Enable Bridgeless (#38002) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38002 Enable Bridgeless in RNTester Android by adding the initialization logic Changelog: [Internal] Reviewed By: fkgozali Differential Revision: D46375362 fbshipit-source-id: 4c62cba5920d4c90d83a43afed4c4f91e52ec469 --- .../facebook/react/ReactActivityDelegate.java | 71 +++++--- .../com/facebook/react/ReactDelegate.java | 118 +++++++++---- .../facebook/react/bridgeless/ReactHost.java | 27 ++- .../react/bridgeless/ReactSurface.java | 10 +- .../react/bridgeless/internal/bolts/Task.java | 3 +- .../DefaultNewArchitectureEntryPoint.kt | 11 +- .../DefaultTurboModuleManagerDelegate.kt | 2 +- .../devsupport/DevSupportManagerBase.java | 4 +- .../react/interfaces/ReactHostInterface.kt | 9 + .../react/interfaces/ReactSurfaceInterface.kt | 8 + .../react/interfaces/TaskInterface.kt | 13 ++ .../react/bridgeless/ReactSurfaceTest.java | 7 +- .../react/uiapp/RNTesterApplication.java | 39 +++++ .../uiapp/RNTesterReactHostDelegate.java | 157 ++++++++++++++++++ 14 files changed, 409 insertions(+), 70 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/TaskInterface.kt create mode 100644 packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.java diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java index 82b3fee2a85..5c942e90140 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactActivityDelegate.java @@ -19,6 +19,8 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.Callback; +import com.facebook.react.config.ReactFeatureFlags; +import com.facebook.react.interfaces.ReactHostInterface; import com.facebook.react.modules.core.PermissionListener; /** @@ -86,6 +88,10 @@ public class ReactActivityDelegate { return ((ReactApplication) getPlainActivity().getApplication()).getReactNativeHost(); } + public ReactHostInterface getReactHost() { + return ((ReactApplication) getPlainActivity().getApplication()).getReactHostInterface(); + } + public ReactInstanceManager getReactInstanceManager() { return mReactDelegate.getReactInstanceManager(); } @@ -97,14 +103,19 @@ public class ReactActivityDelegate { protected void onCreate(Bundle savedInstanceState) { String mainComponentName = getMainComponentName(); final Bundle launchOptions = composeLaunchOptions(); - mReactDelegate = - new ReactDelegate( - getPlainActivity(), getReactNativeHost(), mainComponentName, launchOptions) { - @Override - protected ReactRootView createRootView() { - return ReactActivityDelegate.this.createRootView(launchOptions); - } - }; + if (ReactFeatureFlags.enableBridgelessArchitecture) { + mReactDelegate = + new ReactDelegate(getPlainActivity(), getReactHost(), mainComponentName, launchOptions); + } else { + mReactDelegate = + new ReactDelegate( + getPlainActivity(), getReactNativeHost(), mainComponentName, launchOptions) { + @Override + protected ReactRootView createRootView() { + return ReactActivityDelegate.this.createRootView(launchOptions); + } + }; + } if (mainComponentName != null) { loadApp(mainComponentName); } @@ -137,11 +148,13 @@ public class ReactActivityDelegate { } public boolean onKeyDown(int keyCode, KeyEvent event) { - if (getReactNativeHost().hasInstance() - && getReactNativeHost().getUseDeveloperSupport() - && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { - event.startTracking(); - return true; + if (!ReactFeatureFlags.enableBridgelessArchitecture) { + if (getReactNativeHost().hasInstance() + && getReactNativeHost().getUseDeveloperSupport() + && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { + event.startTracking(); + return true; + } } return false; } @@ -151,11 +164,13 @@ public class ReactActivityDelegate { } public boolean onKeyLongPress(int keyCode, KeyEvent event) { - if (getReactNativeHost().hasInstance() - && getReactNativeHost().getUseDeveloperSupport() - && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { - getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); - return true; + if (!ReactFeatureFlags.enableBridgelessArchitecture) { + if (getReactNativeHost().hasInstance() + && getReactNativeHost().getUseDeveloperSupport() + && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { + getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); + return true; + } } return false; } @@ -165,22 +180,28 @@ public class ReactActivityDelegate { } public boolean onNewIntent(Intent intent) { - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onNewIntent(intent); - return true; + if (!ReactFeatureFlags.enableBridgelessArchitecture) { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onNewIntent(intent); + return true; + } } return false; } public void onWindowFocusChanged(boolean hasFocus) { - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus); + if (!ReactFeatureFlags.enableBridgelessArchitecture) { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus); + } } } public void onConfigurationChanged(Configuration newConfig) { - if (getReactNativeHost().hasInstance()) { - getReactInstanceManager().onConfigurationChanged(getContext(), newConfig); + if (!ReactFeatureFlags.enableBridgelessArchitecture) { + if (getReactNativeHost().hasInstance()) { + getReactInstanceManager().onConfigurationChanged(getContext(), newConfig); + } } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.java index 3f6934a33f2..0ed6f7d20a7 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactDelegate.java @@ -14,7 +14,10 @@ import android.view.KeyEvent; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.facebook.infer.annotation.Assertions; +import com.facebook.react.config.ReactFeatureFlags; import com.facebook.react.devsupport.DoubleTapReloadRecognizer; +import com.facebook.react.interfaces.ReactHostInterface; +import com.facebook.react.interfaces.ReactSurfaceInterface; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; /** @@ -32,7 +35,9 @@ public class ReactDelegate { @Nullable private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer; - private ReactNativeHost mReactNativeHost; + @Nullable private ReactNativeHost mReactNativeHost; + @Nullable private ReactHostInterface mReactHost; + @Nullable private ReactSurfaceInterface mReactSurface; private boolean mFabricEnabled = false; @@ -48,6 +53,18 @@ public class ReactDelegate { mReactNativeHost = reactNativeHost; } + public ReactDelegate( + Activity activity, + ReactHostInterface reactHost, + @Nullable String appKey, + @Nullable Bundle launchOptions) { + mActivity = activity; + mMainComponentName = appKey; + mLaunchOptions = launchOptions; + mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); + mReactHost = reactHost; + } + public ReactDelegate( Activity activity, ReactNativeHost reactNativeHost, @@ -63,48 +80,72 @@ public class ReactDelegate { } public void onHostResume() { - if (getReactNativeHost().hasInstance()) { + if (ReactFeatureFlags.enableBridgelessArchitecture) { if (mActivity instanceof DefaultHardwareBackBtnHandler) { - getReactNativeHost() - .getReactInstanceManager() - .onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity); - } else { - throw new ClassCastException( - "Host Activity does not implement DefaultHardwareBackBtnHandler"); + mReactHost.onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity); + } + } else { + if (getReactNativeHost().hasInstance()) { + if (mActivity instanceof DefaultHardwareBackBtnHandler) { + getReactNativeHost() + .getReactInstanceManager() + .onHostResume(mActivity, (DefaultHardwareBackBtnHandler) mActivity); + } else { + throw new ClassCastException( + "Host Activity does not implement DefaultHardwareBackBtnHandler"); + } } } } public void onHostPause() { - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onHostPause(mActivity); + if (ReactFeatureFlags.enableBridgelessArchitecture) { + mReactHost.onHostPause(mActivity); + } else { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onHostPause(mActivity); + } } } public void onHostDestroy() { - if (mReactRootView != null) { - mReactRootView.unmountReactApplication(); - mReactRootView = null; - } - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onHostDestroy(mActivity); + if (ReactFeatureFlags.enableBridgelessArchitecture) { + mReactHost.onHostDestroy(mActivity); + } else { + if (mReactRootView != null) { + mReactRootView.unmountReactApplication(); + mReactRootView = null; + } + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onHostDestroy(mActivity); + } } } public boolean onBackPressed() { - if (getReactNativeHost().hasInstance()) { - getReactNativeHost().getReactInstanceManager().onBackPressed(); + if (ReactFeatureFlags.enableBridgelessArchitecture) { + mReactHost.onBackPressed(); return true; + } else { + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onBackPressed(); + return true; + } } return false; } public void onActivityResult( int requestCode, int resultCode, Intent data, boolean shouldForwardToReactInstance) { - if (getReactNativeHost().hasInstance() && shouldForwardToReactInstance) { - getReactNativeHost() - .getReactInstanceManager() - .onActivityResult(mActivity, requestCode, resultCode, data); + if (ReactFeatureFlags.enableBridgelessArchitecture) { + // TODO T156475655: Implement onActivityResult for Bridgeless + return; + } else { + if (getReactNativeHost().hasInstance() && shouldForwardToReactInstance) { + getReactNativeHost() + .getReactInstanceManager() + .onActivityResult(mActivity, requestCode, resultCode, data); + } } } @@ -113,16 +154,31 @@ public class ReactDelegate { } public void loadApp(String appKey) { - if (mReactRootView != null) { - throw new IllegalStateException("Cannot loadApp while app is already running."); + // With Bridgeless enabled, create and start the surface + if (ReactFeatureFlags.enableBridgelessArchitecture) { + if (mReactSurface == null) { + // Create a ReactSurface + mReactSurface = mReactHost.createSurface(mActivity, appKey, mLaunchOptions); + // Set main Activity's content view + mActivity.setContentView(mReactSurface.getView()); + } + mReactSurface.start(); + } else { + if (mReactRootView != null) { + throw new IllegalStateException("Cannot loadApp while app is already running."); + } + mReactRootView = createRootView(); + mReactRootView.startReactApplication( + getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions); } - mReactRootView = createRootView(); - mReactRootView.startReactApplication( - getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions); } public ReactRootView getReactRootView() { - return mReactRootView; + if (ReactFeatureFlags.enableBridgelessArchitecture) { + return (ReactRootView) mReactSurface.getView(); + } else { + return mReactRootView; + } } protected ReactRootView createRootView() { @@ -139,7 +195,11 @@ public class ReactDelegate { * application. */ public boolean shouldShowDevMenuOrReload(int keyCode, KeyEvent event) { - if (getReactNativeHost().hasInstance() && getReactNativeHost().getUseDeveloperSupport()) { + if (ReactFeatureFlags.enableBridgelessArchitecture) { + // TODO T156475655: Implement shouldShowDevMenuOrReload for Bridgeless + return false; + } else if (getReactNativeHost().hasInstance() + && getReactNativeHost().getUseDeveloperSupport()) { if (keyCode == KeyEvent.KEYCODE_MENU) { getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); return true; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java index 354e0cf62ee..5f1eba3764e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java @@ -15,6 +15,7 @@ import static java.lang.Boolean.TRUE; import android.app.Activity; import android.content.Context; +import android.os.Bundle; import androidx.annotation.Nullable; import com.facebook.common.logging.FLog; import com.facebook.infer.annotation.Assertions; @@ -44,11 +45,13 @@ import com.facebook.react.bridgeless.internal.bolts.TaskCompletionSource; import com.facebook.react.common.LifecycleState; import com.facebook.react.common.build.ReactBuildConfig; import com.facebook.react.config.ReactFeatureFlags; +import com.facebook.react.devsupport.DevSupportManagerBase; import com.facebook.react.devsupport.DisabledDevSupportManager; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.fabric.ComponentFactory; import com.facebook.react.fabric.FabricUIManager; import com.facebook.react.interfaces.ReactHostInterface; +import com.facebook.react.interfaces.ReactSurfaceInterface; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.DeviceEventManagerModule; import com.facebook.react.uimanager.UIManagerModule; @@ -432,6 +435,15 @@ public class ReactHost implements ReactHostInterface { return assertNotNull(mDevSupportManager); } + @Override + public ReactSurfaceInterface createSurface( + Context context, String moduleName, @Nullable Bundle initialProps) { + ReactSurface surface = new ReactSurface(context, moduleName, initialProps); + surface.attachView(new ReactSurfaceView(context, surface)); + surface.attach(this); + return surface; + } + @Nullable /* package */ Activity getCurrentActivity() { return mActivity.get(); @@ -495,7 +507,7 @@ public class ReactHost implements ReactHostInterface { return null; } - /* package */ DefaultHardwareBackBtnHandler getDefaultBackButtonHandler() { + public DefaultHardwareBackBtnHandler getDefaultBackButtonHandler() { return () -> { UiThreadUtil.assertOnUiThread(); if (mDefaultHardwareBackBtnHandler != null) { @@ -1002,16 +1014,21 @@ public class ReactHost implements ReactHostInterface { log(method); final TaskCompletionSource taskCompletionSource = new TaskCompletionSource<>(); - final DevSupportManager asyncDevSupportManager = getDevSupportManager(); - final String sourceUrl = asyncDevSupportManager.getSourceUrl(); + final DevSupportManagerBase asyncDevSupportManager = + ((DevSupportManagerBase) getDevSupportManager()); + String bundleURL = + asyncDevSupportManager + .getDevServerHelper() + .getDevServerBundleURL( + Assertions.assertNotNull(asyncDevSupportManager.getJSAppBundleName())); asyncDevSupportManager.reloadJSFromServer( - sourceUrl, + bundleURL, () -> { log(method, "Creating BundleLoader"); JSBundleLoader bundleLoader = JSBundleLoader.createCachedBundleFromNetworkLoader( - sourceUrl, asyncDevSupportManager.getDownloadedJSBundleFile()); + bundleURL, asyncDevSupportManager.getDownloadedJSBundleFile()); taskCompletionSource.setResult(bundleLoader); }); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactSurface.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactSurface.java index 8568296d4d2..a65673d8209 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactSurface.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactSurface.java @@ -12,6 +12,7 @@ import android.os.Bundle; import android.util.DisplayMetrics; import android.view.View; import android.view.View.MeasureSpec; +import android.view.ViewGroup; import androidx.annotation.UiThread; import com.facebook.infer.annotation.Nullsafe; import com.facebook.infer.annotation.ThreadSafe; @@ -24,6 +25,7 @@ import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.fabric.SurfaceHandler; import com.facebook.react.fabric.SurfaceHandlerBinding; import com.facebook.react.interfaces.ReactSurfaceInterface; +import com.facebook.react.interfaces.TaskInterface; import com.facebook.react.modules.i18nmanager.I18nUtil; import com.facebook.react.uimanager.events.EventDispatcher; import java.util.concurrent.atomic.AtomicReference; @@ -130,7 +132,8 @@ public class ReactSurface implements ReactSurfaceInterface { return mSurfaceHandler; } - public @Nullable ReactSurfaceView getView() { + @Override + public @Nullable ViewGroup getView() { return mSurfaceView.get(); } @@ -144,7 +147,8 @@ public class ReactSurface implements ReactSurfaceInterface { return host.prerenderSurface(this); } - public Task start() { + @Override + public TaskInterface start() { if (mSurfaceView.get() == null) { return Task.forError( new IllegalStateException( @@ -181,7 +185,7 @@ public class ReactSurface implements ReactSurfaceInterface { public void clear() { UiThreadUtil.runOnUiThread( () -> { - ReactSurfaceView view = getView(); + ReactSurfaceView view = (ReactSurfaceView) getView(); if (view != null) { view.removeAllViews(); view.setId(View.NO_ID); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Task.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Task.java index 499afcab9d1..e02fbcbb7da 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Task.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/internal/bolts/Task.java @@ -7,6 +7,7 @@ package com.facebook.react.bridgeless.internal.bolts; +import com.facebook.react.interfaces.TaskInterface; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -26,7 +27,7 @@ import java.util.concurrent.atomic.AtomicInteger; * * @param The type of the result of the task. */ -public class Task { +public class Task implements TaskInterface { /** An {@link java.util.concurrent.Executor} that executes tasks in parallel. */ public static final ExecutorService BACKGROUND_EXECUTOR = BoltsExecutors.background(); diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultNewArchitectureEntryPoint.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultNewArchitectureEntryPoint.kt index 73cb1e0f1da..3e862dcf616 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultNewArchitectureEntryPoint.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultNewArchitectureEntryPoint.kt @@ -27,15 +27,18 @@ object DefaultNewArchitectureEntryPoint { fun load( turboModulesEnabled: Boolean = true, fabricEnabled: Boolean = true, + bridgelessEnaled: Boolean = false, dynamicLibraryName: String = "appmodules", ) { ReactFeatureFlags.useTurboModules = turboModulesEnabled ReactFeatureFlags.enableFabricRenderer = fabricEnabled ReactFeatureFlags.unstable_useFabricInterop = fabricEnabled + ReactFeatureFlags.enableBridgelessArchitecture = bridgelessEnaled this.privateFabricEnabled = fabricEnabled this.privateTurboModulesEnabled = turboModulesEnabled this.privateConcurrentReactEnabled = fabricEnabled + this.privateBridgelessEnabled = bridgelessEnaled SoLoader.loadLibrary("react_newarchdefaults") SoLoader.loadLibrary(dynamicLibraryName) @@ -49,10 +52,11 @@ object DefaultNewArchitectureEntryPoint { fun load( turboModulesEnabled: Boolean = true, fabricEnabled: Boolean = true, + bridgelessEnaled: Boolean = false, @Suppress("UNUSED_PARAMETER") concurrentReactEnabled: Boolean = true, dynamicLibraryName: String = "appmodules", ) { - load(turboModulesEnabled, fabricEnabled, dynamicLibraryName) + load(turboModulesEnabled, fabricEnabled, bridgelessEnaled, dynamicLibraryName) } private var privateFabricEnabled: Boolean = false @@ -69,4 +73,9 @@ object DefaultNewArchitectureEntryPoint { @JvmStatic val concurrentReactEnabled: Boolean get() = privateConcurrentReactEnabled + + private var privateBridgelessEnabled: Boolean = false + @JvmStatic + val bridgelessEnaled: Boolean + get() = privateBridgelessEnabled } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultTurboModuleManagerDelegate.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultTurboModuleManagerDelegate.kt index ac1fd1a4c7d..de6c9ef7579 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultTurboModuleManagerDelegate.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/defaults/DefaultTurboModuleManagerDelegate.kt @@ -27,7 +27,7 @@ private constructor(context: ReactApplicationContext, packages: List) = + public override fun build(context: ReactApplicationContext, packages: List) = DefaultTurboModuleManagerDelegate(context, packages) } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java index b393db16756..0fefa46c5df 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.java @@ -687,7 +687,7 @@ public abstract class DevSupportManagerBase implements DevSupportManager { return mCurrentContext; } - protected @Nullable String getJSAppBundleName() { + public @Nullable String getJSAppBundleName() { return mJSAppBundleName; } @@ -695,7 +695,7 @@ public abstract class DevSupportManagerBase implements DevSupportManager { return mApplicationContext; } - protected DevServerHelper getDevServerHelper() { + public DevServerHelper getDevServerHelper() { return mDevServerHelper; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactHostInterface.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactHostInterface.kt index cdaadcc7060..3086f8f5871 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactHostInterface.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactHostInterface.kt @@ -8,6 +8,8 @@ package com.facebook.react.interfaces import android.app.Activity +import android.content.Context +import android.os.Bundle import com.facebook.react.bridge.ReactContext import com.facebook.react.bridge.queue.ReactQueueConfiguration import com.facebook.react.common.LifecycleState @@ -63,4 +65,11 @@ interface ReactHostInterface { /** To be called when the host activity is destroyed. */ fun onHostDestroy(activity: Activity?) + + /** To be called to create and setup an ReactSurface. */ + fun createSurface( + context: Context, + moduleName: String, + initialProps: Bundle? + ): ReactSurfaceInterface? } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactSurfaceInterface.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactSurfaceInterface.kt index 5dd268bdf09..a045b02754f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactSurfaceInterface.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/ReactSurfaceInterface.kt @@ -7,8 +7,16 @@ package com.facebook.react.interfaces +import android.view.ViewGroup + /** Represents a Surface in React Native. */ interface ReactSurfaceInterface { // the API of this interface will be completed as we analyze and refactor API of ReactSurface, // ReactRootView, etc. + + // Start running this surface + fun start(): TaskInterface + + // Get React root view of this surface + fun getView(): ViewGroup? } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/TaskInterface.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/TaskInterface.kt new file mode 100644 index 00000000000..a6e980a638e --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/interfaces/TaskInterface.kt @@ -0,0 +1,13 @@ +/* + * 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.interfaces + +/** + * This is the public interface for Task which represents the result of an asynchronous computation. + */ +interface TaskInterface {} diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactSurfaceTest.java b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactSurfaceTest.java index aeba7ffe654..70ea7531537 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactSurfaceTest.java +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/bridgeless/ReactSurfaceTest.java @@ -88,7 +88,7 @@ public class ReactSurfaceTest { public void testStart() throws InterruptedException { mReactSurface.attach(mReactHost); assertThat(mReactHost.isSurfaceAttached(mReactSurface)).isFalse(); - Task task = mReactSurface.start(); + Task task = (Task) mReactSurface.start(); task.waitForCompletion(); verify(mReactHost).startSurface(mReactSurface); @@ -99,7 +99,7 @@ public class ReactSurfaceTest { public void testStop() throws InterruptedException { mReactSurface.attach(mReactHost); - Task task = mReactSurface.start(); + Task task = (Task) mReactSurface.start(); task.waitForCompletion(); task = mReactSurface.stop(); @@ -147,7 +147,8 @@ public class ReactSurfaceTest { assertThat(mReactSurface.isRunning()).isFalse(); - mReactSurface.start().waitForCompletion(); + Task task = (Task) mReactSurface.start(); + task.waitForCompletion(); assertThat(mReactSurface.isRunning()).isTrue(); diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.java b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.java index cb00882bbed..b49166a4650 100644 --- a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.java +++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterApplication.java @@ -16,9 +16,16 @@ import com.facebook.react.ReactPackage; import com.facebook.react.TurboReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridgeless.ReactHost; +import com.facebook.react.bridgeless.exceptionmanager.ReactJsExceptionHandler; +import com.facebook.react.common.annotations.UnstableReactNativeAPI; +import com.facebook.react.common.mapbuffer.ReadableMapBuffer; import com.facebook.react.config.ReactFeatureFlags; +import com.facebook.react.defaults.DefaultComponentsRegistry; import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; import com.facebook.react.defaults.DefaultReactNativeHost; +import com.facebook.react.fabric.ComponentFactory; +import com.facebook.react.interfaces.ReactHostInterface; import com.facebook.react.module.model.ReactModuleInfo; import com.facebook.react.module.model.ReactModuleInfoProvider; import com.facebook.react.shell.MainReactPackage; @@ -36,6 +43,8 @@ import java.util.Map; public class RNTesterApplication extends Application implements ReactApplication { + private ReactHost mReactHost; + private final ReactNativeHost mReactNativeHost = new DefaultReactNativeHost(this) { @Override @@ -142,4 +151,34 @@ public class RNTesterApplication extends Application implements ReactApplication public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } + + @Override + @UnstableReactNativeAPI + public ReactHostInterface getReactHostInterface() { + if (mReactHost == null) { + // Create an instance of ReactHost to manager the instance of ReactInstance, + // which is similar to how we use ReactNativeHost to manager instance of ReactInstanceManager + RNTesterReactHostDelegate reactHostDelegate = + new RNTesterReactHostDelegate(getApplicationContext()); + RNTesterReactJsExceptionHandler reactJsExceptionHandler = + new RNTesterReactJsExceptionHandler(); + + ComponentFactory componentFactory = new ComponentFactory(); + DefaultComponentsRegistry.register(componentFactory); + mReactHost = + new ReactHost( + this.getApplicationContext(), + reactHostDelegate, + componentFactory, + true, + reactJsExceptionHandler, + true); + reactHostDelegate.setReactHost(mReactHost); + } + return mReactHost; + } + + public static class RNTesterReactJsExceptionHandler implements ReactJsExceptionHandler { + public void reportJsException(ReadableMapBuffer errorMap) {} + } }; diff --git a/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.java b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.java new file mode 100644 index 00000000000..42855ddfa6f --- /dev/null +++ b/packages/rn-tester/android/app/src/main/java/com/facebook/react/uiapp/RNTesterReactHostDelegate.java @@ -0,0 +1,157 @@ +/* + * 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.uiapp; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.fbreact.specs.SampleTurboModule; +import com.facebook.react.ReactPackage; +import com.facebook.react.ReactPackageTurboModuleManagerDelegate; +import com.facebook.react.TurboReactPackage; +import com.facebook.react.bridge.JSBundleLoader; +import com.facebook.react.bridge.NativeModule; +import com.facebook.react.bridge.ReactApplicationContext; +import com.facebook.react.bridgeless.BindingsInstaller; +import com.facebook.react.bridgeless.JSEngineInstance; +import com.facebook.react.bridgeless.ReactHost; +import com.facebook.react.bridgeless.ReactHostDelegate; +import com.facebook.react.bridgeless.hermes.HermesInstance; +import com.facebook.react.common.annotations.UnstableReactNativeAPI; +import com.facebook.react.config.ReactFeatureFlags; +import com.facebook.react.defaults.DefaultTurboModuleManagerDelegate; +import com.facebook.react.fabric.ReactNativeConfig; +import com.facebook.react.module.model.ReactModuleInfo; +import com.facebook.react.module.model.ReactModuleInfoProvider; +import com.facebook.react.shell.MainReactPackage; +import com.facebook.react.turbomodule.core.TurboModuleManager; +import com.facebook.react.uiapp.component.MyLegacyViewManager; +import com.facebook.react.uiapp.component.MyNativeViewManager; +import com.facebook.react.uimanager.ViewManager; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@UnstableReactNativeAPI +public class RNTesterReactHostDelegate implements ReactHostDelegate { + private final Context mContext; + private @Nullable ReactHost mReactHost; + private @Nullable List mReactPackages; + + RNTesterReactHostDelegate(Context context) { + this.mContext = context; + } + + public void setReactHost(ReactHost reactHost) { + mReactHost = reactHost; + } + + @Override + public String getJSMainModulePath() { + return "js/RNTesterApp.android"; + } + + @Override + public JSBundleLoader getJSBundleLoader() { + return JSBundleLoader.createAssetLoader(mContext, "assets://RNTesterApp.android.bundle", true); + } + + @Override + public synchronized BindingsInstaller getBindingsInstaller() { + return null; + } + + @NonNull + @Override + public ReactPackageTurboModuleManagerDelegate.Builder getTurboModuleManagerDelegateBuilder() { + return new DefaultTurboModuleManagerDelegate.Builder(); + } + + @Override + public JSEngineInstance getJSEngineInstance() { + return new HermesInstance(); + } + + @Override + public void handleInstanceException(Exception e) {} + + @Override + public ReactNativeConfig getReactNativeConfig(TurboModuleManager turboModuleManager) { + return ReactNativeConfig.DEFAULT_CONFIG; + } + + @Override + public List getReactPackages() { + if (mReactPackages == null) { + mReactPackages = + Arrays.asList( + new MainReactPackage(), + new TurboReactPackage() { + public NativeModule getModule( + final String name, final ReactApplicationContext reactContext) { + if (!ReactFeatureFlags.useTurboModules) { + return null; + } + + if (SampleTurboModule.NAME.equals(name)) { + return new SampleTurboModule(reactContext); + } + + return null; + } + + // Note: Specialized annotation processor for @ReactModule isn't configured in OSS + // yet. For now, hardcode this information, though it's not necessary for most + // modules. + public ReactModuleInfoProvider getReactModuleInfoProvider() { + return new ReactModuleInfoProvider() { + public Map getReactModuleInfos() { + final Map moduleInfos = new HashMap<>(); + if (ReactFeatureFlags.useTurboModules) { + moduleInfos.put( + SampleTurboModule.NAME, + new ReactModuleInfo( + SampleTurboModule.NAME, + "SampleTurboModule", + false, // canOverrideExistingModule + false, // needsEagerInit + true, // hasConstants + false, // isCxxModule + true // isTurboModule + )); + } + return moduleInfos; + } + }; + } + }, + new ReactPackage() { + @NonNull + @Override + public List createNativeModules( + @NonNull ReactApplicationContext reactContext) { + return Collections.emptyList(); + } + + @NonNull + @Override + public List createViewManagers( + @NonNull ReactApplicationContext reactContext) { + List viewManagers = new ArrayList<>(); + viewManagers.add(new MyNativeViewManager()); + viewManagers.add(new MyLegacyViewManager(reactContext)); + return viewManagers; + } + }); + } + return mReactPackages; + } +}