Skip to content

Commit

Permalink
Support custom DevSupportManager (#31841)
Browse files Browse the repository at this point in the history
Summary:
to open possibilities for some DX enhancement, the pr introduces `DevSupportManagerFactory` customization. applications could implement a different DevSupportManager in ReactNativeHost.

## Changelog

[Internal] [Added] - Support custom DevSupportManager

Pull Request resolved: #31841

Test Plan: this pr just introduces some new interfaces and should not break existing functionalities.

Reviewed By: RSNara

Differential Revision: D30878134

Pulled By: yungsters

fbshipit-source-id: ccdf798caa322b07a876da8312b97002da057388
  • Loading branch information
Kudo authored and facebook-github-bot committed Sep 22, 2021
1 parent 8802189 commit 369b28c
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 74 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ public static ReactInstanceManagerBuilder builder() {
@Nullable String jsMainModulePath,
List<ReactPackage> packages,
boolean useDeveloperSupport,
DevSupportManagerFactory devSupportManagerFactory,
boolean requireActivity,
@Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener,
LifecycleState initialLifecycleState,
Expand Down Expand Up @@ -250,7 +251,7 @@ public static ReactInstanceManagerBuilder builder() {
Systrace.beginSection(
Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstanceManager.initDevSupportManager");
mDevSupportManager =
DevSupportManagerFactory.create(
devSupportManagerFactory.create(
applicationContext,
createDevHelperInterface(),
mJSMainModulePath,
Expand Down Expand Up @@ -1225,7 +1226,8 @@ private void attachRootViewToInstance(final ReactRoot reactRoot) {
// If we can't get a UIManager something has probably gone horribly wrong
if (uiManager == null) {
throw new IllegalStateException(
"Unable to attach a rootView to ReactInstance when UIManager is not properly initialized.");
"Unable to attach a rootView to ReactInstance when UIManager is not properly"
+ " initialized.");
}

@Nullable Bundle initialProperties = reactRoot.getAppProperties();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.DefaultDevSupportManagerFactory;
import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.RedBoxHandler;
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
Expand All @@ -45,6 +47,7 @@ public class ReactInstanceManagerBuilder {
private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener;
private @Nullable Application mApplication;
private boolean mUseDeveloperSupport;
private @Nullable DevSupportManagerFactory mDevSupportManagerFactory;
private boolean mRequireActivity;
private @Nullable LifecycleState mInitialLifecycleState;
private @Nullable UIImplementationProvider mUIImplementationProvider;
Expand Down Expand Up @@ -172,6 +175,16 @@ public ReactInstanceManagerBuilder setUseDeveloperSupport(boolean useDeveloperSu
return this;
}

/**
* Set the custom {@link DevSupportManagerFactory}. If not set, will use {@link
* DefaultDevSupportManagerFactory}.
*/
public ReactInstanceManagerBuilder setDevSupportManagerFactory(
final DevSupportManagerFactory devSupportManagerFactory) {
mDevSupportManagerFactory = devSupportManagerFactory;
return this;
}

/**
* When {@code false}, indicates that correct usage of React Native will NOT involve an Activity.
* For the vast majority of Android apps in the ecosystem, this will not need to change. Unless
Expand Down Expand Up @@ -294,6 +307,9 @@ public ReactInstanceManager build() {
mJSMainModulePath,
mPackages,
mUseDeveloperSupport,
mDevSupportManagerFactory == null
? new DefaultDevSupportManagerFactory()
: mDevSupportManagerFactory,
mRequireActivity,
mBridgeIdleDebugListener,
Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.bridge.ReactMarkerConstants;
import com.facebook.react.common.LifecycleState;
import com.facebook.react.devsupport.DevSupportManagerFactory;
import com.facebook.react.devsupport.RedBoxHandler;
import com.facebook.react.uimanager.UIImplementationProvider;
import java.util.List;
Expand Down Expand Up @@ -68,6 +69,7 @@ protected ReactInstanceManager createReactInstanceManager() {
.setApplication(mApplication)
.setJSMainModulePath(getJSMainModuleName())
.setUseDeveloperSupport(getUseDeveloperSupport())
.setDevSupportManagerFactory(getDevSupportManagerFactory())
.setRequireActivity(getShouldRequireActivity())
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
Expand Down Expand Up @@ -160,6 +162,11 @@ protected String getJSMainModuleName() {
/** Returns whether dev mode should be enabled. This enables e.g. the dev menu. */
public abstract boolean getUseDeveloperSupport();

/** Get the {@link DevSupportManagerFactory}. Override this to use a custom dev support manager */
protected @Nullable DevSupportManagerFactory getDevSupportManagerFactory() {
return null;
}

/**
* Returns a list of {@link ReactPackage} used by the app. You'll most likely want to return at
* least the {@code MainReactPackage}. If your app uses additional views or modules besides the
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) Facebook, Inc. and its 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.react.devsupport.interfaces.DevBundleDownloadListener;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.packagerconnection.RequestHandler;
import java.lang.reflect.Constructor;
import java.util.Map;

/**
* A simple factory that creates instances of {@link 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, {@link
* DisabledDevSupportManager} is returned instead.
*/
public class DefaultDevSupportManagerFactory implements DevSupportManagerFactory {

private static final String DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport";
private static final String DEVSUPPORT_IMPL_CLASS = "BridgeDevSupportManager";

public DevSupportManager create(
Context applicationContext,
ReactInstanceDevHelper reactInstanceDevHelper,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
int minNumShakes) {

return create(
applicationContext,
reactInstanceDevHelper,
packagerPathForJSBundleName,
enableOnCreate,
null,
null,
minNumShakes,
null);
}

@Override
public DevSupportManager create(
Context applicationContext,
ReactInstanceDevHelper reactInstanceManagerHelper,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler,
@Nullable DevBundleDownloadListener devBundleDownloadListener,
int minNumShakes,
@Nullable Map<String, RequestHandler> customPackagerCommandHandlers) {
if (!enableOnCreate) {
return new DisabledDevSupportManager();
}
try {
// ProGuard is surprisingly smart in this case and will keep a class if it detects a call to
// Class.forName() with a static string. So instead we generate a quasi-dynamic string to
// confuse it.
String className =
new StringBuilder(DEVSUPPORT_IMPL_PACKAGE)
.append(".")
.append(DEVSUPPORT_IMPL_CLASS)
.toString();
Class<?> devSupportManagerClass = Class.forName(className);
Constructor constructor =
devSupportManagerClass.getConstructor(
Context.class,
ReactInstanceDevHelper.class,
String.class,
boolean.class,
RedBoxHandler.class,
DevBundleDownloadListener.class,
int.class,
Map.class);
return (DevSupportManager)
constructor.newInstance(
applicationContext,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
true,
redBoxHandler,
devBundleDownloadListener,
minNumShakes,
customPackagerCommandHandlers);
} catch (Exception e) {
throw new RuntimeException(
"Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found"
+ " or could not be created",
e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,85 +12,16 @@
import com.facebook.react.devsupport.interfaces.DevBundleDownloadListener;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.packagerconnection.RequestHandler;
import java.lang.reflect.Constructor;
import java.util.Map;

/**
* A simple factory that creates instances of {@link 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, {@link
* DisabledDevSupportManager} is returned instead.
*/
public class DevSupportManagerFactory {

private static final String DEVSUPPORT_IMPL_PACKAGE = "com.facebook.react.devsupport";
private static final String DEVSUPPORT_IMPL_CLASS = "BridgeDevSupportManager";

public static DevSupportManager create(
Context applicationContext,
ReactInstanceDevHelper reactInstanceDevHelper,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
int minNumShakes) {

return create(
applicationContext,
reactInstanceDevHelper,
packagerPathForJSBundleName,
enableOnCreate,
null,
null,
minNumShakes,
null);
}

public static DevSupportManager create(
public interface DevSupportManagerFactory {
DevSupportManager create(
Context applicationContext,
ReactInstanceDevHelper reactInstanceManagerHelper,
@Nullable String packagerPathForJSBundleName,
boolean enableOnCreate,
@Nullable RedBoxHandler redBoxHandler,
@Nullable DevBundleDownloadListener devBundleDownloadListener,
int minNumShakes,
@Nullable Map<String, RequestHandler> customPackagerCommandHandlers) {
if (!enableOnCreate) {
return new DisabledDevSupportManager();
}
try {
// ProGuard is surprisingly smart in this case and will keep a class if it detects a call to
// Class.forName() with a static string. So instead we generate a quasi-dynamic string to
// confuse it.
String className =
new StringBuilder(DEVSUPPORT_IMPL_PACKAGE)
.append(".")
.append(DEVSUPPORT_IMPL_CLASS)
.toString();
Class<?> devSupportManagerClass = Class.forName(className);
Constructor constructor =
devSupportManagerClass.getConstructor(
Context.class,
ReactInstanceDevHelper.class,
String.class,
boolean.class,
RedBoxHandler.class,
DevBundleDownloadListener.class,
int.class,
Map.class);
return (DevSupportManager)
constructor.newInstance(
applicationContext,
reactInstanceManagerHelper,
packagerPathForJSBundleName,
true,
redBoxHandler,
devBundleDownloadListener,
minNumShakes,
customPackagerCommandHandlers);
} catch (Exception e) {
throw new RuntimeException(
"Requested enabled DevSupportManager, but BridgeDevSupportManager class was not found"
+ " or could not be created",
e);
}
}
@Nullable Map<String, RequestHandler> customPackagerCommandHandlers);
}

0 comments on commit 369b28c

Please sign in to comment.