Skip to content

Commit

Permalink
Add NativeLogBox module on Android
Browse files Browse the repository at this point in the history
Summary:
This diff adds a NativeLogBox module implementation on Android to manage rendering LogBox the way we render RedBox, except rendering a React Native component instead of a native view.

The strategy here is:
- initialize: will create a React rootview and render it.
- show: will add the rootview to a dialog and display the dialog.
- hide: will remove the rootview from it's parent, dismiss the dialog, and release the reference to the activity to prevent leaks.

Most of this is copied from the way RedBox works, the difference here is that we eagerly initialize the rootview with the `initialize` function so that it's warm by the time the dialog needs to render.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D18768517

fbshipit-source-id: 2510d6c186ccf73153ef9372c736c9e0c71bbc7d
  • Loading branch information
rickhanlonii authored and facebook-github-bot committed Dec 10, 2019
1 parent 6b22a4e commit e272089
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactMarker;
import com.facebook.react.devsupport.LogBoxModule;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.module.annotations.ReactModuleList;
import com.facebook.react.module.model.ReactModuleInfo;
Expand Down Expand Up @@ -49,6 +50,7 @@
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
Expand Down Expand Up @@ -94,6 +96,7 @@ public ReactModuleInfoProvider getReactModuleInfoProvider() {
DeviceInfoModule.class,
DevSettingsModule.class,
ExceptionsManagerModule.class,
LogBoxModule.class,
HeadlessJsTaskSupportModule.class,
SourceCodeModule.class,
TimingModule.class,
Expand Down Expand Up @@ -142,6 +145,8 @@ public NativeModule getModule(String name, ReactApplicationContext reactContext)
return new DevSettingsModule(reactContext, mReactInstanceManager.getDevSupportManager());
case ExceptionsManagerModule.NAME:
return new ExceptionsManagerModule(mReactInstanceManager.getDevSupportManager());
case LogBoxModule.NAME:
return new LogBoxModule(reactContext, mReactInstanceManager.getDevSupportManager());
case HeadlessJsTaskSupportModule.NAME:
return new HeadlessJsTaskSupportModule(reactContext);
case SourceCodeModule.NAME:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,27 @@ public void toggleElementInspector() {
public JavaScriptExecutorFactory getJavaScriptExecutorFactory() {
return ReactInstanceManager.this.getJSExecutorFactory();
}

@Override
public @Nullable View createRootView(String appKey) {
Activity currentActivity = getCurrentActivity();
if (currentActivity != null) {
ReactRootView rootView = new ReactRootView(currentActivity);

rootView.startReactApplication(ReactInstanceManager.this, appKey, null);

return rootView;
}

return null;
}

@Override
public void destroyRootView(View rootView) {
if (rootView instanceof ReactRootView) {
((ReactRootView) rootView).unmountReactApplication();
}
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import android.content.pm.PackageManager;
import android.hardware.SensorManager;
import android.util.Pair;
import android.view.View;
import android.widget.EditText;
import android.widget.Toast;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -377,6 +378,14 @@ public void hideRedboxDialog() {
}
}

public @Nullable View createRootView(String appKey) {
return mReactInstanceManagerHelper.createRootView(appKey);
}

public void destroyRootView(View rootView) {
mReactInstanceManagerHelper.destroyRootView(rootView);
}

private void hideDevOptionsDialog() {
if (mDevOptionsDialog != null) {
mDevOptionsDialog.dismiss();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react.devsupport;

import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.DefaultNativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
Expand Down Expand Up @@ -40,6 +41,14 @@ public void addCustomDevOption(String optionName, DevOptionHandler optionHandler
@Override
public void showNewJSError(String message, ReadableArray details, int errorCookie) {}

@Override
public @Nullable View createRootView(String appKey) {
return null;
}

@Override
public void destroyRootView(View rootView) {}

@Override
public void updateJSError(String message, ReadableArray details, int errorCookie) {}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.app.Activity;
import android.app.Dialog;
import android.view.View;
import android.view.Window;
import com.facebook.react.R;

/** Dialog for displaying JS errors in LogBox. */
public class LogBoxDialog extends Dialog {
public LogBoxDialog(Activity context, View reactRootView) {
super(context, R.style.Theme_Catalyst_LogBox);

requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(reactRootView);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* 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.app.Activity;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.Nullable;
import com.facebook.common.logging.FLog;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.devsupport.interfaces.DevSupportManager;
import com.facebook.react.module.annotations.ReactModule;

@ReactModule(name = LogBoxModule.NAME)
public class LogBoxModule extends ReactContextBaseJavaModule {

public static final String NAME = "LogBox";

private final DevSupportManager mDevSupportManager;
private @Nullable View mReactRootView;
private @Nullable LogBoxDialog mLogBoxDialog;

public LogBoxModule(ReactApplicationContext reactContext, DevSupportManager devSupportManager) {
super(reactContext);

mDevSupportManager = devSupportManager;
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mReactRootView == null) {
mReactRootView = mDevSupportManager.createRootView("LogBox");
if (mReactRootView == null) {
FLog.e(
ReactConstants.TAG,
"Unable to launch logbox because react was unable to create the root view");
}
}
}
});
}

@Override
public String getName() {
return NAME;
}

@ReactMethod
public void show() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mLogBoxDialog == null) {
Activity context = getCurrentActivity();
if (context == null || context.isFinishing()) {
FLog.e(
ReactConstants.TAG,
"Unable to launch logbox because react activity "
+ "is not available, here is the error that logbox would've displayed: ");
return;
}
mLogBoxDialog = new LogBoxDialog(context, mReactRootView);
mLogBoxDialog.setCancelable(false);
mLogBoxDialog.show();
}
}
});
}

@ReactMethod
public void hide() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mLogBoxDialog != null) {
if (mReactRootView.getParent() != null) {
((ViewGroup) mReactRootView.getParent()).removeView(mReactRootView);
}
mLogBoxDialog.dismiss();
mLogBoxDialog = null;
}
}
});
}

@Override
public void onCatalystInstanceDestroy() {
UiThreadUtil.runOnUiThread(
new Runnable() {
@Override
public void run() {
if (mReactRootView != null) {
mDevSupportManager.destroyRootView(mReactRootView);
mReactRootView = null;
}
}
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package com.facebook.react.devsupport;

import android.app.Activity;
import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.JavaJSExecutor;
import com.facebook.react.bridge.JavaScriptExecutorFactory;
Expand All @@ -32,4 +33,9 @@ public interface ReactInstanceManagerDevHelper {
Activity getCurrentActivity();

JavaScriptExecutorFactory getJavaScriptExecutorFactory();

@Nullable
View createRootView(String appKey);

void destroyRootView(View rootView);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

package com.facebook.react.devsupport.interfaces;

import android.view.View;
import androidx.annotation.Nullable;
import com.facebook.react.bridge.NativeModuleCallExceptionHandler;
import com.facebook.react.bridge.ReactContext;
Expand All @@ -25,6 +26,11 @@ public interface DevSupportManager extends NativeModuleCallExceptionHandler {

void addCustomDevOption(String optionName, DevOptionHandler optionHandler);

@Nullable
View createRootView(String appKey);

void destroyRootView(View rootView);

void showNewJSError(String message, ReadableArray details, int errorCookie);

void updateJSError(final String message, final ReadableArray details, final int errorCookie);
Expand Down
1 change: 1 addition & 0 deletions ReactAndroid/src/main/res/devsupport/values/colors.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="catalyst_redbox_background">#eecc0000</color>
<color name="catalyst_logbox_background">#ffffffff</color>
</resources>
12 changes: 12 additions & 0 deletions ReactAndroid/src/main/res/devsupport/values/styles.xml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,22 @@
<item name="android:outAnimation">@android:anim/fade_out</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="Theme.Catalyst.LogBox">
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowAnimationStyle">@style/Animation.Catalyst.LogBox</item>
<item name="android:inAnimation">@android:anim/fade_in</item>
<item name="android:outAnimation">@android:anim/fade_out</item>
<item name="android:textColor">@android:color/white</item>
</style>
<style name="Animation.Catalyst.RedBox" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/catalyst_push_up_in</item>
<item name="android:windowExitAnimation">@anim/catalyst_push_up_out</item>
</style>
<style name="Animation.Catalyst.LogBox" parent="@android:style/Animation">
<item name="android:windowEnterAnimation">@anim/catalyst_push_up_in</item>
<item name="android:windowExitAnimation">@anim/catalyst_push_up_out</item>
</style>
<style name="redboxButton">
<item name="android:layout_width">0dp</item>
<item name="android:layout_height">wrap_content</item>
Expand Down

0 comments on commit e272089

Please sign in to comment.