Skip to content

Instantly share code, notes, and snippets.

@mkonicek
Created December 16, 2015 15:28
Show Gist options
  • Save mkonicek/aa7997919c83740c66e6 to your computer and use it in GitHub Desktop.
Save mkonicek/aa7997919c83740c66e6 to your computer and use it in GitHub Desktop.
package com.facebook.react.shell;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.provider.Settings;
import android.support.v4.app.FragmentActivity;
import android.view.KeyEvent;
import android.widget.EditText;
import android.widget.Toast;
import com.facebook.common.logging.FLog;
import com.facebook.infer.annotation.Assertions;
import com.facebook.react.LifecycleState;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactPackage;
import com.facebook.react.ReactRootView;
import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.build.ReactBuildConfig;
import com.facebook.react.devsupport.DevOptionHandler;
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler;
import com.facebook.react.uimanager.UIImplementationProvider;
import com.facebook.systrace.Systrace;
import com.facebook.systrace.SystraceMessage;
/**
* Base for an Activity that hosts a ReactApplicationFragment and wants JS debugging features,
* including FPS, reloading and debugging.
*/
public abstract class ReactActivity extends FragmentActivity
implements DefaultHardwareBackBtnHandler {
private static final String EXTRA_JS_INITIAL_PROPS = "initial_props";
private static final String REDBOX_PERMISSION_MESSAGE =
"Overlay permissions needs to be granted in order for react native apps to run in dev mode";
private @Nullable ReactRootView mReactRootView;
private @Nullable ReactInstanceManager mReactInstanceManager;
private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME;
private boolean mDoRefresh = false;
private @Nullable UIImplementationProvider mUIImplementationProvider;
protected void setUIImplementationProvider(
UIImplementationProvider uiImplementationProvider) {
mUIImplementationProvider = uiImplementationProvider;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
// TODO(5846507): save instance state; not calling super() here causes fragments to *not* be
// re-created when restoring the instance
}
/**
* @return the name of the bundle in assets. If this is null, the app will only work with dev
* support on and will always try to load the JS bundle from the packager server.
* eg. "index.android.bundle"
*/
protected abstract @Nullable String getDefaultBundleName();
/**
* @return the name of the main module. This is used to determine the URL to fetch the JS bundle
* from the packager server and is only used when dev support is enabled.
* This is the first file to be executed once the {@code ReactInstanceManager} is created.
* eg. "RKJSModules/Apps/Airbourne/AirbourneAppBundle"
*/
protected abstract String getDefaultMainModule();
/**
* @return the Builder instance to use to create the ReactInstanceManager.
*/
protected ReactInstanceManager.Builder getReactInstanceManagerBuilder() {
return ReactInstanceManager.builder();
}
/**
* Additional debug options available in the system menu.
* Basic options are always available, see
* {@link com.facebook.react.devsupport.DevSupportManager}.
*/
protected List<String> getAdditionalDebugOptions() {
return Collections.emptyList();
}
/**
* Method returns a {@link ReactPackage} associated with this class.
* A subclass may override this method if it needs more views or modules.
* In that case a subclass should return a {@link com.facebook.react.CompositeReactPackage} of
* this ReactPackage and its own.
*/
protected abstract ReactPackage getPackage();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (ReactBuildConfig.DEBUG && Build.VERSION.SDK_INT >= 23) {
// Get permission to show redbox in dev builds.
if (!Settings.canDrawOverlays(this)) {
Intent serviceIntent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
startActivity(serviceIntent);
FLog.w(ReactConstants.TAG, REDBOX_PERMISSION_MESSAGE);
Toast.makeText(this, REDBOX_PERMISSION_MESSAGE, Toast.LENGTH_LONG).show();
}
}
mReactRootView = new ReactRootView(this);
setContentView(mReactRootView);
}
@Override
protected void onPause() {
super.onPause();
mLifecycleState = LifecycleState.BEFORE_RESUME;
if (mReactInstanceManager != null) {
mReactInstanceManager.onPause();
}
}
@Override
protected void onResume() {
super.onResume();
mLifecycleState = LifecycleState.RESUMED;
if (mReactInstanceManager != null) {
mReactInstanceManager.onResume(this, this);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mReactInstanceManager != null) {
mReactInstanceManager.onDestroy();
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (mReactInstanceManager != null) {
mReactInstanceManager.onActivityResult(requestCode, resultCode, data);
}
}
protected void loadApp(String appKey) {
SystraceMessage.beginSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "loadApp")
.arg("appKey", appKey)
.flush();
try {
Bundle launchOptions = getIntent().getBundleExtra(EXTRA_JS_INITIAL_PROPS);
ReactInstanceManager.Builder builder = getReactInstanceManagerBuilder()
.setApplication(getApplication())
.setBundleAssetName(getDefaultBundleName())
.setJSMainModuleName(getDefaultMainModule())
.addPackage(getPackage())
.setUseDeveloperSupport(ReactBuildConfig.IS_INTERNAL_BUILD)
.setInitialLifecycleState(mLifecycleState)
.setUIImplementationProvider(mUIImplementationProvider);
mReactInstanceManager = builder.build();
registerAdditionalDebugOptions();
Assertions.assertNotNull(mReactRootView).startReactApplication(
mReactInstanceManager,
appKey,
launchOptions);
} finally {
Systrace.endSection(Systrace.TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
if (mReactInstanceManager != null &&
mReactInstanceManager.getDevSupportManager().getDevSupportEnabled()) {
if (keyCode == KeyEvent.KEYCODE_MENU) {
mReactInstanceManager.showDevOptionsDialog();
return true;
}
if (keyCode == KeyEvent.KEYCODE_R && !(getCurrentFocus() instanceof EditText)) {
if (mDoRefresh) {
mReactInstanceManager.getDevSupportManager().handleReloadJS();
mDoRefresh = false;
} else {
mDoRefresh = true;
new Handler().postDelayed(
new Runnable() {
@Override
public void run() {
mDoRefresh = false;
}
},
200);
}
}
}
return super.onKeyUp(keyCode, event);
}
@Override
public void onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager.onBackPressed();
} else {
super.onBackPressed();
}
}
@Override
public void invokeDefaultOnBackPressed() {
super.onBackPressed();
}
@Nullable
public ReactInstanceManager getReactInstanceManager() {
return mReactInstanceManager;
}
public boolean onAdditionalDevOptionSelected(String option) {
return false;
}
private void registerAdditionalDebugOptions() {
List<String> additionalOptions = getAdditionalDebugOptions();
if (additionalOptions == null || additionalOptions.isEmpty()) {
// Subclasses should not return null but don't crash if they do
return;
}
for (String option : getAdditionalDebugOptions()) {
registerAdditionalDebugOption(option);
}
}
private void registerAdditionalDebugOption(final String additionalDebugOption) {
Assertions.assertNotNull(mReactInstanceManager).getDevSupportManager()
.addCustomDevOption(additionalDebugOption, new DevOptionHandler() {
@Override
public void onOptionSelected() {
onAdditionalDevOptionSelected(additionalDebugOption);
}
});
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment