diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 0a50cc37974935..d82095cf7f63ec 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -239,6 +239,9 @@ public abstract interface class com/facebook/react/ReactHost { public abstract fun reload (Ljava/lang/String;)Lcom/facebook/react/interfaces/TaskInterface; public abstract fun removeBeforeDestroyListener (Lkotlin/jvm/functions/Function0;)V public abstract fun removeReactInstanceEventListener (Lcom/facebook/react/ReactInstanceEventListener;)V + public fun setBundleSource (Ljava/lang/String;)V + public fun setBundleSource (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun setBundleSource$default (Lcom/facebook/react/ReactHost;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V public abstract fun start ()Lcom/facebook/react/interfaces/TaskInterface; } @@ -1898,7 +1901,7 @@ public final class com/facebook/react/devsupport/DefaultDevLoadingViewImplementa public class com/facebook/react/devsupport/DevServerHelper { public fun (Lcom/facebook/react/modules/debug/interfaces/DeveloperSettings;Landroid/content/Context;Lcom/facebook/react/packagerconnection/PackagerConnectionSettings;)V public final fun closeInspectorConnection ()V - public final fun closePackagerConnection ()V + public final fun closePackagerConnection ()Landroid/os/AsyncTask; public final fun disableDebugger ()V public final fun downloadBundleFromURL (Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;Ljava/io/File;Ljava/lang/String;Lcom/facebook/react/devsupport/BundleDownloader$BundleInfo;)V public final fun downloadBundleFromURL (Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;Ljava/io/File;Ljava/lang/String;Lcom/facebook/react/devsupport/BundleDownloader$BundleInfo;Lokhttp3/Request$Builder;)V @@ -1925,7 +1928,8 @@ public abstract interface class com/facebook/react/devsupport/DevServerHelper$Pa public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/facebook/react/devsupport/interfaces/DevSupportManager { public static final field Companion Lcom/facebook/react/devsupport/DevSupportManagerBase$Companion; - public fun (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;)V + public fun (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;Ljava/lang/String;)V + public synthetic fun (Landroid/content/Context;Lcom/facebook/react/devsupport/ReactInstanceDevHelper;Ljava/lang/String;ZLcom/facebook/react/devsupport/interfaces/RedBoxHandler;Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;ILjava/util/Map;Lcom/facebook/react/common/SurfaceDelegateFactory;Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;Lcom/facebook/react/devsupport/interfaces/PausedInDebuggerOverlayManager;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun addCustomDevOption (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/DevOptionHandler;)V public fun createRootView (Ljava/lang/String;)Landroid/view/View; public fun createSurfaceDelegate (Ljava/lang/String;)Lcom/facebook/react/common/SurfaceDelegate; @@ -1935,6 +1939,7 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/ protected final fun getApplicationContext ()Landroid/content/Context; public fun getCurrentActivity ()Landroid/app/Activity; public final fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext; + public fun getCustomBundleFilePath ()Ljava/lang/String; public final fun getDevLoadingViewManager ()Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager; public final fun getDevServerHelper ()Lcom/facebook/react/devsupport/DevServerHelper; public final fun getDevSettings ()Lcom/facebook/react/modules/debug/interfaces/DeveloperSettings; @@ -1964,10 +1969,12 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/ public fun reloadJSFromServer (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/BundleLoadCallback;)V public fun reloadSettings ()V public fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V + public fun setCustomBundleFilePath (Ljava/lang/String;)V public final fun setDevLoadingViewManager (Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;)V public final fun setDevSupportEnabled (Z)V public fun setFpsDebugEnabled (Z)V public fun setHotModuleReplacementEnabled (Z)V + public final fun setJsAppBundleName (Ljava/lang/String;)V public final fun setLastErrorCookie (I)V public final fun setLastErrorStack ([Lcom/facebook/react/devsupport/interfaces/StackFrame;)V public final fun setLastErrorTitle (Ljava/lang/String;)V @@ -2131,6 +2138,7 @@ public abstract interface class com/facebook/react/devsupport/interfaces/DevSupp public abstract fun downloadBundleResourceFromUrlSync (Ljava/lang/String;Ljava/io/File;)Ljava/io/File; public abstract fun getCurrentActivity ()Landroid/app/Activity; public abstract fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext; + public fun getCustomBundleFilePath ()Ljava/lang/String; public abstract fun getDevSettings ()Lcom/facebook/react/modules/debug/interfaces/DeveloperSettings; public abstract fun getDevSupportEnabled ()Z public abstract fun getDownloadedJSBundleFile ()Ljava/lang/String; @@ -2155,6 +2163,7 @@ public abstract interface class com/facebook/react/devsupport/interfaces/DevSupp public abstract fun reloadJSFromServer (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/BundleLoadCallback;)V public abstract fun reloadSettings ()V public abstract fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V + public fun setCustomBundleFilePath (Ljava/lang/String;)V public abstract fun setDevSupportEnabled (Z)V public abstract fun setFpsDebugEnabled (Z)V public abstract fun setHotModuleReplacementEnabled (Z)V @@ -2993,6 +3002,8 @@ public class com/facebook/react/packagerconnection/PackagerConnectionSettings { public fun resetDebugServerHost ()V public final fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V public fun setDebugServerHost (Ljava/lang/String;)V + public final fun setPackagerOptionsUpdater (Lkotlin/jvm/functions/Function1;)V + public final fun updatePackagerOptions (Ljava/util/Map;)Ljava/util/Map; } public final class com/facebook/react/packagerconnection/ReconnectingWebSocket : okhttp3/WebSocketListener { @@ -3071,6 +3082,8 @@ public final class com/facebook/react/runtime/ReactHostImpl : com/facebook/react public fun reload (Ljava/lang/String;)Lcom/facebook/react/interfaces/TaskInterface; public fun removeBeforeDestroyListener (Lkotlin/jvm/functions/Function0;)V public fun removeReactInstanceEventListener (Lcom/facebook/react/ReactInstanceEventListener;)V + public fun setBundleSource (Ljava/lang/String;)V + public fun setBundleSource (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V public fun start ()Lcom/facebook/react/interfaces/TaskInterface; } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt index 8119107cd82266..e2e0172c9e5bab 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactHost.kt @@ -189,4 +189,21 @@ public interface ReactHost { /** Remove a listener previously added with [addReactInstanceEventListener]. */ public fun removeReactInstanceEventListener(listener: ReactInstanceEventListener) + + /** Sets the source of the bundle to be loaded from the file system. */ + public fun setBundleSource(filePath: String): Unit = Unit + + /** + * Sets the source of the bundle to be loaded from the packager server and updates the packager + * connection. + * + * @param debugServerHost host and port of the server, for example "localhost:8081" + * @param moduleName the module name to load, for example "js/RNTesterApp.android" + * @param queryBuilder a function that takes current packager options and returns updated options + */ + public fun setBundleSource( + debugServerHost: String, + moduleName: String, + queryBuilder: (Map) -> Map = { it }, + ): Unit = Unit } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.kt index d7777c3b8f7d06..5a8bab4b76137c 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevServerHelper.kt @@ -196,8 +196,9 @@ public open class DevServerHelper( .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) } - public fun closePackagerConnection() { - object : AsyncTask() { + public fun closePackagerConnection(): AsyncTask { + val task = + object : AsyncTask() { @Deprecated("This class needs to be rewritten to don't use AsyncTasks") override fun doInBackground(vararg params: Void): Void? { packagerClient?.close() @@ -205,7 +206,8 @@ public open class DevServerHelper( return null } } - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + return task } public fun openInspectorConnection() { @@ -280,7 +282,11 @@ public open class DevServerHelper( ): String { val dev = devMode val additionalOptionsBuilder = StringBuilder() - for ((key, value) in packagerConnectionSettings.additionalOptionsForPackager) { + val packagerOptions = + packagerConnectionSettings.updatePackagerOptions( + packagerConnectionSettings.additionalOptionsForPackager + ) + for ((key, value) in packagerOptions) { if (value.isEmpty()) { continue } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt index 79b09fca56dbe1..9ac54321e2225e 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DevSupportManagerBase.kt @@ -85,7 +85,7 @@ import java.util.Locale public abstract class DevSupportManagerBase( protected val applicationContext: Context, public val reactInstanceDevHelper: ReactInstanceDevHelper, - @get:JvmName("getJSAppBundleName") public val jsAppBundleName: String?, + @get:JvmName("getJSAppBundleName") public var jsAppBundleName: String?, enableOnCreate: Boolean, public override val redBoxHandler: RedBoxHandler?, private val devBundleDownloadListener: DevBundleDownloadListener?, @@ -94,6 +94,7 @@ public abstract class DevSupportManagerBase( private val surfaceDelegateFactory: SurfaceDelegateFactory?, public var devLoadingViewManager: DevLoadingViewManager?, private var pausedInDebuggerOverlayManager: PausedInDebuggerOverlayManager?, + private var customBundleFilePathField: String? = null, ) : DevSupportManager { public interface CallbackWithBundleLoader { @@ -132,6 +133,12 @@ public abstract class DevSupportManagerBase( reloadSettings() } + override var customBundleFilePath: String? + get() = customBundleFilePathField + set(value) { + customBundleFilePathField = value + } + override val sourceMapUrl: String get() = jsAppBundleName?.let { devServerHelper.getSourceMapUrl(it) } ?: "" diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt index 3356e1dc3ac56e..a45928a95e57a2 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt @@ -36,6 +36,10 @@ public interface DevSupportManager : JSExceptionHandler { public val currentActivity: Activity? public val currentReactContext: ReactContext? + public var customBundleFilePath: String? + get() = null + set(value) = Unit + public var devSupportEnabled: Boolean public fun showNewJavaError(message: String?, e: Throwable) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/PackagerConnectionSettings.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/PackagerConnectionSettings.kt index 6ac6731e07376c..642e930b106a3a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/PackagerConnectionSettings.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/PackagerConnectionSettings.kt @@ -4,30 +4,24 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ - -@file:Suppress("DEPRECATION") // PreferenceManager should be migrated to androidx - package com.facebook.react.packagerconnection import android.content.Context -import android.content.SharedPreferences -import android.preference.PreferenceManager import com.facebook.common.logging.FLog import com.facebook.react.modules.systeminfo.AndroidInfoHelpers public open class PackagerConnectionSettings(private val appContext: Context) { - private val preferences: SharedPreferences = - PreferenceManager.getDefaultSharedPreferences(appContext) public val packageName: String = appContext.packageName private val _additionalOptionsForPackager: MutableMap = mutableMapOf() + private var _packagerOptionsUpdater: (Map) -> Map = { it } + private var cachedHost: String? = null public open var debugServerHost: String get() { - // Check host setting first. If empty try to detect emulator type and use default + // Check cached host first. If empty try to detect emulator type and use default // hostname for those - val hostFromSettings = preferences.getString(PREFS_DEBUG_SERVER_HOST_KEY, null) - if (!hostFromSettings.isNullOrEmpty()) { - return hostFromSettings + cachedHost?.let { + return it } val host = AndroidInfoHelpers.getServerHost(appContext) if (host == AndroidInfoHelpers.DEVICE_LOCALHOST) { @@ -36,20 +30,29 @@ public open class PackagerConnectionSettings(private val appContext: Context) { "You seem to be running on device. Run '${AndroidInfoHelpers.getAdbReverseTcpCommand(appContext)}' to forward the debug server's port to the device.", ) } + + cachedHost = host return host } set(host) { if (host.isEmpty()) { - preferences.edit().remove(PREFS_DEBUG_SERVER_HOST_KEY).apply() + cachedHost = null } else { - preferences.edit().putString(PREFS_DEBUG_SERVER_HOST_KEY, host).apply() + cachedHost = host } } public open fun resetDebugServerHost() { - preferences.edit().remove(PREFS_DEBUG_SERVER_HOST_KEY).apply() + cachedHost = null } + public fun setPackagerOptionsUpdater(updater: (Map) -> Map) { + _packagerOptionsUpdater = updater + } + + public fun updatePackagerOptions(options: Map): Map = + _packagerOptionsUpdater(options) + public fun setAdditionalOptionForPackager(key: String, value: String) { _additionalOptionsForPackager[key] = value } @@ -59,6 +62,5 @@ public open class PackagerConnectionSettings(private val appContext: Context) { private companion object { private val TAG = PackagerConnectionSettings::class.java.simpleName - private const val PREFS_DEBUG_SERVER_HOST_KEY = "debug_http_host" } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt index c7e0c339c9029d..74cf64fcd58174 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/ReactHostImpl.kt @@ -73,6 +73,9 @@ import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicReference import kotlin.Unit import kotlin.concurrent.Volatile +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch /** * A ReactHost is an object that manages a single [ReactInstance]. A ReactHost can be constructed @@ -643,6 +646,29 @@ public class ReactHostImpl( } } + @ThreadConfined(value = ThreadConfined.UI) + override fun setBundleSource(filePath: String) { + devSupportManager.customBundleFilePath = filePath + reload("Change bundle source") + } + + @ThreadConfined(value = ThreadConfined.UI) + override fun setBundleSource( + debugServerHost: String, + moduleName: String, + queryBuilder: (Map) -> Map, + ) { + CoroutineScope(Dispatchers.Default).launch { + (devSupportManager as DevSupportManagerBase).devServerHelper.closePackagerConnection() + devSupportManager.devSettings.packagerConnectionSettings.let { it -> + it.setPackagerOptionsUpdater(queryBuilder) + it.debugServerHost = debugServerHost + } + devSupportManager.jsAppBundleName = moduleName + reload("Changed bundle source") + } + } + @ThreadConfined(ThreadConfined.UI) override fun onConfigurationChanged(context: Context) { val currentReactContext = this.currentReactContext @@ -1057,6 +1083,14 @@ public class ReactHostImpl( get() { stateTracker.enterState("getJSBundleLoader()") + if (devSupportManager.customBundleFilePath != null) { + return try { + Task.forResult(JSBundleLoader.createFileLoader(devSupportManager.customBundleFilePath!!)) + } catch (e: Exception) { + Task.forError(e) + } + } + if (useDevSupport && allowPackagerServerAccess) { return isMetroRunning.onSuccessTask( { task ->