diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java index ff6cf3f42..23f4060b4 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactActivity.java @@ -40,38 +40,8 @@ public abstract class ReactActivity extends Activity private @Nullable PermissionListener mPermissionListener; private @Nullable ReactInstanceManager mReactInstanceManager; private @Nullable ReactRootView mReactRootView; - private LifecycleState mLifecycleState = LifecycleState.BEFORE_RESUME; private DoubleTapReloadRecognizer mDoubleTapReloadRecognizer; - - /** - * 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"; - } + private boolean mDoRefresh = false; /** * Returns the launchOptions which will be passed to the {@link ReactInstanceManager} @@ -91,44 +61,6 @@ public abstract class ReactActivity extends Activity */ 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}. */ @@ -136,6 +68,27 @@ public abstract class ReactActivity extends Activity return new ReactRootView(this); } + /** + * Get the {@link ReactNativeHost} used by this app. By default, assumes {@link #getApplication()} + * is an instance of {@link ReactApplication} and calls + * {@link ReactApplication#getReactNativeHost()}. Override this method if your application class + * does not implement {@code ReactApplication} or you simply have a different mechanism for + * storing a {@code ReactNativeHost}, e.g. as a static field somewhere. + */ + protected ReactNativeHost getReactNativeHost() { + return ((ReactApplication) getApplication()).getReactNativeHost(); + } + + /** + * Get whether developer support should be enabled or not. By default this delegates to + * {@link ReactNativeHost#getUseDeveloperSupport()}. Override this method if your application + * class does not implement {@code ReactApplication} or you simply have a different logic for + * determining this (default just checks {@code BuildConfig}). + */ + protected boolean getUseDeveloperSupport() { + return ((ReactApplication) getApplication()).getReactNativeHost().getUseDeveloperSupport(); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -150,9 +103,11 @@ public abstract class ReactActivity extends Activity } } - mReactInstanceManager = createReactInstanceManager(); mReactRootView = createRootView(); - mReactRootView.startReactApplication(mReactInstanceManager, getMainComponentName(), getLaunchOptions()); + mReactRootView.startReactApplication( + getReactNativeHost().getReactInstanceManager(), + getMainComponentName(), + getLaunchOptions()); setContentView(mReactRootView); mDoubleTapReloadRecognizer = new DoubleTapReloadRecognizer(); } @@ -161,10 +116,8 @@ public abstract class ReactActivity extends Activity protected void onPause() { super.onPause(); - mLifecycleState = LifecycleState.BEFORE_RESUME; - - if (mReactInstanceManager != null) { - mReactInstanceManager.onHostPause(); + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onHostPause(); } } @@ -172,10 +125,8 @@ public abstract class ReactActivity extends Activity protected void onResume() { super.onResume(); - mLifecycleState = LifecycleState.RESUMED; - - if (mReactInstanceManager != null) { - mReactInstanceManager.onHostResume(this, this); + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onHostResume(this, this); } } @@ -183,31 +134,30 @@ public abstract class ReactActivity extends Activity protected void onDestroy() { super.onDestroy(); - mReactRootView.unmountReactApplication(); - mReactRootView = null; - - if (mReactInstanceManager != null) { - mReactInstanceManager.destroy(); + if (mReactRootView != null) { + mReactRootView.unmountReactApplication(); + mReactRootView = null; } + getReactNativeHost().clear(); } @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { - if (mReactInstanceManager != null) { - mReactInstanceManager.onActivityResult(requestCode, resultCode, data); + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager() + .onActivityResult(requestCode, resultCode, data); } } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (mReactInstanceManager != null && - mReactInstanceManager.getDevSupportManager().getDevSupportEnabled()) { + if (getReactNativeHost().hasInstance() && getUseDeveloperSupport()) { if (keyCode == KeyEvent.KEYCODE_MENU) { - mReactInstanceManager.showDevOptionsDialog(); + getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); return true; } if (mDoubleTapReloadRecognizer.didDoubleTapR(keyCode, getCurrentFocus())) { - mReactInstanceManager.getDevSupportManager().handleReloadJS(); + getReactNativeHost().getReactInstanceManager().getDevSupportManager().handleReloadJS(); } } return super.onKeyUp(keyCode, event); @@ -215,8 +165,8 @@ public abstract class ReactActivity extends Activity @Override public void onBackPressed() { - if (mReactInstanceManager != null) { - mReactInstanceManager.onBackPressed(); + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onBackPressed(); } else { super.onBackPressed(); } @@ -229,8 +179,8 @@ public abstract class ReactActivity extends Activity @Override public void onNewIntent(Intent intent) { - if (mReactInstanceManager != null) { - mReactInstanceManager.onNewIntent(intent); + if (getReactNativeHost().hasInstance()) { + getReactNativeHost().getReactInstanceManager().onNewIntent(intent); } else { super.onNewIntent(intent); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactApplication.java b/ReactAndroid/src/main/java/com/facebook/react/ReactApplication.java new file mode 100644 index 000000000..6b74532df --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactApplication.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react; + +public interface ReactApplication { + + /** + * Get the default {@link ReactNativeHost} for this app. + */ + ReactNativeHost getReactNativeHost(); +} diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java new file mode 100644 index 000000000..b30202442 --- /dev/null +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react; + +import javax.annotation.Nullable; + +import java.util.List; + +import android.app.Application; + +import com.facebook.infer.annotation.Assertions; + +/** + * Simple class that holds an instance of {@link ReactInstanceManager}. This can be used in your + * {@link Application class} (see {@link ReactApplication}), or as a static field. + */ +public abstract class ReactNativeHost { + + private final Application mApplication; + private @Nullable ReactInstanceManager mReactInstanceManager; + + protected ReactNativeHost(Application application) { + mApplication = application; + } + + /** + * Get the current {@link ReactInstanceManager} instance, or create one. + */ + public ReactInstanceManager getReactInstanceManager() { + if (mReactInstanceManager == null) { + mReactInstanceManager = createReactInstanceManager(); + } + return mReactInstanceManager; + } + + /** + * Get whether this holder contains a {@link ReactInstanceManager} instance, or not. I.e. if + * {@link #getReactInstanceManager()} has been called at least once since this object was created + * or {@link #clear()} was called. + */ + public boolean hasInstance() { + return mReactInstanceManager != null; + } + + /** + * Destroy the current instance and release the internal reference to it, allowing it to be GCed. + */ + public void clear() { + if (mReactInstanceManager != null) { + mReactInstanceManager.destroy(); + mReactInstanceManager = null; + } + } + + protected ReactInstanceManager createReactInstanceManager() { + ReactInstanceManager.Builder builder = ReactInstanceManager.builder() + .setApplication(mApplication) + .setJSMainModuleName(getJSMainModuleName()) + .setUseDeveloperSupport(getUseDeveloperSupport()) + .setInitialLifecycleState(LifecycleState.BEFORE_CREATE); + + for (ReactPackage reactPackage : getPackages()) { + builder.addPackage(reactPackage); + } + + String jsBundleFile = getJSBundleFile(); + if (jsBundleFile != null) { + builder.setJSBundleFile(jsBundleFile); + } else { + builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName())); + } + + return builder.build(); + } + + /** + * 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 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 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 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(); +} diff --git a/local-cli/generator-android/templates/package/MainActivity.java b/local-cli/generator-android/templates/package/MainActivity.java index 283dffc92..c027ca136 100644 --- a/local-cli/generator-android/templates/package/MainActivity.java +++ b/local-cli/generator-android/templates/package/MainActivity.java @@ -1,11 +1,6 @@ package <%= package %>; import com.facebook.react.ReactActivity; -import com.facebook.react.ReactPackage; -import com.facebook.react.shell.MainReactPackage; - -import java.util.Arrays; -import java.util.List; public class MainActivity extends ReactActivity { @@ -17,24 +12,4 @@ public class MainActivity extends ReactActivity { protected String getMainComponentName() { return "<%= name %>"; } - - /** - * Returns whether dev mode should be enabled. - * This enables e.g. the dev menu. - */ - @Override - 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 - protected List getPackages() { - return Arrays.asList( - new MainReactPackage() - ); - } } diff --git a/local-cli/generator-android/templates/package/MainApplication.java b/local-cli/generator-android/templates/package/MainApplication.java new file mode 100644 index 000000000..525eb0088 --- /dev/null +++ b/local-cli/generator-android/templates/package/MainApplication.java @@ -0,0 +1,35 @@ +package <%= package %>; + +import android.app.Application; +import android.util.Log; + +import com.facebook.react.ReactApplication; +import com.facebook.react.ReactInstanceManager; +import com.facebook.react.ReactNativeHost; +import com.facebook.react.ReactPackage; +import com.facebook.react.shell.MainReactPackage; + +import java.util.Arrays; +import java.util.List; + +public class MainApplication extends Application implements ReactApplication { + + private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { + @Override + protected boolean getUseDeveloperSupport() { + return BuildConfig.DEBUG; + } + + @Override + protected List getPackages() { + return Arrays.asList( + new MainReactPackage() + ); + } + }; + + @Override + public ReactNativeHost getReactNativeHost() { + return mReactNativeHost; + } +} diff --git a/local-cli/generator-android/templates/src/app/src/main/AndroidManifest.xml b/local-cli/generator-android/templates/src/app/src/main/AndroidManifest.xml index 3bcfaf186..3da058c9f 100644 --- a/local-cli/generator-android/templates/src/app/src/main/AndroidManifest.xml +++ b/local-cli/generator-android/templates/src/app/src/main/AndroidManifest.xml @@ -11,6 +11,7 @@ android:targetSdkVersion="22" />