From e76a7df5b54fdb9782699bf67045662d031f6f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Estev=C3=A3o=20Lucas?= Date: Wed, 17 Apr 2019 10:56:49 -0700 Subject: [PATCH] - Add DevSetting native module (making it cross-platform) (#24441) Summary: React Native has a `NativeModule` to manipulate programmatically the dev menu options (live reload, hot reload, remote debugging, etc), called [`DevSettings`](https://github.com/facebook/react-native/blob/master/React/Modules/RCTDevSettings.mm#L120). However this module is only available for iOS. This PR brings the same `DevSettings` for Android, making it a cross-platform NativeModule. Motivation: Right now if your app needs to programmatically reload RN, one option is to install [`react-native-restart`](https://www.npmjs.com/package/react-native-restart). It's a tiny dependency, but it's annoying to have to install it, while the code to do so is inside RN codebase. According to NPM, react-native-restart has ~12k weekly downloads, shows it's a recurring feature for many apps (my case). Thus making `NativeModules.DevSettings` is a small increment in the codebase, just exposing the dev menu methods, to improve the Development Experience [Android] [Added] - Add DevSetting native module (making it cross-platform) With expection of `setIsShakeToShowDevMenuEnabled`, the following methods will be available for both platforms: * reload * setHotLoadingEnabled * setIsDebuggingRemotely * setIsShakeToShowDevMenuEnabled * setLiveReloadEnabled * setProfilingEnabled * toggleElementInspector Pull Request resolved: https://github.com/facebook/react-native/pull/24441 Differential Revision: D14932751 Pulled By: cpojer fbshipit-source-id: 465e6a89c3beb5fd1ea22e80ea02e9438f596a09 --- .../facebook/react/CoreModulesPackage.java | 5 ++ .../devsupport/DevSupportManagerImpl.java | 84 +++++++++++++++++++ .../devsupport/DisabledDevSupportManager.java | 27 +++++- .../interfaces/DevSupportManager.java | 5 ++ .../com/facebook/react/modules/debug/BUCK | 1 + .../modules/debug/DevSettingsModule.java | 72 ++++++++++++++++ 6 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java index 39151e258ee..074269c2d0e 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java +++ b/ReactAndroid/src/main/java/com/facebook/react/CoreModulesPackage.java @@ -23,6 +23,7 @@ import com.facebook.react.modules.core.ExceptionsManagerModule; import com.facebook.react.modules.core.Timing; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.modules.core.HeadlessJsTaskSupportModule; +import com.facebook.react.modules.debug.DevSettingsModule; import com.facebook.react.modules.debug.SourceCodeModule; import com.facebook.react.modules.deviceinfo.DeviceInfoModule; import com.facebook.react.modules.systeminfo.AndroidInfoModule; @@ -49,6 +50,7 @@ import static com.facebook.react.bridge.ReactMarkerConstants.*; AndroidInfoModule.class, DeviceEventManagerModule.class, DeviceInfoModule.class, + DevSettingsModule.class, ExceptionsManagerModule.class, HeadlessJsTaskSupportModule.class, SourceCodeModule.class, @@ -93,6 +95,7 @@ import static com.facebook.react.bridge.ReactMarkerConstants.*; AndroidInfoModule.class, DeviceEventManagerModule.class, DeviceInfoModule.class, + DevSettingsModule.class, ExceptionsManagerModule.class, HeadlessJsTaskSupportModule.class, SourceCodeModule.class, @@ -138,6 +141,8 @@ import static com.facebook.react.bridge.ReactMarkerConstants.*; return new AndroidInfoModule(reactContext); case DeviceEventManagerModule.NAME: return new DeviceEventManagerModule(reactContext, mHardwareBackBtnHandler); + case DevSettingsModule.NAME: + return new DevSettingsModule(mReactInstanceManager.getDevSupportManager()); case ExceptionsManagerModule.NAME: return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager()); case HeadlessJsTaskSupportModule.NAME: diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java index b3643e77b74..7443b4e3d2b 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerImpl.java @@ -1040,6 +1040,90 @@ public class DevSupportManagerImpl implements mDevServerHelper.closeInspectorConnection(); } + @Override + public void setHotModuleReplacementEnabled(final boolean isHotModuleReplacementEnabled) { + if (!mIsDevSupportEnabled) { + return; + } + + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + mDevSettings.setHotModuleReplacementEnabled(isHotModuleReplacementEnabled); + handleReloadJS(); + } + } + ); + } + + @Override + public void setRemoteJSDebugEnabled(final boolean isRemoteJSDebugEnabled) { + if (!mIsDevSupportEnabled) { + return; + } + + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + mDevSettings.setRemoteJSDebugEnabled(isRemoteJSDebugEnabled); + handleReloadJS(); + } + } + ); + } + + @Override + public void setReloadOnJSChangeEnabled(final boolean isReloadOnJSChangeEnabled) { + if (!mIsDevSupportEnabled) { + return; + } + + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + mDevSettings.setReloadOnJSChangeEnabled(isReloadOnJSChangeEnabled); + handleReloadJS(); + } + } + ); + } + + @Override + public void setFpsDebugEnabled(final boolean isFpsDebugEnabled) { + if (!mIsDevSupportEnabled) { + return; + } + + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + mDevSettings.setFpsDebugEnabled(isFpsDebugEnabled); + } + } + ); + } + + @Override + public void toggleElementInspector() { + if (!mIsDevSupportEnabled) { + return; + } + + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + mDevSettings.setElementInspectorEnabled(!mDevSettings.isElementInspectorEnabled()); + mReactInstanceManagerHelper.toggleElementInspector(); + } + } + ); + } + private void reload() { UiThreadUtil.assertOnUiThread(); diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java index daa11f0ee6b..f57c32ba8ff 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/DisabledDevSupportManager.java @@ -78,6 +78,31 @@ public class DisabledDevSupportManager implements DevSupportManager { } + @Override + public void setHotModuleReplacementEnabled(boolean isHotModuleReplacementEnabled) { + + } + + @Override + public void setRemoteJSDebugEnabled(boolean isRemoteJSDebugEnabled) { + + } + + @Override + public void setReloadOnJSChangeEnabled(boolean isReloadOnJSChangeEnabled) { + + } + + @Override + public void setFpsDebugEnabled(boolean isFpsDebugEnabled) { + + } + + @Override + public void toggleElementInspector() { + + } + @Override public boolean getDevSupportEnabled() { return false; @@ -162,7 +187,7 @@ public class DisabledDevSupportManager implements DevSupportManager { @Override public void registerErrorCustomizer(ErrorCustomizer errorCustomizer) { - + } @Override diff --git a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java index fbd777ea520..6fb3775da7a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.java @@ -45,6 +45,11 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler { void handleReloadJS(); void reloadJSFromServer(final String bundleURL); void isPackagerRunning(PackagerStatusCallback callback); + void setHotModuleReplacementEnabled(final boolean isHotModuleReplacementEnabled); + void setRemoteJSDebugEnabled(final boolean isRemoteJSDebugEnabled); + void setReloadOnJSChangeEnabled(final boolean isReloadOnJSChangeEnabled); + void setFpsDebugEnabled(final boolean isFpsDebugEnabled); + void toggleElementInspector(); @Nullable File downloadBundleResourceFromUrlSync( final String resourceURL, final File outputFile); diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK index fdead3b9759..36fde2d1993 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/BUCK @@ -12,6 +12,7 @@ rn_android_library( react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"), + react_native_target("java/com/facebook/react/devsupport:interfaces"), react_native_target("java/com/facebook/react/module/annotations:annotations"), react_native_target("java/com/facebook/react/modules/core:core"), react_native_target("java/com/facebook/react/modules/debug:interfaces"), diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.java new file mode 100644 index 00000000000..3f067c62b0c --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/debug/DevSettingsModule.java @@ -0,0 +1,72 @@ +/** + * 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.modules.debug; + +import com.facebook.react.bridge.BaseJavaModule; +import com.facebook.react.bridge.ReactContextBaseJavaModule; +import com.facebook.react.bridge.ReactMethod; +import com.facebook.react.devsupport.interfaces.DevSupportManager; +import com.facebook.react.module.annotations.ReactModule; +import com.facebook.react.bridge.UiThreadUtil; + +/** + * Module that exposes the URL to the source code map (used for exception stack trace parsing) to JS + */ +@ReactModule(name = DevSettingsModule.NAME) +public class DevSettingsModule extends BaseJavaModule { + + public static final String NAME = "DevSettings"; + + private final DevSupportManager mDevSupportManager; + + public DevSettingsModule(DevSupportManager devSupportManager) { + mDevSupportManager = devSupportManager; + } + + @Override + public String getName() { + return NAME; + } + + @ReactMethod + public void reload() { + if (mDevSupportManager.getDevSupportEnabled()) { + UiThreadUtil.runOnUiThread(new Runnable() { + @Override + public void run() { + mDevSupportManager.handleReloadJS(); + } + }); + } + } + + @ReactMethod + public void setHotLoadingEnabled(boolean isHotLoadingEnabled) { + mDevSupportManager.setHotModuleReplacementEnabled(isHotLoadingEnabled); + } + + @ReactMethod + public void setIsDebuggingRemotely(boolean isDebugginRemotelyEnabled) { + mDevSupportManager.setRemoteJSDebugEnabled(isDebugginRemotelyEnabled); + } + + @ReactMethod + public void setLiveReloadEnabled(boolean isLiveReloadEnabled) { + mDevSupportManager.setReloadOnJSChangeEnabled(isLiveReloadEnabled); + } + + @ReactMethod + public void setProfilingEnabled(boolean isProfilingEnabled) { + mDevSupportManager.setFpsDebugEnabled(isProfilingEnabled); + } + + @ReactMethod + public void toggleElementInspector() { + mDevSupportManager.toggleElementInspector(); + } +}