Move BridgelessDevSupportManager to .devsupport package (#46914)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/46914

The `BridgelessDevSupportManager` should have lived inside the `.devsupport` package alongside all the other
devsupport related tooling.

It was instead created inside `.runtime` causing a tight coupling with `ReactHostImpl`.
This made it impossible for Frameworks to customize it (i.e. in Expo GO) also because there was a circular
dependency between ReactHostImpl and BridgelessDevSupportManager

In this diff I'm:
1. Breaking the circular dependency by using `ReactHostDevHelper`
2. Updating all the parameters to reference `ReactHost` rather than `ReactHostImpl`
3. Moving BridgelessDevSupportManager to the `.devsupport` package.

This is breaking for users that are manually composing a `BridgelessDevSupportManager` or
that are extending the `ReactInstanceDevHelper`.

- `ReactInstanceDevHelper` has 3 new method which will have to be implemented.
- `BridgelessDevSupportManager` is now living in a different package.

Changelog:
[Android] [Breaking] - Add 3 methods to ReactInstanceDevHelper

Reviewed By: rshest

Differential Revision: D64105790

fbshipit-source-id: 13478fe1a035adb5b0dc83ebb1daad6cfb243881
This commit is contained in:
Nicola Corti
2024-10-16 11:25:26 -07:00
committed by Facebook GitHub Bot
parent ad6a2fa612
commit c867aba2f3
12 changed files with 354 additions and 192 deletions
@@ -2134,6 +2134,7 @@ public final class com/facebook/react/devsupport/DefaultDevLoadingViewImplementa
public final class com/facebook/react/devsupport/DefaultDevSupportManagerFactory : com/facebook/react/devsupport/DevSupportManagerFactory {
public fun <init> ()V
public fun create (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;)Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
public fun create (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;Z)Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
}
public class com/facebook/react/devsupport/DevServerHelper {
@@ -2173,6 +2174,7 @@ public final class com/facebook/react/devsupport/DevSettingsActivity : android/p
}
public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/facebook/react/devsupport/interfaces/DevSupportManager {
protected final field mReactInstanceDevHelper Lcom/facebook/react/devsupport/ReactInstanceDevHelper;
public fun <init> (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;)V
public fun addCustomDevOption (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/DevOptionHandler;)V
public fun createRootView (Ljava/lang/String;)Landroid/view/View;
@@ -2236,6 +2238,7 @@ public abstract interface class com/facebook/react/devsupport/DevSupportManagerB
public abstract interface class com/facebook/react/devsupport/DevSupportManagerFactory {
public abstract fun create (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;)Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
public abstract fun create (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;Z)Lcom/facebook/react/devsupport/interfaces/DevSupportManager;
}
public final class com/facebook/react/devsupport/DoubleTapReloadRecognizer {
@@ -2327,9 +2330,12 @@ public abstract interface class com/facebook/react/devsupport/ReactInstanceDevHe
public abstract fun createRootView (Ljava/lang/String;)Landroid/view/View;
public abstract fun destroyRootView (Landroid/view/View;)V
public abstract fun getCurrentActivity ()Landroid/app/Activity;
public abstract fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
public abstract fun getJavaScriptExecutorFactory ()Lcom/facebook/react/bridge/JavaScriptExecutorFactory;
public abstract fun loadBundle (Lcom/facebook/react/bridge/JSBundleLoader;)Lcom/facebook/react/interfaces/TaskInterface;
public abstract fun onJSBundleLoadedFromServer ()V
public abstract fun onReloadWithJSDebugger (Lcom/facebook/react/bridge/JavaJSExecutor$Factory;)V
public abstract fun reload (Ljava/lang/String;)V
public abstract fun toggleElementInspector ()V
}
@@ -3887,6 +3893,7 @@ public abstract class com/facebook/react/runtime/JSRuntimeFactory {
public class com/facebook/react/runtime/ReactHostImpl : com/facebook/react/ReactHost {
public fun <init> (Landroid/content/Context;Lcom/facebook/react/runtime/ReactHostDelegate;Lcom/facebook/react/fabric/ComponentFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;ZZ)V
public fun <init> (Landroid/content/Context;Lcom/facebook/react/runtime/ReactHostDelegate;Lcom/facebook/react/fabric/ComponentFactory;Ljava/util/concurrent/Executor;Ljava/util/concurrent/Executor;ZZLcom/facebook/react/devsupport/DevSupportManagerFactory;)V
public fun <init> (Landroid/content/Context;Lcom/facebook/react/runtime/ReactHostDelegate;Lcom/facebook/react/fabric/ComponentFactory;ZZ)V
public fun addBeforeDestroyListener (Lkotlin/jvm/functions/Function0;)V
public fun addReactInstanceEventListener (Lcom/facebook/react/ReactInstanceEventListener;)V
@@ -3918,7 +3925,7 @@ public class com/facebook/react/runtime/ReactHostImpl : com/facebook/react/React
public class com/facebook/react/runtime/ReactSurfaceImpl : com/facebook/react/interfaces/fabric/ReactSurface {
public fun <init> (Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)V
public fun attach (Lcom/facebook/react/runtime/ReactHostImpl;)V
public fun attach (Lcom/facebook/react/ReactHost;)V
public fun attachView (Lcom/facebook/react/runtime/ReactSurfaceView;)V
public fun clear ()V
public static fun createWithView (Landroid/content/Context;Ljava/lang/String;Landroid/os/Bundle;)Lcom/facebook/react/runtime/ReactSurfaceImpl;
@@ -92,6 +92,7 @@ import com.facebook.react.devsupport.interfaces.DevSupportManager.PausedInDebugg
import com.facebook.react.devsupport.interfaces.PackagerStatusCallback;
import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager;
import com.facebook.react.devsupport.interfaces.RedBoxHandler;
import com.facebook.react.interfaces.TaskInterface;
import com.facebook.react.internal.AndroidChoreographerProvider;
import com.facebook.react.internal.ChoreographerProvider;
import com.facebook.react.internal.featureflags.ReactNativeFeatureFlags;
@@ -369,6 +370,23 @@ public class ReactInstanceManager {
((ReactRootView) rootView).unmountReactApplication();
}
}
@Override
public void reload(String s) {
// no-op not implemented for Bridge Mode
}
@Override
public TaskInterface<Boolean> loadBundle(JSBundleLoader bundleLoader) {
// no-op not implemented for Bridge Mode
return null;
}
@Override
public ReactContext getCurrentReactContext() {
// no-op not implemented for Bridge Mode
return null;
}
};
}
@@ -0,0 +1,123 @@
/*
* 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.devsupport;
import android.content.Context;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.SurfaceDelegateFactory;
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
import com.facebook.react.devsupport.interfaces.DevLoadingViewManager;
import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback;
import com.facebook.react.devsupport.interfaces.PausedInDebuggerOverlayManager;
import com.facebook.react.devsupport.interfaces.RedBoxHandler;
import com.facebook.react.packagerconnection.RequestHandler;
import java.util.Map;
/**
* An implementation of {@link com.facebook.react.devsupport.interfaces.DevSupportManager} that
* extends the functionality in {@link DevSupportManagerBase} with some additional, more flexible
* APIs for asynchronously loading the JS bundle.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
class BridgelessDevSupportManager extends DevSupportManagerBase {
public BridgelessDevSupportManager(
Context context,
ReactInstanceDevHelper reactInstanceManagerHelper,
@Nullable String packagerPathForJSBundleName) {
this(
context.getApplicationContext(),
reactInstanceManagerHelper,
packagerPathForJSBundleName,
true /* enableOnCreate */,
null /* redBoxHandler */,
null /* devBundleDownloadListener */,
2 /* minNumShakes */,
null /* customPackagerCommandHandlers */,
null /* surfaceDelegateFactory */,
null /* devLoadingViewManager */,
null /* pausedInDebuggerOverlayManager */);
}
/**
* This constructor mirrors the same constructor we have for {@link BridgeDevSupportManager} and
* is kept for backward compatibility.
*/
public BridgelessDevSupportManager(
Context applicationContext,
ReactInstanceDevHelper reactInstanceManagerHelper,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler,
@Nullable DevBundleDownloadListener devBundleDownloadListener,
int minNumShakes,
@Nullable Map<String, RequestHandler> customPackagerCommandHandlers,
@Nullable SurfaceDelegateFactory surfaceDelegateFactory,
@Nullable DevLoadingViewManager devLoadingViewManager,
@Nullable PausedInDebuggerOverlayManager pausedInDebuggerOverlayManager) {
super(
applicationContext,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
enableOnCreate,
redBoxHandler,
devBundleDownloadListener,
minNumShakes,
customPackagerCommandHandlers,
surfaceDelegateFactory,
devLoadingViewManager,
pausedInDebuggerOverlayManager);
}
@Override
protected String getUniqueTag() {
return "Bridgeless";
}
@Override
public void loadSplitBundleFromServer(
final String bundlePath, final DevSplitBundleCallback callback) {
fetchSplitBundleAndCreateBundleLoader(
bundlePath,
new CallbackWithBundleLoader() {
@Override
public void onSuccess(final JSBundleLoader bundleLoader) {
try {
mReactInstanceDevHelper.loadBundle(bundleLoader).waitForCompletion();
String bundleURL = getDevServerHelper().getDevServerSplitBundleURL(bundlePath);
ReactContext reactContext = mReactInstanceDevHelper.getCurrentReactContext();
if (reactContext != null) {
reactContext.getJSModule(HMRClient.class).registerBundle(bundleURL);
}
callback.onSuccess();
} catch (InterruptedException e) {
throw new RuntimeException(
"[BridgelessDevSupportManager]: Got interrupted while loading bundle", e);
}
}
@Override
public void onError(String url, Throwable cause) {
callback.onError(url, cause);
}
});
}
@Override
public void handleReloadJS() {
UiThreadUtil.assertOnUiThread();
// dismiss redbox if exists
hideRedboxDialog();
mReactInstanceDevHelper.reload("BridgelessDevSupportManager.handleReloadJS()");
}
}
@@ -19,7 +19,8 @@ import com.facebook.react.packagerconnection.RequestHandler
/**
* A simple factory that creates instances of [DevSupportManager] implementations. Uses reflection
* to create BridgeDevSupportManager if it exists. This allows ProGuard to strip that class and its
* dependencies in release builds. If the class isn't found, [ ] is returned instead.
* dependencies in release builds. If the class isn't found, [PerftestDevSupportManager] is returned
* instead.
*/
public class DefaultDevSupportManagerFactory : DevSupportManagerFactory {
@@ -85,6 +86,37 @@ public class DefaultDevSupportManagerFactory : DevSupportManagerFactory {
}
}
override fun create(
applicationContext: Context,
reactInstanceManagerHelper: ReactInstanceDevHelper,
packagerPathForJSBundleName: String?,
enableOnCreate: Boolean,
redBoxHandler: RedBoxHandler?,
devBundleDownloadListener: DevBundleDownloadListener?,
minNumShakes: Int,
customPackagerCommandHandlers: MutableMap<String, RequestHandler>?,
surfaceDelegateFactory: SurfaceDelegateFactory?,
devLoadingViewManager: DevLoadingViewManager?,
pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?,
useDevSupport: Boolean
): DevSupportManager =
if (!useDevSupport) {
ReleaseDevSupportManager()
} else {
BridgelessDevSupportManager(
applicationContext,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
enableOnCreate,
redBoxHandler,
devBundleDownloadListener,
minNumShakes,
customPackagerCommandHandlers,
surfaceDelegateFactory,
devLoadingViewManager,
pausedInDebuggerOverlayManager)
}
private companion object {
private const val DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport"
private const val DEVSUPPORT_IMPL_CLASS = "BridgeDevSupportManager"
@@ -94,7 +94,7 @@ public abstract class DevSupportManagerBase implements DevSupportManager {
private final BroadcastReceiver mReloadAppBroadcastReceiver;
private final DevServerHelper mDevServerHelper;
private final LinkedHashMap<String, DevOptionHandler> mCustomDevOptions = new LinkedHashMap<>();
private final ReactInstanceDevHelper mReactInstanceDevHelper;
protected final ReactInstanceDevHelper mReactInstanceDevHelper;
private final @Nullable String mJSAppBundleName;
private final File mJSBundleDownloadedFile;
private final File mJSSplitBundlesDir;
@@ -19,6 +19,10 @@ import com.facebook.react.packagerconnection.RequestHandler;
import java.util.Map;
public interface DevSupportManagerFactory {
/**
* Factory used by the Old Architecture flow to create a {@link DevSupportManager} and a {@link
* com.facebook.react.runtime.BridgeDevSupportManager}
*/
DevSupportManager create(
Context applicationContext,
ReactInstanceDevHelper reactInstanceManagerHelper,
@@ -31,4 +35,22 @@ public interface DevSupportManagerFactory {
@Nullable SurfaceDelegateFactory surfaceDelegateFactory,
@Nullable DevLoadingViewManager devLoadingViewManager,
@Nullable PausedInDebuggerOverlayManager pausedInDebuggerOverlayManager);
/**
* Factory used by the New Architecture/Bridgeless flow to create a {@link DevSupportManager} and
* a {@link BridgelessDevSupportManager}
*/
DevSupportManager create(
Context applicationContext,
ReactInstanceDevHelper reactInstanceManagerHelper,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler,
@Nullable DevBundleDownloadListener devBundleDownloadListener,
int minNumShakes,
@Nullable Map<String, RequestHandler> customPackagerCommandHandlers,
@Nullable SurfaceDelegateFactory surfaceDelegateFactory,
@Nullable DevLoadingViewManager devLoadingViewManager,
@Nullable PausedInDebuggerOverlayManager pausedInDebuggerOverlayManager,
boolean useDevSupport);
}
@@ -10,13 +10,16 @@ package com.facebook.react.devsupport;
import android.app.Activity;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.interfaces.TaskInterface;
/**
* Interface used by {@link DevSupportManager} for accessing some fields and methods of {@link
* ReactInstanceManager} or {@link ReactHostImpl} for the purpose of displaying and handling
* developer menu options.
* ReactInstanceManager} or {@link ReactHost} for the purpose of displaying and handling developer
* menu options.
*/
public interface ReactInstanceDevHelper {
@@ -39,4 +42,11 @@ public interface ReactInstanceDevHelper {
View createRootView(String appKey);
void destroyRootView(View rootView);
void reload(String s);
TaskInterface<Boolean> loadBundle(JSBundleLoader bundleLoader);
@Nullable
ReactContext getCurrentReactContext();
}
@@ -1,159 +0,0 @@
/*
* 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.runtime;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.react.bridge.JSBundleLoader;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.devsupport.DevSupportManagerBase;
import com.facebook.react.devsupport.HMRClient;
import com.facebook.react.devsupport.ReactInstanceDevHelper;
import com.facebook.react.devsupport.interfaces.DevSplitBundleCallback;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import com.facebook.react.runtime.internal.bolts.Continuation;
import com.facebook.react.runtime.internal.bolts.Task;
/**
* An implementation of {@link com.facebook.react.devsupport.interfaces.DevSupportManager} that
* extends the functionality in {@link DevSupportManagerBase} with some additional, more flexible
* APIs for asynchronously loading the JS bundle.
*/
@Nullsafe(Nullsafe.Mode.LOCAL)
class BridgelessDevSupportManager extends DevSupportManagerBase {
private final ReactHostImpl mReactHost;
public BridgelessDevSupportManager(
ReactHostImpl host, Context context, @Nullable String packagerPathForJSBundleName) {
super(
context.getApplicationContext(),
createInstanceDevHelper(host),
packagerPathForJSBundleName,
true /* enableOnCreate */,
null /* redBoxHandler */,
null /* devBundleDownloadListener */,
2 /* minNumShakes */,
null /* customPackagerCommandHandlers */,
null /* surfaceDelegateFactory */,
null /* devLoadingViewManager */,
null /* pausedInDebuggerOverlayManager */);
mReactHost = host;
}
@Override
protected String getUniqueTag() {
return "Bridgeless";
}
@Override
public void loadSplitBundleFromServer(
final String bundlePath, final DevSplitBundleCallback callback) {
fetchSplitBundleAndCreateBundleLoader(
bundlePath,
new CallbackWithBundleLoader() {
@Override
public void onSuccess(final JSBundleLoader bundleLoader) {
mReactHost
.loadBundle(bundleLoader)
.onSuccess(
new Continuation<Boolean, Void>() {
@Override
public Void then(Task<Boolean> task) {
if (task.getResult().equals(Boolean.TRUE)) {
String bundleURL =
getDevServerHelper().getDevServerSplitBundleURL(bundlePath);
ReactContext reactContext = mReactHost.getCurrentReactContext();
if (reactContext != null) {
reactContext.getJSModule(HMRClient.class).registerBundle(bundleURL);
}
callback.onSuccess();
}
return null;
}
});
}
@Override
public void onError(String url, Throwable cause) {
callback.onError(url, cause);
}
});
}
@Override
public void handleReloadJS() {
UiThreadUtil.assertOnUiThread();
// dismiss redbox if exists
hideRedboxDialog();
mReactHost.reload("BridgelessDevSupportManager.handleReloadJS()");
}
private static ReactInstanceDevHelper createInstanceDevHelper(final ReactHostImpl reactHost) {
return new ReactInstanceDevHelper() {
@Override
public void onReloadWithJSDebugger(JavaJSExecutor.Factory proxyExecutorFactory) {
// Not implemented, only used by BridgeDevSupportManager to reload with proxy executor
}
@Override
public void onJSBundleLoadedFromServer() {
// Not implemented, only referenced by BridgeDevSupportManager
}
@Override
public void toggleElementInspector() {
ReactContext reactContext = reactHost.getCurrentReactContext();
if (reactContext != null) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("toggleElementInspector", null);
}
}
@Nullable
@Override
public Activity getCurrentActivity() {
return reactHost.getLastUsedActivity();
}
@Override
public JavaScriptExecutorFactory getJavaScriptExecutorFactory() {
throw new IllegalStateException("Not implemented for bridgeless mode");
}
@Nullable
@Override
public View createRootView(String appKey) {
Activity currentActivity = getCurrentActivity();
if (currentActivity != null && !reactHost.isSurfaceWithModuleNameAttached(appKey)) {
ReactSurfaceImpl reactSurface =
ReactSurfaceImpl.createWithView(currentActivity, appKey, new Bundle());
reactSurface.attach(reactHost);
reactSurface.start();
return reactSurface.getView();
}
return null;
}
@Override
public void destroyRootView(View rootView) {
// Not implemented
}
};
}
}
@@ -46,9 +46,10 @@ import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.queue.ReactQueueConfiguration;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.devsupport.DefaultDevSupportManagerFactory;
import com.facebook.react.devsupport.DevSupportManagerBase;
import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.InspectorFlags;
import com.facebook.react.devsupport.ReleaseDevSupportManager;
import com.facebook.react.devsupport.inspector.InspectorNetworkHelper;
import com.facebook.react.devsupport.inspector.InspectorNetworkRequestListener;
import com.facebook.react.devsupport.interfaces.BundleLoadCallback;
@@ -106,7 +107,7 @@ public class ReactHostImpl implements ReactHost {
private final Context mContext;
private final ReactHostDelegate mReactHostDelegate;
private final ComponentFactory mComponentFactory;
private final DevSupportManager mDevSupportManager;
private DevSupportManager mDevSupportManager;
private final Executor mBGExecutor;
private final Executor mUIExecutor;
private final Set<ReactSurfaceImpl> mAttachedSurfaces = new HashSet<>();
@@ -165,6 +166,26 @@ public class ReactHostImpl implements ReactHost {
Executor uiExecutor,
boolean allowPackagerServerAccess,
boolean useDevSupport) {
this(
context,
delegate,
componentFactory,
bgExecutor,
uiExecutor,
allowPackagerServerAccess,
useDevSupport,
null);
}
public ReactHostImpl(
Context context,
ReactHostDelegate delegate,
ComponentFactory componentFactory,
Executor bgExecutor,
Executor uiExecutor,
boolean allowPackagerServerAccess,
boolean useDevSupport,
@Nullable DevSupportManagerFactory devSupportManagerFactory) {
mContext = context;
mReactHostDelegate = delegate;
mComponentFactory = componentFactory;
@@ -173,14 +194,24 @@ public class ReactHostImpl implements ReactHost {
mMemoryPressureRouter = new MemoryPressureRouter(context);
mAllowPackagerServerAccess = allowPackagerServerAccess;
mUseDevSupport = useDevSupport;
if (mUseDevSupport) {
mDevSupportManager =
new BridgelessDevSupportManager(
ReactHostImpl.this, mContext, mReactHostDelegate.getJsMainModulePath());
} else {
mDevSupportManager = new ReleaseDevSupportManager();
if (devSupportManagerFactory == null) {
devSupportManagerFactory = new DefaultDevSupportManagerFactory();
}
mDevSupportManager =
devSupportManagerFactory.create(
/* applicationContext */ context.getApplicationContext(),
/* reactInstanceManagerHelper */ new ReactHostImplDevHelper(ReactHostImpl.this),
/* packagerPathForJSBundleName */ mReactHostDelegate.getJsMainModulePath(),
/* enableOnCreate */ true,
/* redBoxHandler */ null,
/* devBundleDownloadListener */ null,
/* minNumShakes */ 2,
/* customPackagerCommandHandlers */ null,
/* surfaceDelegateFactory */ null,
/* devLoadingViewManager */ null,
/* pausedInDebuggerOverlayManager */ null,
mUseDevSupport);
}
@Override
@@ -0,0 +1,75 @@
/*
* 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.runtime
import android.app.Activity
import android.os.Bundle
import android.view.View
import com.facebook.react.bridge.JSBundleLoader
import com.facebook.react.bridge.JavaJSExecutor
import com.facebook.react.bridge.JavaScriptExecutorFactory
import com.facebook.react.bridge.ReactContext
import com.facebook.react.devsupport.ReactInstanceDevHelper
import com.facebook.react.interfaces.TaskInterface
import com.facebook.react.modules.core.DeviceEventManagerModule
/**
* Implementation of [ReactInstanceDevHelper] for [ReactHostImpl].
*
* This allows [BridgelessDevSupportHelper] and other classes inside the .devsupport package to
* communicate with the Bridgeless infrastructure without exposing public APIs.
*/
internal class ReactHostImplDevHelper(private val delegate: ReactHostImpl) :
ReactInstanceDevHelper {
override fun onReloadWithJSDebugger(proxyExecutorFactory: JavaJSExecutor.Factory) {
// Not implemented, only used by BridgeDevSupportManager to reload with proxy executor
}
override fun onJSBundleLoadedFromServer() {
// Not implemented, only referenced by BridgeDevSupportManager
}
override fun toggleElementInspector() {
val reactContext = delegate.currentReactContext
reactContext
?.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
?.emit("toggleElementInspector", null)
}
override fun getCurrentActivity(): Activity? = delegate.lastUsedActivity
override fun getJavaScriptExecutorFactory(): JavaScriptExecutorFactory {
throw IllegalStateException("Not implemented for bridgeless mode")
}
override fun createRootView(appKey: String): View? {
val currentActivity = currentActivity
if (currentActivity != null && !delegate.isSurfaceWithModuleNameAttached(appKey)) {
val reactSurface = ReactSurfaceImpl.createWithView(currentActivity, appKey, Bundle())
reactSurface.attach(delegate)
reactSurface.start()
return reactSurface.view
}
return null
}
override fun destroyRootView(rootView: View) {
// Not implemented, only referenced by BridgeDevSupportManager
}
override fun reload(s: String) {
delegate.reload(s)
}
override fun loadBundle(bundleLoader: JSBundleLoader): TaskInterface<Boolean> =
delegate.loadBundle(bundleLoader)
override fun getCurrentReactContext(): ReactContext? = delegate.currentReactContext
}
@@ -16,6 +16,7 @@ import android.view.ViewGroup;
import androidx.annotation.UiThread;
import com.facebook.infer.annotation.Nullsafe;
import com.facebook.infer.annotation.ThreadSafe;
import com.facebook.react.ReactHost;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.NativeMap;
import com.facebook.react.bridge.UiThreadUtil;
@@ -93,9 +94,12 @@ public class ReactSurfaceImpl implements ReactSurface {
*
* @param host The ReactHost to attach.
*/
public void attach(ReactHostImpl host) {
if (!mReactHost.compareAndSet(null, host)) {
public void attach(ReactHost host) {
if (host instanceof ReactHostImpl && !mReactHost.compareAndSet(null, (ReactHostImpl) host)) {
throw new IllegalStateException("This surface is already attached to a host!");
} else if (!(host instanceof ReactHostImpl)) {
throw new IllegalArgumentException(
"ReactSurfaceImpl.attach can only attach to ReactHostImpl.");
}
}
@@ -16,7 +16,6 @@ import com.facebook.react.bridge.UIManager
import com.facebook.react.common.LifecycleState
import com.facebook.react.common.annotations.UnstableReactNativeAPI
import com.facebook.react.devsupport.ReleaseDevSupportManager
import com.facebook.react.devsupport.interfaces.PackagerStatusCallback
import com.facebook.react.fabric.ComponentFactory
import com.facebook.react.interfaces.TaskInterface
import com.facebook.react.runtime.internal.bolts.TaskCompletionSource
@@ -58,7 +57,6 @@ class ReactHostTest {
private lateinit var mockedReactInstanceCtor: MockedConstruction<ReactInstance>
private lateinit var mockedReactHostInspectorTargetCtor:
MockedConstruction<ReactHostInspectorTarget>
private lateinit var mockedDevSupportManagerCtor: MockedConstruction<BridgelessDevSupportManager>
private lateinit var mockedBridgelessReactContextCtor: MockedConstruction<BridgelessReactContext>
private lateinit var mockedMemoryPressureRouterCtor: MockedConstruction<MemoryPressureRouter>
private lateinit var mockedTaskCompletionSourceCtor: MockedConstruction<TaskCompletionSource<*>>
@@ -77,7 +75,6 @@ class ReactHostTest {
mockedReactInstanceCtor = Mockito.mockConstruction(ReactInstance::class.java)
mockedReactHostInspectorTargetCtor =
Mockito.mockConstruction(ReactHostInspectorTarget::class.java)
mockedDevSupportManagerCtor = Mockito.mockConstruction(BridgelessDevSupportManager::class.java)
mockedBridgelessReactContextCtor = Mockito.mockConstruction(BridgelessReactContext::class.java)
mockedMemoryPressureRouterCtor = Mockito.mockConstruction(MemoryPressureRouter::class.java)
@@ -95,7 +92,6 @@ class ReactHostTest {
fun tearDown() {
mockedReactInstanceCtor.close()
mockedReactHostInspectorTargetCtor.close()
mockedDevSupportManagerCtor.close()
mockedBridgelessReactContextCtor.close()
mockedMemoryPressureRouterCtor.close()
mockedTaskCompletionSourceCtor.close()
@@ -114,26 +110,29 @@ class ReactHostTest {
}
@Test
fun testGetDevSupportManager() {
fun testGetDevSupportManager_withRelease() {
// BridgelessDevSupportManager is created only for debug
// we check if it was instantiated or if ReleaseDevSupportManager was created (for release).
if (mockedDevSupportManagerCtor.constructed().isNotEmpty()) {
val devSupportManager = mockedDevSupportManagerCtor.constructed().first()
Assertions.assertThat(reactHost.devSupportManager).isEqualTo(devSupportManager)
} else {
Assertions.assertThat(reactHost.devSupportManager)
.isInstanceOf(ReleaseDevSupportManager::class.java)
}
Assertions.assertThat(reactHost.devSupportManager)
.isInstanceOf(ReleaseDevSupportManager::class.java)
}
@Test
fun testGetDevSupportManager_withDebug() {
reactHost =
ReactHostImpl(
activityController.get().application,
reactHostDelegate,
componentFactory,
false,
true /* useDevSupport */)
Assertions.assertThat(reactHost.devSupportManager)
.isNotInstanceOf(ReleaseDevSupportManager::class.java)
}
@Test
@Ignore("Test is currently failing in OSS and needs to be looked into")
fun testStart() {
val devSupportManager = mockedDevSupportManagerCtor.constructed().first()
Mockito.doNothing()
.`when`(devSupportManager)
.isPackagerRunning(ArgumentMatchers.any(PackagerStatusCallback::class.java))
Assertions.assertThat(reactHost.isInstanceInitialized).isFalse()
waitForTaskUIThread(reactHost.start())
Assertions.assertThat(reactHost.isInstanceInitialized).isTrue()