From 75c5d74ea2d172c966bdb08879c40bd6660d3244 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Mon, 21 Aug 2023 12:43:42 -0700 Subject: [PATCH] Fix: React Native reloads after teardown render a "blank screen" (#38999) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/38999 After React Native tears down, a RedBox can appear, prompting the user to reload. **Problem:** After React Native reloads, the React Native screen wouldn't show up. **Cause:** ReactContext.onHostResume() wasn't executed. Why: - React Native teardown moves the React manager into the **onHostDestroy()** state. - During initialization, React Native only calls ReactContext.onHostResume(), if the React manager was *already* in the **onHostResume()** state. https://www.internalfb.com/code/fbsource/[f82938c7cc9a0ee722c85c33d1027f326049d37c]/xplat/js/react-native-github/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHost.java?lines=924-925 **Question:** Why does React Native only call ReactContext.onHostResume(), **if the React manager was already in the onHostResume() state?** In short, we want ReactContext.onHostResume() to be delayed until the user navigates to the first React Native screen. Please read the comments in the code to understand why. ## The fix If we're initializing React Native during a reload, just always call ReactContext.onHostResume(). If React Native is reloading, it seems reasonable to assume that: 1. We must have navigated to a React Native screen in the past, or 2. We must be on a React Native screen. Changelog: [Internal] Reviewed By: mdvacca Differential Revision: D48076895 fbshipit-source-id: 6794105920ee30d62cebd745256bb5dea805e891 --- .../react/bridgeless/ReactHostImpl.java | 51 ++++++++++++++----- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHostImpl.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHostImpl.java index 4d136512d09..79baa4efb7a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHostImpl.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridgeless/ReactHostImpl.java @@ -909,29 +909,52 @@ public class ReactHostImpl implements ReactHost { }); class Result { - final ReactInstance mInstance; - final ReactContext mContext; - - Result(ReactInstance instance, ReactContext context) { - mInstance = instance; - mContext = context; - } + final ReactInstance mInstance = instance; + final ReactContext mContext = reactContext; + final boolean mIsReloading = mReloadTask != null; } - return new Result(instance, reactContext); + return new Result(); }, mBGExecutor) .onSuccess( task -> { - ReactInstance reactInstance = task.getResult().mInstance; - ReactContext reactContext = task.getResult().mContext; + final ReactInstance reactInstance = task.getResult().mInstance; + final ReactContext reactContext = task.getResult().mContext; + final boolean isReloading = task.getResult().mIsReloading; + final boolean isManagerResumed = + mReactLifecycleStateManager.getLifecycleState() == LifecycleState.RESUMED; /** - * Call ReactContext.onHostResume() only when already in the resumed state which - * aligns with the bridge https://fburl.com/diffusion/2qhxmudv. + * ReactContext.onHostResume() should only be called when the user navigates to + * the first React Native screen. + * + *

During init: The application puts the React manager in a resumed state, + * when the user navigates to a React Native screen. Two types of init: (1) If + * React Native init happens when the user navigates to a React Native screen, + * the React manager will get resumed on init start, so + * ReactContext.onHostResume() will be executed here. (2) If React Native init + * happens before the user navigates to a React Native screen (i.e: React Native + * is preloaded), the React manager won't be in a resumed state here. So + * ReactContext.onHostResume() won't be executed here. But, when the user + * navigates to their first React Native screen, the application will call + * ReactHost.onHostResume(). That will call ReactContext.onHostResume(). + * + *

During reloads, if the manager isn't resumed, call + * ReactContext.onHostResume(). If React Native is reloading, it seems + * reasonable to assume that: (1) We must have navigated to a React Native + * screen in the past, or (2) We must be on a React Native screen. */ - mReactLifecycleStateManager.resumeReactContextIfHostResumed( - reactContext, mActivity.get()); + if (isReloading && !isManagerResumed) { + mReactLifecycleStateManager.moveToOnHostResume(reactContext, mActivity.get()); + } else { + /** + * Call ReactContext.onHostResume() only when already in the resumed state + * which aligns with the bridge https://fburl.com/diffusion/2qhxmudv. + */ + mReactLifecycleStateManager.resumeReactContextIfHostResumed( + reactContext, mActivity.get()); + } ReactInstanceEventListener[] listeners = new ReactInstanceEventListener[mReactInstanceEventListeners.size()];