You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Android: Add setBundleSource method to ReactHost for changing bundle URL at runtime (facebook#54139)
Summary:
Following the [RFC](react-native-community/discussions-and-proposals#933), this PR adds new `setBundleSource` methods to `ReactHost` for modifying bundle URL at runtime. The first one with signature:
```Kotlin
public fun setBundleSource(debugServerHost: String, moduleName: String, queryBuilder: (Map<String, String>) -> Map<String, String> = { it })
```
takes debugServerHost (set in packager connection settings), moduleName (set in DevSupportManager's jsAppBundleName), and queryBuilder (set in packager connection settings). Before updating settings, the packager connection is closed to reset the packager client, which will be newly created during reload with updated configuration.
The second one for loading bundle from the file takes single `filePath` argument:
```Kotlin
public fun setBundleSource(filePath: String)
```
It sets `customBundleFilePath` in `DevSupportManager` which has priority over other methods of loading the bundle in `jsBundleLoader` and reloads `ReactHost`.
## Changelog:
[ANDROID][ADDED] - added new `setBundleSource` method to `ReactHost` for changing bundle URL at runtime.
Test Plan:
Started with running two Metro instances on ports `8081` and `8082` (first with white background, second with blue). Created a native button that toggles `debugServerHost` port and invokes `setBundleSource`.
https://github.com/user-attachments/assets/7afe2cbc-6fef-44bc-930c-e9f9c4edd2bd
For setting bundle file path, generated JS bundle with different background comparing to the one serving by Metro. Moved file to the `app/files` directory in android emulator and configured native button to invoke a `setBundleSource(filePath)`.
https://github.com/user-attachments/assets/5e59d7b7-c6ae-475c-94e3-50d4ac69cf24
<details>
<summary>code:</summary>
Changing debug server host:
`RNTesterActivity.kt`:
```Kotlin
package com.facebook.react.uiapp
import android.content.res.Configuration
import android.graphics.Color
import android.os.Bundle
import android.view.View
import android.widget.Button
import android.widget.FrameLayout
import androidx.core.graphics.drawable.toDrawable
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import com.facebook.common.logging.FLog
import com.facebook.react.FBRNTesterEndToEndHelper
import com.facebook.react.ReactActivity
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
import com.facebook.react.defaults.DefaultReactActivityDelegate
import java.io.FileDescriptor
import java.io.PrintWriter
internal class RNTesterActivity : ReactActivity() {
private var activePort = "8081"
class RNTesterActivityDelegate(val activity: ReactActivity, mainComponentName: String) :
DefaultReactActivityDelegate(activity, mainComponentName, fabricEnabled) {
private val PARAM_ROUTE = "route"
private lateinit var initialProps: Bundle
override fun onCreate(savedInstanceState: Bundle?) {
// Get remote param before calling super which uses it
val bundle = activity.intent?.extras
if (bundle != null && bundle.containsKey(PARAM_ROUTE)) {
val routeUri = "rntester://example/${bundle.getString(PARAM_ROUTE)}Example"
initialProps = Bundle().apply { putString("exampleFromAppetizeParams", routeUri) }
}
FBRNTesterEndToEndHelper.onCreate(activity.application)
super.onCreate(savedInstanceState)
}
override fun getLaunchOptions() =
if (this::initialProps.isInitialized) initialProps else Bundle()
}
private fun getButtonText(): String {
return "Port: $activePort"
}
private fun setupPortButton(onClick: () -> Unit) {
val portButton = Button(this).apply {
text = getButtonText()
setBackgroundColor(Color.rgb(0, 123, 255)) // Blue background
setTextColor(Color.WHITE)
setPadding(32, 16, 32, 16)
textSize = 16f
elevation = 8f
}
// Get the root view and add button to it
val rootView = this.findViewById<FrameLayout>(android.R.id.content)
val layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT
).apply {
gravity = android.view.Gravity.TOP or android.view.Gravity.CENTER_HORIZONTAL
topMargin = 200 // 50dp from top
}
rootView.addView(portButton, layoutParams)
portButton.setOnClickListener {
onClick()
portButton.text = getButtonText()
}
}
// set background color so it will show below transparent system bars on forced edge-to-edge
private fun maybeUpdateBackgroundColor() {
val isDarkMode =
resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK ==
Configuration.UI_MODE_NIGHT_YES
val color =
if (isDarkMode) {
Color.rgb(11, 6, 0)
} else {
Color.rgb(243, 248, 255)
}
window?.setBackgroundDrawable(color.toDrawable())
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fullyDrawnReporter.addReporter()
maybeUpdateBackgroundColor()
reactDelegate?.reactHost?.let {
setupPortButton {
activePort = if (activePort == "8081") "8082" else "8081"
reactHost.setBundleSource("10.0.2.2:$activePort", "js/RNTesterApp.android")
// reactHost.setBundleSource("/data/user/0/com.facebook.react.uiapp/files/android.bundle")
}
}
// register insets listener to update margins on the ReactRootView to avoid overlap w/ system
// bars
reactDelegate?.reactRootView?.let { rootView ->
val insetsType: Int =
WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.displayCutout()
val windowInsetsListener = { view: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(insetsType)
(view.layoutParams as FrameLayout.LayoutParams).apply {
setMargins(insets.left, insets.top, insets.right, insets.bottom)
}
WindowInsetsCompat.CONSUMED
}
ViewCompat.setOnApplyWindowInsetsListener(rootView, windowInsetsListener)
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
// update background color on UI mode change
maybeUpdateBackgroundColor()
}
override fun createReactActivityDelegate() = RNTesterActivityDelegate(this, mainComponentName)
override fun getMainComponentName() = "RNTesterApp"
override fun dump(
prefix: String,
fd: FileDescriptor?,
writer: PrintWriter,
args: Array<String>?,
) {
FBRNTesterEndToEndHelper.maybeDump(prefix, writer, args)
}
}
```
</detail>
Differential Revision: D84713639
Pulled By: coado
Copy file name to clipboardExpand all lines: packages/react-native/ReactAndroid/api/ReactAndroid.api
+13-1Lines changed: 13 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -239,6 +239,9 @@ public abstract interface class com/facebook/react/ReactHost {
239
239
public abstract fun reload (Ljava/lang/String;)Lcom/facebook/react/interfaces/TaskInterface;
240
240
public abstract fun removeBeforeDestroyListener (Lkotlin/jvm/functions/Function0;)V
241
241
public abstract fun removeReactInstanceEventListener (Lcom/facebook/react/ReactInstanceEventListener;)V
242
+
public fun setBundleSource (Ljava/lang/String;)V
243
+
public fun setBundleSource (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
244
+
public static synthetic fun setBundleSource$default (Lcom/facebook/react/ReactHost;Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
242
245
public fun setDevMenuConfiguration (Lcom/facebook/react/devsupport/DevMenuConfiguration;)V
243
246
public abstract fun start ()Lcom/facebook/react/interfaces/TaskInterface;
244
247
}
@@ -1908,7 +1911,7 @@ public final class com/facebook/react/devsupport/DevMenuConfiguration {
1908
1911
public class com/facebook/react/devsupport/DevServerHelper {
1909
1912
public fun <init> (Lcom/facebook/react/modules/debug/interfaces/DeveloperSettings;Landroid/content/Context;Lcom/facebook/react/packagerconnection/PackagerConnectionSettings;)V
1910
1913
public final fun closeInspectorConnection ()V
1911
-
public final fun closePackagerConnection ()V
1914
+
public final fun closePackagerConnection ()Landroid/os/AsyncTask;
1912
1915
public final fun disableDebugger ()V
1913
1916
public final fun downloadBundleFromURL (Lcom/facebook/react/devsupport/interfaces/DevBundleDownloadListener;Ljava/io/File;Ljava/lang/String;Lcom/facebook/react/devsupport/BundleDownloader$BundleInfo;)V
1914
1917
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
@@ -1943,6 +1946,7 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/
1943
1946
public fun downloadBundleResourceFromUrlSync (Ljava/lang/String;Ljava/io/File;)Ljava/io/File;
1944
1947
public final fun fetchSplitBundleAndCreateBundleLoader (Ljava/lang/String;Lcom/facebook/react/devsupport/DevSupportManagerBase$CallbackWithBundleLoader;)V
1945
1948
protected final fun getApplicationContext ()Landroid/content/Context;
1949
+
public fun getBundleFilePath ()Ljava/lang/String;
1946
1950
public fun getCurrentActivity ()Landroid/app/Activity;
1947
1951
public final fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
1948
1952
public final fun getDevLoadingViewManager ()Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;
@@ -1977,11 +1981,13 @@ public abstract class com/facebook/react/devsupport/DevSupportManagerBase : com/
1977
1981
public fun reloadJSFromServer (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/BundleLoadCallback;)V
1978
1982
public fun reloadSettings ()V
1979
1983
public fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V
1984
+
public fun setBundleFilePath (Ljava/lang/String;)V
1980
1985
public final fun setDevLoadingViewManager (Lcom/facebook/react/devsupport/interfaces/DevLoadingViewManager;)V
1981
1986
public fun setDevMenuEnabled (Z)V
1982
1987
public final fun setDevSupportEnabled (Z)V
1983
1988
public fun setFpsDebugEnabled (Z)V
1984
1989
public fun setHotModuleReplacementEnabled (Z)V
1990
+
public final fun setJsAppBundleName (Ljava/lang/String;)V
1985
1991
public fun setKeyboardShortcutsEnabled (Z)V
1986
1992
public final fun setLastErrorCookie (I)V
1987
1993
public final fun setLastErrorStack ([Lcom/facebook/react/devsupport/interfaces/StackFrame;)V
@@ -2151,6 +2157,7 @@ public abstract interface class com/facebook/react/devsupport/interfaces/DevSupp
2151
2157
public abstract fun createSurfaceDelegate (Ljava/lang/String;)Lcom/facebook/react/common/SurfaceDelegate;
2152
2158
public abstract fun destroyRootView (Landroid/view/View;)V
2153
2159
public abstract fun downloadBundleResourceFromUrlSync (Ljava/lang/String;Ljava/io/File;)Ljava/io/File;
2160
+
public fun getBundleFilePath ()Ljava/lang/String;
2154
2161
public abstract fun getCurrentActivity ()Landroid/app/Activity;
2155
2162
public abstract fun getCurrentReactContext ()Lcom/facebook/react/bridge/ReactContext;
2156
2163
public fun getDevMenuEnabled ()Z
@@ -2180,6 +2187,7 @@ public abstract interface class com/facebook/react/devsupport/interfaces/DevSupp
2180
2187
public abstract fun reloadJSFromServer (Ljava/lang/String;Lcom/facebook/react/devsupport/interfaces/BundleLoadCallback;)V
2181
2188
public abstract fun reloadSettings ()V
2182
2189
public abstract fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V
2190
+
public fun setBundleFilePath (Ljava/lang/String;)V
2183
2191
public fun setDevMenuEnabled (Z)V
2184
2192
public abstract fun setDevSupportEnabled (Z)V
2185
2193
public abstract fun setFpsDebugEnabled (Z)V
@@ -3021,6 +3029,8 @@ public class com/facebook/react/packagerconnection/PackagerConnectionSettings {
3021
3029
public fun resetDebugServerHost ()V
3022
3030
public final fun setAdditionalOptionForPackager (Ljava/lang/String;Ljava/lang/String;)V
3023
3031
public fun setDebugServerHost (Ljava/lang/String;)V
3032
+
public final fun setPackagerOptionsUpdater (Lkotlin/jvm/functions/Function1;)V
3033
+
public final fun updatePackagerOptions (Ljava/util/Map;)Ljava/util/Map;
3024
3034
}
3025
3035
3026
3036
public final class com/facebook/react/packagerconnection/ReconnectingWebSocket : okhttp3/WebSocketListener {
@@ -3099,6 +3109,8 @@ public final class com/facebook/react/runtime/ReactHostImpl : com/facebook/react
3099
3109
public fun reload (Ljava/lang/String;)Lcom/facebook/react/interfaces/TaskInterface;
3100
3110
public fun removeBeforeDestroyListener (Lkotlin/jvm/functions/Function0;)V
3101
3111
public fun removeReactInstanceEventListener (Lcom/facebook/react/ReactInstanceEventListener;)V
3112
+
public fun setBundleSource (Ljava/lang/String;)V
3113
+
public fun setBundleSource (Ljava/lang/String;Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
3102
3114
public fun setDevMenuConfiguration (Lcom/facebook/react/devsupport/DevMenuConfiguration;)V
3103
3115
public fun start ()Lcom/facebook/react/interfaces/TaskInterface;
Copy file name to clipboardExpand all lines: packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevSupportManager.kt
+4Lines changed: 4 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -48,6 +48,10 @@ public interface DevSupportManager : JSExceptionHandler {
Copy file name to clipboardExpand all lines: packages/react-native/ReactAndroid/src/main/java/com/facebook/react/packagerconnection/PackagerConnectionSettings.kt
+17-15Lines changed: 17 additions & 15 deletions
Original file line number
Diff line number
Diff line change
@@ -4,30 +4,24 @@
4
4
* This source code is licensed under the MIT license found in the
5
5
* LICENSE file in the root directory of this source tree.
6
6
*/
7
-
8
-
@file:Suppress("DEPRECATION") // PreferenceManager should be migrated to androidx
0 commit comments