From 935cbb76c02cffd378a8f391c6e7443a3da13adc Mon Sep 17 00:00:00 2001 From: Satyajit Sahoo Date: Tue, 22 Dec 2015 09:56:52 -0800 Subject: [PATCH] Add a base activity for React Native apps. Fixes #4043 Summary: Having a base activity allows us to add new features and fixes without having to change the generated `MainActivity` file. cc mkonicek arbesfeld Closes https://github.com/facebook/react-native/pull/4827 Reviewed By: bestander Differential Revision: D2783527 Pulled By: mkonicek fb-gh-sync-id: 707b82839809ca2e1775f5d3ac022a6d00bcac5a --- .../com/facebook/react/ReactActivity.java | 215 ++++++++++++++++++ .../templates/package/MainActivity.java | 85 ++----- 2 files changed, 236 insertions(+), 64 deletions(-) create mode 100644 ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java new file mode 100644 index 000000000..064c3b8b3 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -0,0 +1,215 @@ +package com.facebook.react; + +import android.app.Activity; +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.provider.Settings; +import android.view.KeyEvent; +import android.widget.EditText; +import android.widget.Toast; + +import com.facebook.common.logging.FLog; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; + +import java.util.List; + +import javax.annotation.Nullable; + +/** + * Base Activity for React Native applications. + */ +public abstract class ReactActivity extends Activity implements DefaultHardwareBackBtnHandler { + + 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 ReactInstanceManager mReactInstanceManager; + private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME; + private boolean mDoRefresh = false; + + /** + * Returns the name of the bundle in assets. If this is null, and no file path is specified for + * the bundle, the app will only work with {@code getUseDeveloperSupport} enabled and will + * always try to load the JS bundle from the packager server. + * e.g. "index.android.bundle" + */ + protected @Nullable String getBundleAssetName() { + return "index.android.bundle"; + }; + + /** + * Returns a custom path of the bundle file. This is used in cases the bundle should be loaded + * from a custom path. By default it is loaded from Android assets, from a path specified + * by {@link getBundleAssetName}. + * e.g. "file://sdcard/myapp_cache/index.android.bundle" + */ + protected @Nullable String getJSBundleFile() { + return null; + } + + /** + * Returns the name of the main module. Determines the URL used to fetch the JS bundle + * from the packager server. It is only used when dev support is enabled. + * This is the first file to be executed once the {@link ReactInstanceManager} is created. + * e.g. "index.android" + */ + protected String getJSMainModuleName() { + return "index.android"; + } + + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + * e.g. "MoviesApp" + */ + protected abstract String getMainComponentName(); + + /** + * Returns whether dev mode should be enabled. This enables e.g. the dev menu. + */ + protected abstract boolean getUseDeveloperSupport(); + + /** + * 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 default ones, + * you'll want to include more packages here. + */ + protected abstract List getPackages(); + + /** + * A subclass may override this method if it needs to use a custom instance. + */ + protected ReactInstanceManager createReactInstanceManager() { + ReactInstanceManager.Builder builder = ReactInstanceManager.builder() + .setApplication(getApplication()) + .setJSMainModuleName(getJSMainModuleName()) + .setUseDeveloperSupport(getUseDeveloperSupport()) + .setInitialLifecycleState(mLifecycleState); + + for (ReactPackage reactPackage : getPackages()) { + builder.addPackage(reactPackage); + } + + String jsBundleFile = getJSBundleFile(); + + if (jsBundleFile != null) { + builder.setJSBundleFile(jsBundleFile); + } else { + builder.setBundleAssetName(getBundleAssetName()); + } + + return builder.build(); + } + + /** + * A subclass may override this method if it needs to use a custom {@link ReactRootView}. + */ + protected ReactRootView createRootView() { + return new ReactRootView(this); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (getUseDeveloperSupport() && 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(); + } + } + + mReactInstanceManager = createReactInstanceManager(); + ReactRootView mReactRootView = createRootView(); + mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName()); + 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); + } + } + + @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)) { + // Enable double-tap-R-to-reload + 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(); + } +} diff --git a/local-cli/generator-android/templates/package/MainActivity.java b/local-cli/generator-android/templates/package/MainActivity.java index 1e2b912f9..ab8626844 100644 --- a/local-cli/generator-android/templates/package/MainActivity.java +++ b/local-cli/generator-android/templates/package/MainActivity.java @@ -1,78 +1,35 @@ package <%= package %>; -import android.app.Activity; -import android.os.Bundle; -import android.view.KeyEvent; - -import com.facebook.react.LifecycleState; -import com.facebook.react.ReactInstanceManager; -import com.facebook.react.ReactRootView; -import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; +import com.facebook.react.ReactActivity; import com.facebook.react.shell.MainReactPackage; -import com.facebook.soloader.SoLoader; -public class MainActivity extends Activity implements DefaultHardwareBackBtnHandler { - - private ReactInstanceManager mReactInstanceManager; - private ReactRootView mReactRootView; +public class MainActivity extends ReactActivity { + /** + * Returns the name of the main component registered from JavaScript. + * This is used to schedule rendering of the component. + */ @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mReactRootView = new ReactRootView(this); - - mReactInstanceManager = ReactInstanceManager.builder() - .setApplication(getApplication()) - .setBundleAssetName("index.android.bundle") - .setJSMainModuleName("index.android") - .addPackage(new MainReactPackage()) - .setUseDeveloperSupport(BuildConfig.DEBUG) - .setInitialLifecycleState(LifecycleState.RESUMED) - .build(); - - mReactRootView.startReactApplication(mReactInstanceManager, "<%= name %>", null); - - setContentView(mReactRootView); + protected String getMainComponentName() { + return "<%= name %>"; } + /** + * Returns whether dev mode should be enabled. + * This enables e.g. the dev menu. + */ @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { - mReactInstanceManager.showDevOptionsDialog(); - return true; - } - return super.onKeyUp(keyCode, event); + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; } + /** + * A list of packages used by the app. If the app uses additional views + * or modules besides the default ones, add more packages here. + */ @Override - public void onBackPressed() { - if (mReactInstanceManager != null) { - mReactInstanceManager.onBackPressed(); - } else { - super.onBackPressed(); - } - } - - @Override - public void invokeDefaultOnBackPressed() { - super.onBackPressed(); - } - - @Override - protected void onPause() { - super.onPause(); - - if (mReactInstanceManager != null) { - mReactInstanceManager.onPause(); - } - } - - @Override - protected void onResume() { - super.onResume(); - - if (mReactInstanceManager != null) { - mReactInstanceManager.onResume(this, this); - } + protected abstract List getPackages() { + return Arrays.asList( + new MainReactPackage()); } }