From 4841e1bae068a1742cd195fa9fbf4b67531fd344 Mon Sep 17 00:00:00 2001 From: Joshua Gross Date: Wed, 25 Aug 2021 02:19:40 -0700 Subject: [PATCH] Add first-class support to ReactInstanceManager for Activity-less usage Summary: I'm not sure if we'll ever need to do more than this (probably!) but I want to have a specific option for Activity-less usage, so that our logic in ReactInstanceManager and elsewhere is more clear. I want to be able to confidently assert that the Activity is present and correct, or never present, depending on if this `requireActivity` flag is set; instead of adding else statements that imply "well, the Activity should be here, hope everything is okay" which feels hacky. Doesn't change much for now, but it's an additional constraint in the RN Android codebase that we need to explicitly embed in code so that we can point to it, and so that the logic is more clear. Changelog: [Internal] Reviewed By: javache, motiz88 Differential Revision: D30504616 fbshipit-source-id: d2abdb7c4765a16113c9517406cdbb8cf42822ff --- .../facebook/react/ReactInstanceManager.java | 42 ++++++++++++------- .../react/ReactInstanceManagerBuilder.java | 12 ++++++ .../com/facebook/react/ReactNativeHost.java | 6 +++ 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java index 3792fe60c96..9980ff8c1c6 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java @@ -156,12 +156,14 @@ public class ReactInstanceManager { /* accessed from any thread */ private final JavaScriptExecutorFactory mJavaScriptExecutorFactory; + // See {@code ReactInstanceManagerBuilder} for description of all flags here. private @Nullable List mViewManagerNames = null; private final @Nullable JSBundleLoader mBundleLoader; private final @Nullable String mJSMainModulePath; /* path to JS bundle root on Metro */ private final List mPackages; private final DevSupportManager mDevSupportManager; private final boolean mUseDeveloperSupport; + private final boolean mRequireActivity; private @Nullable ComponentNameResolverManager mComponentNameResolverManager; private @Nullable RuntimeSchedulerManager mRuntimeSchedulerManager; private final @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; @@ -216,6 +218,7 @@ public class ReactInstanceManager { @Nullable String jsMainModulePath, List packages, boolean useDeveloperSupport, + boolean requireActivity, @Nullable NotThreadSafeBridgeIdleDebugListener bridgeIdleDebugListener, LifecycleState initialLifecycleState, @Nullable UIImplementationProvider mUIImplementationProvider, @@ -233,6 +236,7 @@ public class ReactInstanceManager { DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(applicationContext); + // See {@code ReactInstanceManagerBuilder} for description of all flags here. mApplicationContext = applicationContext; mCurrentActivity = currentActivity; mDefaultBackButtonImpl = defaultHardwareBackBtnHandler; @@ -241,6 +245,7 @@ public class ReactInstanceManager { mJSMainModulePath = jsMainModulePath; mPackages = new ArrayList<>(); mUseDeveloperSupport = useDeveloperSupport; + mRequireActivity = requireActivity; Systrace.beginSection( Systrace.TRACE_TAG_REACT_JAVA_BRIDGE, "ReactInstanceManager.initDevSupportManager"); mDevSupportManager = @@ -558,16 +563,20 @@ public class ReactInstanceManager { * @param activity the activity being paused */ @ThreadConfined(UI) - public void onHostPause(Activity activity) { - Assertions.assertNotNull(mCurrentActivity); - Assertions.assertCondition( - activity == mCurrentActivity, - "Pausing an activity that is not the current activity, this is incorrect! " - + "Current activity: " - + mCurrentActivity.getClass().getSimpleName() - + " " - + "Paused activity: " - + activity.getClass().getSimpleName()); + public void onHostPause(@Nullable Activity activity) { + if (mRequireActivity) { + Assertions.assertCondition(mCurrentActivity != null); + } + if (mCurrentActivity != null) { + Assertions.assertCondition( + activity == mCurrentActivity, + "Pausing an activity that is not the current activity, this is incorrect! " + + "Current activity: " + + mCurrentActivity.getClass().getSimpleName() + + " " + + "Paused activity: " + + activity.getClass().getSimpleName()); + } onHostPause(); } @@ -583,7 +592,8 @@ public class ReactInstanceManager { * this instance of {@link ReactInstanceManager}. */ @ThreadConfined(UI) - public void onHostResume(Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) { + public void onHostResume( + @Nullable Activity activity, DefaultHardwareBackBtnHandler defaultBackButtonImpl) { UiThreadUtil.assertOnUiThread(); mDefaultBackButtonImpl = defaultBackButtonImpl; @@ -592,7 +602,7 @@ public class ReactInstanceManager { /** Use this method when the activity resumes. */ @ThreadConfined(UI) - public void onHostResume(Activity activity) { + public void onHostResume(@Nullable Activity activity) { UiThreadUtil.assertOnUiThread(); mCurrentActivity = activity; @@ -631,8 +641,8 @@ public class ReactInstanceManager { // activity is attached to window, we can enable dev support immediately mDevSupportManager.setDevSupportEnabled(true); } - } else { - // there is no activity, we can enable dev support + } else if (!mRequireActivity) { + // there is no activity, but we can enable dev support mDevSupportManager.setDevSupportEnabled(true); } } @@ -666,7 +676,9 @@ public class ReactInstanceManager { * @param activity the activity being destroyed */ @ThreadConfined(UI) - public void onHostDestroy(Activity activity) { + public void onHostDestroy(@Nullable Activity activity) { + // In some cases, Activity may (correctly) be null. + // See mRequireActivity flag. if (activity == mCurrentActivity) { onHostDestroy(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java index c06f71b9a59..36f700e4976 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java @@ -45,6 +45,7 @@ public class ReactInstanceManagerBuilder { private @Nullable NotThreadSafeBridgeIdleDebugListener mBridgeIdleDebugListener; private @Nullable Application mApplication; private boolean mUseDeveloperSupport; + private boolean mRequireActivity; private @Nullable LifecycleState mInitialLifecycleState; private @Nullable UIImplementationProvider mUIImplementationProvider; private @Nullable NativeModuleCallExceptionHandler mNativeModuleCallExceptionHandler; @@ -171,6 +172,16 @@ public class ReactInstanceManagerBuilder { return this; } + /** + * When {@code false}, indicates that correct usage of React Native will NOT involve an Activity. + * For the vast majority of Android apps in the ecosystem, this will not need to change. Unless + * you really know what you're doing, you should probably not change this! + */ + public ReactInstanceManagerBuilder setRequireActivity(boolean requireActivity) { + mRequireActivity = requireActivity; + return this; + } + /** * Sets the initial lifecycle state of the host. For example, if the host is already resumed at * creation time, we wouldn't expect an onResume call until we get an onPause call. @@ -283,6 +294,7 @@ public class ReactInstanceManagerBuilder { mJSMainModulePath, mPackages, mUseDeveloperSupport, + mRequireActivity, mBridgeIdleDebugListener, Assertions.assertNotNull(mInitialLifecycleState, "Initial lifecycle state was not set"), mUIImplementationProvider, diff --git a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java index d4c457ff87d..dc313d33bc0 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java +++ b/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java @@ -68,6 +68,7 @@ public abstract class ReactNativeHost { .setApplication(mApplication) .setJSMainModulePath(getJSMainModuleName()) .setUseDeveloperSupport(getUseDeveloperSupport()) + .setRequireActivity(getShouldRequireActivity()) .setRedBoxHandler(getRedBoxHandler()) .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory()) .setUIImplementationProvider(getUIImplementationProvider()) @@ -124,6 +125,11 @@ public abstract class ReactNativeHost { return null; } + /** Returns whether or not to treat it as normal if Activity is null. */ + public boolean getShouldRequireActivity() { + return true; + } + /** * Returns the name of the main module. Determines the URL used to fetch the JS bundle from Metro. * It is only used when dev support is enabled. This is the first file to be executed once the