From fb234704832513d655a2890458eec66f873a5674 Mon Sep 17 00:00:00 2001 From: Ramanpreet Nara Date: Tue, 21 May 2024 09:58:17 -0700 Subject: [PATCH] Push ReactContext logic in derived classes (#44226) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/44226 Changelog: [Android][Removed] Delete ReactContext.initializeWithInstance(). ReactContext now no longer contains legacy react instance methods. Please use BridgeReactInstance instead. Yet another attempt to land this (last one was D55964787). Copy-pasting below the amazing summary from RSNara. ## Context Prior, ReactContext used to implement bridge logic. For bridgeless mode, we created BridgelessReactContext < ReactContext ## Problem This could lead to failures: we could call bridge methods in bridgeless mode. ## Changes Primary change: - Make all the react instance methods inside ReactContext abstract. Secondary changes: Implement react instance methods in concrete subclasses: - **New:** BridgeReactContext: By delegating to CatalystInstance - **New:** ThemedReactContext: By delegating to inner ReactContext - **Unchanged:** BridgelessReactContext: By delegating to ReactHost ## Auxiliary changes This fixes ThemedReactContext in bridgeless mode. **Problem:** Prior, ThemedReactContext's react instance methods did not work in bridgeless mode: ThemedReactContext wasn't initialized in bridgeless mode, so all those methods had undefined behaviour. **Solution:** ThemedReactContext now implements all react instance methods, by just forwarding to the initialized ReactContext it decorates (which has an instance). NOTE: Intentionally not converting `BridgeReactContext` to Kotlin to minimize the risk of these changes. Reviewed By: cortinico Differential Revision: D56064036 fbshipit-source-id: 2e380bf7ee46892c5fc0044b03a929f12d122157 --- .../ReactAndroid/api/ReactAndroid.api | 75 +++-- .../react/bridge/BridgeReactContext.java | 284 ++++++++++++++++++ .../react/bridge/BridgeReactContext.kt | 20 -- .../facebook/react/bridge/ReactContext.java | 163 ++-------- .../react/runtime/BridgelessReactContext.java | 14 + .../react/uimanager/ThemedReactContext.java | 98 +++++- 6 files changed, 466 insertions(+), 188 deletions(-) create mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java delete mode 100644 packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.kt diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 89cc6f8dcd4ded..e41508ed1ec2e0 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -549,8 +549,29 @@ public abstract class com/facebook/react/bridge/BaseJavaModule : com/facebook/re public fun invalidate ()V } -public final class com/facebook/react/bridge/BridgeReactContext : com/facebook/react/bridge/ReactApplicationContext { +public class com/facebook/react/bridge/BridgeReactContext : com/facebook/react/bridge/ReactApplicationContext { public fun (Landroid/content/Context;)V + public fun destroy ()V + public fun getCatalystInstance ()Lcom/facebook/react/bridge/CatalystInstance; + public fun getFabricUIManager ()Lcom/facebook/react/bridge/UIManager; + public fun getJSCallInvokerHolder ()Lcom/facebook/react/turbomodule/core/interfaces/CallInvokerHolder; + public fun getJSModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule; + public fun getNativeModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/NativeModule; + public fun getNativeModules ()Ljava/util/Collection; + public fun getSourceURL ()Ljava/lang/String; + public fun handleException (Ljava/lang/Exception;)V + public fun hasActiveCatalystInstance ()Z + public fun hasActiveReactInstance ()Z + public fun hasCatalystInstance ()Z + public fun hasNativeModule (Ljava/lang/Class;)Z + public fun hasReactInstance ()Z + public fun initializeWithInstance (Lcom/facebook/react/bridge/CatalystInstance;)V + public fun isBridgeless ()Z + public fun registerSegment (ILjava/lang/String;Lcom/facebook/react/bridge/Callback;)V +} + +public abstract interface class com/facebook/react/bridge/BridgeReactContext$RCTDeviceEventEmitter : com/facebook/react/bridge/JavaScriptModule { + public abstract fun emit (Ljava/lang/String;Ljava/lang/Object;)V } public abstract interface class com/facebook/react/bridge/Callback { @@ -1071,38 +1092,37 @@ public abstract class com/facebook/react/bridge/ReactContext : android/content/C public fun assertOnNativeModulesQueueThread ()V public fun assertOnNativeModulesQueueThread (Ljava/lang/String;)V public fun assertOnUiQueueThread ()V - public fun destroy ()V + public abstract fun destroy ()V public fun emitDeviceEvent (Ljava/lang/String;)V public fun emitDeviceEvent (Ljava/lang/String;Ljava/lang/Object;)V - public fun getCatalystInstance ()Lcom/facebook/react/bridge/CatalystInstance; + public abstract fun getCatalystInstance ()Lcom/facebook/react/bridge/CatalystInstance; public fun getCurrentActivity ()Landroid/app/Activity; public fun getExceptionHandler ()Lcom/facebook/react/bridge/JSExceptionHandler; - public fun getFabricUIManager ()Lcom/facebook/react/bridge/UIManager; - public fun getJSCallInvokerHolder ()Lcom/facebook/react/turbomodule/core/interfaces/CallInvokerHolder; + public abstract fun getFabricUIManager ()Lcom/facebook/react/bridge/UIManager; + public abstract fun getJSCallInvokerHolder ()Lcom/facebook/react/turbomodule/core/interfaces/CallInvokerHolder; public fun getJSExceptionHandler ()Lcom/facebook/react/bridge/JSExceptionHandler; public fun getJSMessageQueueThread ()Lcom/facebook/react/bridge/queue/MessageQueueThread; - public fun getJSModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule; - public fun getJavaScriptContextHolder ()Lcom/facebook/react/bridge/JavaScriptContextHolder; + public abstract fun getJSModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule; + public abstract fun getJavaScriptContextHolder ()Lcom/facebook/react/bridge/JavaScriptContextHolder; public fun getLifecycleState ()Lcom/facebook/react/common/LifecycleState; - public fun getNativeModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/NativeModule; - public fun getNativeModules ()Ljava/util/Collection; + public abstract fun getNativeModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/NativeModule; + public abstract fun getNativeModules ()Ljava/util/Collection; public fun getNativeModulesMessageQueueThread ()Lcom/facebook/react/bridge/queue/MessageQueueThread; - public fun getSourceURL ()Ljava/lang/String; + public abstract fun getSourceURL ()Ljava/lang/String; public fun getSystemService (Ljava/lang/String;)Ljava/lang/Object; public fun getUiMessageQueueThread ()Lcom/facebook/react/bridge/queue/MessageQueueThread; - public fun handleException (Ljava/lang/Exception;)V - public fun hasActiveCatalystInstance ()Z - public fun hasActiveReactInstance ()Z - public fun hasCatalystInstance ()Z + public abstract fun handleException (Ljava/lang/Exception;)V + public abstract fun hasActiveCatalystInstance ()Z + public abstract fun hasActiveReactInstance ()Z + public abstract fun hasCatalystInstance ()Z public fun hasCurrentActivity ()Z - public fun hasNativeModule (Ljava/lang/Class;)Z - public fun hasReactInstance ()Z + public abstract fun hasNativeModule (Ljava/lang/Class;)Z + public abstract fun hasReactInstance ()Z + protected fun initializeFromOther (Lcom/facebook/react/bridge/ReactContext;)V protected fun initializeInteropModules ()V - protected fun initializeInteropModules (Lcom/facebook/react/bridge/ReactContext;)V public fun initializeMessageQueueThreads (Lcom/facebook/react/bridge/queue/ReactQueueConfiguration;)V - public fun initializeWithInstance (Lcom/facebook/react/bridge/CatalystInstance;)V public fun internal_registerInteropModule (Ljava/lang/Class;Ljava/lang/Object;)V - public fun isBridgeless ()Z + public abstract fun isBridgeless ()Z public fun isOnJSQueueThread ()Z public fun isOnNativeModulesQueueThread ()Z public fun isOnUiQueueThread ()Z @@ -1113,7 +1133,7 @@ public abstract class com/facebook/react/bridge/ReactContext : android/content/C public fun onHostResume (Landroid/app/Activity;)V public fun onNewIntent (Landroid/app/Activity;Landroid/content/Intent;)V public fun onWindowFocusChange (Z)V - public fun registerSegment (ILjava/lang/String;Lcom/facebook/react/bridge/Callback;)V + public abstract fun registerSegment (ILjava/lang/String;Lcom/facebook/react/bridge/Callback;)V public fun removeActivityEventListener (Lcom/facebook/react/bridge/ActivityEventListener;)V public fun removeLifecycleEventListener (Lcom/facebook/react/bridge/LifecycleEventListener;)V public fun removeWindowFocusChangeListener (Lcom/facebook/react/bridge/WindowFocusChangeListener;)V @@ -4964,14 +4984,29 @@ public class com/facebook/react/uimanager/ThemedReactContext : com/facebook/reac public fun (Lcom/facebook/react/bridge/ReactApplicationContext;Landroid/content/Context;Ljava/lang/String;)V public fun (Lcom/facebook/react/bridge/ReactApplicationContext;Landroid/content/Context;Ljava/lang/String;I)V public fun addLifecycleEventListener (Lcom/facebook/react/bridge/LifecycleEventListener;)V + public fun destroy ()V + public fun getCatalystInstance ()Lcom/facebook/react/bridge/CatalystInstance; public fun getCurrentActivity ()Landroid/app/Activity; public fun getFabricUIManager ()Lcom/facebook/react/bridge/UIManager; + public fun getJSCallInvokerHolder ()Lcom/facebook/react/turbomodule/core/interfaces/CallInvokerHolder; + public fun getJSModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/JavaScriptModule; + public fun getJavaScriptContextHolder ()Lcom/facebook/react/bridge/JavaScriptContextHolder; public fun getModuleName ()Ljava/lang/String; + public fun getNativeModule (Ljava/lang/Class;)Lcom/facebook/react/bridge/NativeModule; + public fun getNativeModules ()Ljava/util/Collection; public fun getReactApplicationContext ()Lcom/facebook/react/bridge/ReactApplicationContext; + public fun getSourceURL ()Ljava/lang/String; public fun getSurfaceID ()Ljava/lang/String; public fun getSurfaceId ()I + public fun handleException (Ljava/lang/Exception;)V + public fun hasActiveCatalystInstance ()Z + public fun hasActiveReactInstance ()Z + public fun hasCatalystInstance ()Z public fun hasCurrentActivity ()Z + public fun hasNativeModule (Ljava/lang/Class;)Z + public fun hasReactInstance ()Z public fun isBridgeless ()Z + public fun registerSegment (ILjava/lang/String;Lcom/facebook/react/bridge/Callback;)V public fun removeLifecycleEventListener (Lcom/facebook/react/bridge/LifecycleEventListener;)V } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java new file mode 100644 index 00000000000000..874886779a2194 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.bridge; + +import static com.facebook.infer.annotation.ThreadConfined.UI; + +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.facebook.common.logging.FLog; +import com.facebook.infer.annotation.Assertions; +import com.facebook.infer.annotation.ThreadConfined; +import com.facebook.proguard.annotations.DoNotStrip; +import com.facebook.react.bridge.queue.ReactQueueConfiguration; +import com.facebook.react.common.ReactConstants; +import com.facebook.react.common.annotations.DeprecatedInNewArchitecture; +import com.facebook.react.common.annotations.FrameworkAPI; +import com.facebook.react.common.annotations.UnstableReactNativeAPI; +import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; +import java.util.Collection; + +/** + * This is the bridge-specific concrete subclass of ReactContext. ReactContext has many methods that + * delegate to the react instance. This subclass implements those methods, by delegating to the + * CatalystInstance. If you need to create a ReactContext within an "bridge context", please create + * BridgeReactContext. + */ +@DeprecatedInNewArchitecture +public class BridgeReactContext extends ReactApplicationContext { + @DoNotStrip + public interface RCTDeviceEventEmitter extends JavaScriptModule { + void emit(@NonNull String eventName, @Nullable Object data); + } + + private static final String TAG = "BridgeReactContext"; + + private static final String EARLY_JS_ACCESS_EXCEPTION_MESSAGE = + "Tried to access a JS module before the React instance was fully set up. Calls to " + + "ReactContext#getJSModule should only happen once initialize() has been called on your " + + "native module."; + private static final String LATE_JS_ACCESS_EXCEPTION_MESSAGE = + "Tried to access a JS module after the React instance was destroyed."; + private static final String EARLY_NATIVE_MODULE_EXCEPTION_MESSAGE = + "Trying to call native module before CatalystInstance has been set!"; + private static final String LATE_NATIVE_MODULE_EXCEPTION_MESSAGE = + "Trying to call native module after CatalystInstance has been destroyed!"; + + private volatile boolean mDestroyed = false; + private @Nullable CatalystInstance mCatalystInstance; + + public BridgeReactContext(Context context) { + super(context); + } + + /** Set and initialize CatalystInstance for this Context. This should be called exactly once. */ + public void initializeWithInstance(CatalystInstance catalystInstance) { + if (catalystInstance == null) { + throw new IllegalArgumentException("CatalystInstance cannot be null."); + } + if (mCatalystInstance != null) { + throw new IllegalStateException("ReactContext has been already initialized"); + } + if (mDestroyed) { + ReactSoftExceptionLogger.logSoftException( + TAG, + new IllegalStateException("Cannot initialize ReactContext after it has been destroyed.")); + } + + mCatalystInstance = catalystInstance; + + ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration(); + initializeMessageQueueThreads(queueConfig); + initializeInteropModules(); + } + + private void raiseCatalystInstanceMissingException() { + throw new IllegalStateException( + mDestroyed ? LATE_NATIVE_MODULE_EXCEPTION_MESSAGE : EARLY_NATIVE_MODULE_EXCEPTION_MESSAGE); + } + + /** + * @return handle to the specified JS module for the CatalystInstance associated with this Context + */ + @Override + public T getJSModule(Class jsInterface) { + if (mCatalystInstance == null) { + if (mDestroyed) { + throw new IllegalStateException(LATE_JS_ACCESS_EXCEPTION_MESSAGE); + } + throw new IllegalStateException(EARLY_JS_ACCESS_EXCEPTION_MESSAGE); + } + if (mInteropModuleRegistry != null + && mInteropModuleRegistry.shouldReturnInteropModule(jsInterface)) { + return mInteropModuleRegistry.getInteropModule(jsInterface); + } + return mCatalystInstance.getJSModule(jsInterface); + } + + @Override + public boolean hasNativeModule(Class nativeModuleInterface) { + if (mCatalystInstance == null) { + raiseCatalystInstanceMissingException(); + } + return mCatalystInstance.hasNativeModule(nativeModuleInterface); + } + + @Override + public Collection getNativeModules() { + if (mCatalystInstance == null) { + raiseCatalystInstanceMissingException(); + } + return mCatalystInstance.getNativeModules(); + } + + /** + * @return the instance of the specified module interface associated with this ReactContext. + */ + @Override + @Nullable + public T getNativeModule(Class nativeModuleInterface) { + if (mCatalystInstance == null) { + raiseCatalystInstanceMissingException(); + } + return mCatalystInstance.getNativeModule(nativeModuleInterface); + } + + @Override + public CatalystInstance getCatalystInstance() { + return Assertions.assertNotNull(mCatalystInstance); + } + + /** + * This API has been deprecated due to naming consideration, please use hasActiveReactInstance() + * instead + * + * @return + */ + @Deprecated + @Override + public boolean hasActiveCatalystInstance() { + return hasActiveReactInstance(); + } + + /** + * @return true if there is an non-null, alive react native instance + */ + @Override + public boolean hasActiveReactInstance() { + return mCatalystInstance != null && !mCatalystInstance.isDestroyed(); + } + + /** + * This API has been deprecated due to naming consideration, please use hasReactInstance() instead + * + * @return + */ + @Deprecated + @Override + public boolean hasCatalystInstance() { + return mCatalystInstance != null; + } + + @Override + public boolean hasReactInstance() { + return mCatalystInstance != null; + } + + /** Destroy this instance, making it unusable. */ + @Override + @ThreadConfined(UI) + public void destroy() { + UiThreadUtil.assertOnUiThread(); + + mDestroyed = true; + if (mCatalystInstance != null) { + mCatalystInstance.destroy(); + } + } + + /** + * Passes the given exception to the current {@link JSExceptionHandler} if one exists, rethrowing + * otherwise. + */ + @Override + public void handleException(Exception e) { + boolean catalystInstanceVariableExists = mCatalystInstance != null; + boolean isCatalystInstanceAlive = + catalystInstanceVariableExists && !mCatalystInstance.isDestroyed(); + boolean hasExceptionHandler = getJSExceptionHandler() != null; + + if (isCatalystInstanceAlive && hasExceptionHandler) { + getJSExceptionHandler().handleException(e); + } else { + FLog.e( + ReactConstants.TAG, + "Unable to handle Exception - catalystInstanceVariableExists: " + + catalystInstanceVariableExists + + " - isCatalystInstanceAlive: " + + isCatalystInstanceAlive + + " - hasExceptionHandler: " + + hasExceptionHandler, + e); + throw new IllegalStateException(e); + } + } + + /** + * @deprecated DO NOT USE, this method will be removed in the near future. + */ + @Deprecated + @Override + public boolean isBridgeless() { + return false; + } + + /** + * Get the C pointer (as a long) to the JavaScriptCore context associated with this instance. Use + * the following pattern to ensure that the JS context is not cleared while you are using it: + * JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder() + * synchronized(jsContext) { nativeThingNeedingJsContext(jsContext.get()); } + */ + @Override + @FrameworkAPI + @UnstableReactNativeAPI + public @Nullable JavaScriptContextHolder getJavaScriptContextHolder() { + if (mCatalystInstance != null) { + return mCatalystInstance.getJavaScriptContextHolder(); + } + return null; + } + + /** + * Returns a hybrid object that contains a pointer to a JS CallInvoker, which is used to schedule + * work on the JS Thread. + */ + @Nullable + @Override + public CallInvokerHolder getJSCallInvokerHolder() { + if (mCatalystInstance != null) { + return mCatalystInstance.getJSCallInvokerHolder(); + } + return null; + } + + @DeprecatedInNewArchitecture( + message = + "This method will be deprecated later as part of Stable APIs with bridge removal and not" + + " encouraged usage.") + /** + * Get the UIManager for Fabric from the CatalystInstance. + * + * @return The UIManager when CatalystInstance is active. + */ + @Override + public @Nullable UIManager getFabricUIManager() { + return mCatalystInstance.getFabricUIManager(); + } + + /** + * Get the sourceURL for the JS bundle from the CatalystInstance. This method is needed for + * compatibility with bridgeless mode, which has no CatalystInstance. + * + * @return The JS bundle URL set when the bundle was loaded + */ + @Override + public @Nullable String getSourceURL() { + return mCatalystInstance == null ? null : mCatalystInstance.getSourceURL(); + } + + /** + * Register a JS segment after loading it from cache or server, make sure mCatalystInstance is + * properly initialised and not null before calling. + */ + @Override + public void registerSegment(int segmentId, String path, Callback callback) { + Assertions.assertNotNull(mCatalystInstance).registerSegment(segmentId, path); + Assertions.assertNotNull(callback).invoke(); + } +} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.kt deleted file mode 100644 index 0f37c2fd675f8a..00000000000000 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/BridgeReactContext.kt +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) Meta Platforms, Inc. and affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -package com.facebook.react.bridge - -import android.content.Context -import com.facebook.react.common.annotations.DeprecatedInNewArchitecture - -/** - * This is the bridge-specific concrete subclass of ReactContext. ReactContext has many methods that - * delegate to the react instance. This subclass will implement those methods, by delegating to the - * CatalystInstance. If you need to create a ReactContext within an "bridge context", please create - * BridgeReactContext. - */ -@DeprecatedInNewArchitecture -public class BridgeReactContext(base: Context) : ReactApplicationContext(base) {} diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java index 4a5b07eb647470..ade85e0a90228c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/ReactContext.java @@ -25,7 +25,6 @@ import com.facebook.react.bridge.queue.MessageQueueThread; import com.facebook.react.bridge.queue.ReactQueueConfiguration; import com.facebook.react.common.LifecycleState; -import com.facebook.react.common.ReactConstants; import com.facebook.react.common.annotations.DeprecatedInNewArchitecture; import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; import java.lang.ref.WeakReference; @@ -45,16 +44,6 @@ public interface RCTDeviceEventEmitter extends JavaScriptModule { } private static final String TAG = "ReactContext"; - private static final String EARLY_JS_ACCESS_EXCEPTION_MESSAGE = - "Tried to access a JS module before the React instance was fully set up. Calls to " - + "ReactContext#getJSModule should only happen once initialize() has been called on your " - + "native module."; - private static final String LATE_JS_ACCESS_EXCEPTION_MESSAGE = - "Tried to access a JS module after the React instance was destroyed."; - private static final String EARLY_NATIVE_MODULE_EXCEPTION_MESSAGE = - "Trying to call native module before CatalystInstance has been set!"; - private static final String LATE_NATIVE_MODULE_EXCEPTION_MESSAGE = - "Trying to call native module after CatalystInstance has been destroyed!"; private final CopyOnWriteArraySet mLifecycleEventListeners = new CopyOnWriteArraySet<>(); @@ -65,9 +54,8 @@ public interface RCTDeviceEventEmitter extends JavaScriptModule { private LifecycleState mLifecycleState = LifecycleState.BEFORE_CREATE; - private volatile boolean mDestroyed = false; - private @Nullable CatalystInstance mCatalystInstance; private @Nullable LayoutInflater mInflater; + private @Nullable ReactQueueConfiguration mQueueConfig; private @Nullable MessageQueueThread mUiMessageQueueThread; private @Nullable MessageQueueThread mNativeModulesMessageQueueThread; private @Nullable MessageQueueThread mJSMessageQueueThread; @@ -82,25 +70,11 @@ public ReactContext(Context base) { super(base); } - /** Set and initialize CatalystInstance for this Context. This should be called exactly once. */ - public void initializeWithInstance(CatalystInstance catalystInstance) { - if (catalystInstance == null) { - throw new IllegalArgumentException("CatalystInstance cannot be null."); + protected void initializeFromOther(ReactContext other) { + if (other.hasReactInstance()) { + initializeMessageQueueThreads(other.mQueueConfig); } - if (mCatalystInstance != null) { - throw new IllegalStateException("ReactContext has been already initialized"); - } - if (mDestroyed) { - ReactSoftExceptionLogger.logSoftException( - TAG, - new IllegalStateException("Cannot initialize ReactContext after it has been destroyed.")); - } - - mCatalystInstance = catalystInstance; - - ReactQueueConfiguration queueConfig = catalystInstance.getReactQueueConfiguration(); - initializeMessageQueueThreads(queueConfig); - initializeInteropModules(); + mInteropModuleRegistry = other.mInteropModuleRegistry; } /** Initialize message queue threads using a ReactQueueConfiguration. */ @@ -111,6 +85,7 @@ public synchronized void initializeMessageQueueThreads(ReactQueueConfiguration q || mJSMessageQueueThread != null) { throw new IllegalStateException("Message queue threads already initialized"); } + mQueueConfig = queueConfig; mUiMessageQueueThread = queueConfig.getUIQueueThread(); mNativeModulesMessageQueueThread = queueConfig.getNativeModulesQueueThread(); mJSMessageQueueThread = queueConfig.getJSQueueThread(); @@ -132,10 +107,6 @@ protected void initializeInteropModules() { mInteropModuleRegistry = new InteropModuleRegistry(); } - protected void initializeInteropModules(ReactContext reactContext) { - mInteropModuleRegistry = reactContext.mInteropModuleRegistry; - } - public void resetPerfStats() { if (mNativeModulesMessageQueueThread != null) { mNativeModulesMessageQueueThread.resetPerfStats(); @@ -149,11 +120,6 @@ public void setJSExceptionHandler(@Nullable JSExceptionHandler jSExceptionHandle mJSExceptionHandler = jSExceptionHandler; } - private void raiseCatalystInstanceMissingException() { - throw new IllegalStateException( - mDestroyed ? LATE_NATIVE_MODULE_EXCEPTION_MESSAGE : EARLY_NATIVE_MODULE_EXCEPTION_MESSAGE); - } - // We override the following method so that views inflated with the inflater obtained from this // context return the ReactContext in #getContext(). The default implementation uses the base // context instead, so it couldn't be cast to ReactContext. @@ -172,44 +138,17 @@ public Object getSystemService(String name) { /** * @return handle to the specified JS module for the CatalystInstance associated with this Context */ - public T getJSModule(Class jsInterface) { - if (mCatalystInstance == null) { - if (mDestroyed) { - throw new IllegalStateException(LATE_JS_ACCESS_EXCEPTION_MESSAGE); - } - throw new IllegalStateException(EARLY_JS_ACCESS_EXCEPTION_MESSAGE); - } - if (mInteropModuleRegistry != null - && mInteropModuleRegistry.shouldReturnInteropModule(jsInterface)) { - return mInteropModuleRegistry.getInteropModule(jsInterface); - } - return mCatalystInstance.getJSModule(jsInterface); - } + public abstract T getJSModule(Class jsInterface); - public boolean hasNativeModule(Class nativeModuleInterface) { - if (mCatalystInstance == null) { - raiseCatalystInstanceMissingException(); - } - return mCatalystInstance.hasNativeModule(nativeModuleInterface); - } + public abstract boolean hasNativeModule(Class nativeModuleInterface); - public Collection getNativeModules() { - if (mCatalystInstance == null) { - raiseCatalystInstanceMissingException(); - } - return mCatalystInstance.getNativeModules(); - } + public abstract Collection getNativeModules(); /** * @return the instance of the specified module interface associated with this ReactContext. */ @Nullable - public T getNativeModule(Class nativeModuleInterface) { - if (mCatalystInstance == null) { - raiseCatalystInstanceMissingException(); - } - return mCatalystInstance.getNativeModule(nativeModuleInterface); - } + public abstract T getNativeModule(Class nativeModuleInterface); /** * Calls RCTDeviceEventEmitter.emit to JavaScript, with given event name and an optional list of @@ -226,9 +165,7 @@ public void emitDeviceEvent(String eventName) { emitDeviceEvent(eventName, null); } - public CatalystInstance getCatalystInstance() { - return Assertions.assertNotNull(mCatalystInstance); - } + public abstract CatalystInstance getCatalystInstance(); /** * This API has been deprecated due to naming consideration, please use hasActiveReactInstance() @@ -237,16 +174,12 @@ public CatalystInstance getCatalystInstance() { * @return */ @Deprecated - public boolean hasActiveCatalystInstance() { - return hasActiveReactInstance(); - } + public abstract boolean hasActiveCatalystInstance(); /** * @return true if there is an non-null, alive react native instance */ - public boolean hasActiveReactInstance() { - return mCatalystInstance != null && !mCatalystInstance.isDestroyed(); - } + public abstract boolean hasActiveReactInstance(); /** * This API has been deprecated due to naming consideration, please use hasReactInstance() instead @@ -254,13 +187,9 @@ public boolean hasActiveReactInstance() { * @return */ @Deprecated - public boolean hasCatalystInstance() { - return mCatalystInstance != null; - } + public abstract boolean hasCatalystInstance(); - public boolean hasReactInstance() { - return mCatalystInstance != null; - } + public abstract boolean hasReactInstance(); public LifecycleState getLifecycleState() { return mLifecycleState; @@ -390,14 +319,7 @@ private void onHostDestroyImpl() { /** Destroy this instance, making it unusable. */ @ThreadConfined(UI) - public void destroy() { - UiThreadUtil.assertOnUiThread(); - - mDestroyed = true; - if (mCatalystInstance != null) { - mCatalystInstance.destroy(); - } - } + public abstract void destroy(); /** Should be called by the hosting Fragment in {@link Fragment#onActivityResult} */ public void onActivityResult( @@ -490,27 +412,7 @@ public boolean runOnJSQueueThread(Runnable runnable) { * Passes the given exception to the current {@link JSExceptionHandler} if one exists, rethrowing * otherwise. */ - public void handleException(Exception e) { - boolean catalystInstanceVariableExists = mCatalystInstance != null; - boolean isCatalystInstanceAlive = - catalystInstanceVariableExists && !mCatalystInstance.isDestroyed(); - boolean hasExceptionHandler = mJSExceptionHandler != null; - - if (isCatalystInstanceAlive && hasExceptionHandler) { - mJSExceptionHandler.handleException(e); - } else { - FLog.e( - ReactConstants.TAG, - "Unable to handle Exception - catalystInstanceVariableExists: " - + catalystInstanceVariableExists - + " - isCatalystInstanceAlive: " - + isCatalystInstanceAlive - + " - hasExceptionHandler: " - + hasExceptionHandler, - e); - throw new IllegalStateException(e); - } - } + public abstract void handleException(Exception e); public class ExceptionHandlerWrapper implements JSExceptionHandler { @@ -565,9 +467,7 @@ public boolean startActivityForResult(Intent intent, int code, Bundle bundle) { * @deprecated DO NOT USE, this method will be removed in the near future. */ @Deprecated - public boolean isBridgeless() { - return false; - } + public abstract boolean isBridgeless(); /** * Get the C pointer (as a long) to the JavaScriptCore context associated with this instance. Use @@ -575,23 +475,13 @@ public boolean isBridgeless() { * JavaScriptContextHolder jsContext = reactContext.getJavaScriptContextHolder() * synchronized(jsContext) { nativeThingNeedingJsContext(jsContext.get()); } */ - public @Nullable JavaScriptContextHolder getJavaScriptContextHolder() { - if (mCatalystInstance != null) { - return mCatalystInstance.getJavaScriptContextHolder(); - } - return null; - } + public abstract @Nullable JavaScriptContextHolder getJavaScriptContextHolder(); /** * Returns a hybrid object that contains a pointer to a JS CallInvoker, which is used to schedule * work on the JS Thread. */ - public @Nullable CallInvokerHolder getJSCallInvokerHolder() { - if (mCatalystInstance != null) { - return mCatalystInstance.getJSCallInvokerHolder(); - } - return null; - } + public abstract @Nullable CallInvokerHolder getJSCallInvokerHolder(); @DeprecatedInNewArchitecture( message = @@ -602,9 +492,7 @@ public boolean isBridgeless() { * * @return The UIManager when CatalystInstance is active. */ - public @Nullable UIManager getFabricUIManager() { - return mCatalystInstance.getFabricUIManager(); - } + public abstract @Nullable UIManager getFabricUIManager(); /** * Get the sourceURL for the JS bundle from the CatalystInstance. This method is needed for @@ -612,18 +500,13 @@ public boolean isBridgeless() { * * @return The JS bundle URL set when the bundle was loaded */ - public @Nullable String getSourceURL() { - return mCatalystInstance == null ? null : mCatalystInstance.getSourceURL(); - } + public abstract @Nullable String getSourceURL(); /** * Register a JS segment after loading it from cache or server, make sure mCatalystInstance is * properly initialised and not null before calling. */ - public void registerSegment(int segmentId, String path, Callback callback) { - Assertions.assertNotNull(mCatalystInstance).registerSegment(segmentId, path); - Assertions.assertNotNull(callback).invoke(); - } + public abstract void registerSegment(int segmentId, String path, Callback callback); /** * Register a {@link JavaScriptModule} within the Interop Layer so that can be consumed whenever diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java index 97a1bedabef657..6c7b343d25b811 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java @@ -90,16 +90,30 @@ public CatalystInstance getCatalystInstance() { return new BridgelessCatalystInstance(mReactHost); } + @Deprecated + @Override + public boolean hasActiveCatalystInstance() { + return hasActiveReactInstance(); + } + @Override public boolean hasActiveReactInstance() { return mReactHost.isInstanceInitialized(); } + @Override + public boolean hasCatalystInstance() { + return false; + } + @Override public boolean hasReactInstance() { return mReactHost.isInstanceInitialized(); } + @Override + public void destroy() {} + DevSupportManager getDevSupportManager() { return mReactHost.getDevSupportManager(); } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java index 50c22442af047e..628c6796aabd15 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/uimanager/ThemedReactContext.java @@ -10,10 +10,17 @@ import android.app.Activity; import android.content.Context; import androidx.annotation.Nullable; +import com.facebook.react.bridge.Callback; +import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.JavaScriptContextHolder; +import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.LifecycleEventListener; +import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContext; import com.facebook.react.bridge.UIManager; +import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder; +import java.util.Collection; /** * Wraps {@link ReactContext} with the base {@link Context} passed into the constructor. It provides @@ -46,10 +53,7 @@ public ThemedReactContext( @Nullable String moduleName, int surfaceId) { super(base); - if (reactApplicationContext.hasCatalystInstance()) { - initializeWithInstance(reactApplicationContext.getCatalystInstance()); - } - initializeInteropModules(reactApplicationContext); + initializeFromOther(reactApplicationContext); mReactApplicationContext = reactApplicationContext; mModuleName = moduleName; mSurfaceId = surfaceId; @@ -75,6 +79,58 @@ public boolean hasCurrentActivity() { return mReactApplicationContext.getCurrentActivity(); } + @Override + public T getJSModule(Class jsInterface) { + return mReactApplicationContext.getJSModule(jsInterface); + } + + @Override + public boolean hasNativeModule(Class nativeModuleInterface) { + return mReactApplicationContext.hasNativeModule(nativeModuleInterface); + } + + @Override + public Collection getNativeModules() { + return mReactApplicationContext.getNativeModules(); + } + + @Nullable + @Override + public T getNativeModule(Class nativeModuleInterface) { + return mReactApplicationContext.getNativeModule(nativeModuleInterface); + } + + @Override + public CatalystInstance getCatalystInstance() { + return mReactApplicationContext.getCatalystInstance(); + } + + @Deprecated + @Override + public boolean hasActiveCatalystInstance() { + return mReactApplicationContext.hasActiveCatalystInstance(); + } + + @Override + public boolean hasActiveReactInstance() { + return mReactApplicationContext.hasActiveCatalystInstance(); + } + + @Override + public boolean hasCatalystInstance() { + return mReactApplicationContext.hasCatalystInstance(); + } + + @Override + public boolean hasReactInstance() { + return mReactApplicationContext.hasReactInstance(); + } + + @Override + public void destroy() { + mReactApplicationContext.destroy(); + } + /** * This is misnamed but has some uses out in the wild. It will be deleted in a future release of * RN. @@ -103,16 +159,42 @@ public ReactApplicationContext getReactApplicationContext() { return mReactApplicationContext; } + @Override + public void handleException(Exception e) { + mReactApplicationContext.handleException(e); + } + + @Deprecated @Override public boolean isBridgeless() { return mReactApplicationContext.isBridgeless(); } + @Nullable + @Override + public JavaScriptContextHolder getJavaScriptContextHolder() { + return mReactApplicationContext.getJavaScriptContextHolder(); + } + + @Nullable + @Override + public CallInvokerHolder getJSCallInvokerHolder() { + return mReactApplicationContext.getJSCallInvokerHolder(); + } + @Override public UIManager getFabricUIManager() { - if (isBridgeless()) { - return mReactApplicationContext.getFabricUIManager(); - } - return super.getFabricUIManager(); + return mReactApplicationContext.getFabricUIManager(); + } + + @Nullable + @Override + public String getSourceURL() { + return mReactApplicationContext.getSourceURL(); + } + + @Override + public void registerSegment(int segmentId, String path, Callback callback) { + mReactApplicationContext.registerSegment(segmentId, path, callback); } }