From a88d7d30faa7bc1d18afdcc1b3b2ab194a221bb8 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Mon, 22 Sep 2025 09:54:18 -0700 Subject: [PATCH 1/3] Align Android with iOS in displaying HMR "refreshing" in color (#53846) Summary: Changelog: [Android][Added] - hot reload banner is now displayed in blue background like on iOS iOS has a colorful "Refreshing..." banner: {F1982086204} While android doesn't respect the color HMR asks it to set up for the banner: {F1982086218} Reviewed By: cortinico Differential Revision: D82726743 --- .../DefaultDevLoadingViewImplementation.kt | 14 ++++++++++++-- .../devsupport/interfaces/DevLoadingViewManager.kt | 2 ++ .../react/modules/devloading/DevLoadingModule.kt | 4 +++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevLoadingViewImplementation.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevLoadingViewImplementation.kt index 7e06ff82c4ce..330c2e89acde 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevLoadingViewImplementation.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/DefaultDevLoadingViewImplementation.kt @@ -33,10 +33,14 @@ public class DefaultDevLoadingViewImplementation( private var devLoadingPopup: PopupWindow? = null override fun showMessage(message: String) { + showMessage(message, color = null, backgroundColor = null) + } + + override fun showMessage(message: String, color: Double?, backgroundColor: Double?) { if (!isEnabled) { return } - UiThreadUtil.runOnUiThread { showInternal(message) } + UiThreadUtil.runOnUiThread { showInternal(message, color, backgroundColor) } } override fun updateProgress(status: String?, done: Int?, total: Int?) { @@ -59,7 +63,7 @@ public class DefaultDevLoadingViewImplementation( } } - private fun showInternal(message: String) { + private fun showInternal(message: String, color: Double?, backgroundColor: Double?) { if (devLoadingPopup?.isShowing == true) { // already showing return @@ -84,6 +88,12 @@ public class DefaultDevLoadingViewImplementation( currentActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater val view = inflater.inflate(R.layout.dev_loading_view, null) as TextView view.text = message + if (color != null) { + view.setTextColor(color.toInt()) + } + if (backgroundColor != null) { + view.setBackgroundColor(backgroundColor.toInt()) + } val popup = PopupWindow( view, diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevLoadingViewManager.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevLoadingViewManager.kt index 08d4757a4c5b..9366cfb3b300 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevLoadingViewManager.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/devsupport/interfaces/DevLoadingViewManager.kt @@ -11,6 +11,8 @@ package com.facebook.react.devsupport.interfaces public interface DevLoadingViewManager { public fun showMessage(message: String) + public fun showMessage(message: String, color: Double?, backgroundColor: Double?) + public fun updateProgress(status: String?, done: Int?, total: Int?) public fun hide() diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.kt index 7344fcdad8dd..78a3a84146d9 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/devloading/DevLoadingModule.kt @@ -31,7 +31,9 @@ internal class DevLoadingModule(reactContext: ReactApplicationContext) : } override fun showMessage(message: String, color: Double?, backgroundColor: Double?) { - UiThreadUtil.runOnUiThread { devLoadingViewManager?.showMessage(message) } + UiThreadUtil.runOnUiThread { + devLoadingViewManager?.showMessage(message, color, backgroundColor) + } } override fun hide() { From fe2ca247c2d703ab647695052e05d08834176252 Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Mon, 22 Sep 2025 09:54:18 -0700 Subject: [PATCH 2/3] add disconnection message (#53883) Summary: Changelog: [General][Added] - new banner added to indicate that Fast Refresh has lost connection, and that the app needs to be restarted to reconnect When HMR were getting disconnected, we were not giving any indication for the user. This lead to situations were the connection is re-established, but HMR is still disconnected, which is unexpected for users. Instead, raise a banner hinting the user to reload the app to reconnect. Reviewed By: huntie Differential Revision: D82726853 --- .../Libraries/Utilities/DevLoadingView.js | 20 +++++++++++++------ .../Libraries/Utilities/HMRClient.js | 11 ++++++---- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/packages/react-native/Libraries/Utilities/DevLoadingView.js b/packages/react-native/Libraries/Utilities/DevLoadingView.js index f43bb86b2969..338a5614df35 100644 --- a/packages/react-native/Libraries/Utilities/DevLoadingView.js +++ b/packages/react-native/Libraries/Utilities/DevLoadingView.js @@ -14,29 +14,37 @@ import NativeDevLoadingView from './NativeDevLoadingView'; const COLOR_SCHEME = { dark: { + load: { + backgroundColor: '#fafafa', + textColor: '#242526', + }, refresh: { backgroundColor: '#2584e8', textColor: '#ffffff', }, - load: { - backgroundColor: '#fafafa', - textColor: '#242526', + error: { + backgroundColor: '#1065AF', + textColor: '#ffffff', }, }, default: { + load: { + backgroundColor: '#404040', + textColor: '#ffffff', + }, refresh: { backgroundColor: '#2584e8', textColor: '#ffffff', }, - load: { - backgroundColor: '#404040', + error: { + backgroundColor: '#1065AF', textColor: '#ffffff', }, }, }; export default { - showMessage(message: string, type: 'load' | 'refresh') { + showMessage(message: string, type: 'load' | 'refresh' | 'error') { if (NativeDevLoadingView) { const colorScheme = getColorScheme() === 'dark' ? COLOR_SCHEME.dark : COLOR_SCHEME.default; diff --git a/packages/react-native/Libraries/Utilities/HMRClient.js b/packages/react-native/Libraries/Utilities/HMRClient.js index 6d16b6b973f3..f466f7a7f99a 100644 --- a/packages/react-native/Libraries/Utilities/HMRClient.js +++ b/packages/react-native/Libraries/Utilities/HMRClient.js @@ -232,8 +232,6 @@ Error: ${e.message}`; }); client.on('error', data => { - DevLoadingView.hide(); - if (data.type === 'GraphNotFoundError') { client.close(); setHMRUnavailableReason( @@ -253,8 +251,6 @@ Error: ${e.message}`; }); client.on('close', closeEvent => { - DevLoadingView.hide(); - // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 // https://www.rfc-editor.org/rfc/rfc6455.html#section-7.1.5 const isNormalOrUnsetCloseReason = @@ -296,10 +292,17 @@ function setHMRUnavailableReason(reason: string) { } hmrUnavailableReason = reason; + const DevLoadingView = require('./DevLoadingView').default; + DevLoadingView.hide(); + // We only want to show a warning if Fast Refresh is on *and* if we ever // previously managed to connect successfully. We don't want to show // the warning to native engineers who use cached bundles without Metro. if (hmrClient.isEnabled() && didConnect) { + DevLoadingView.showMessage( + 'Fast Refresh disconnected. Reload app to reconnect.', + 'error', + ); console.warn(reason); // (Not using the `warning` module to prevent a Buck cycle.) } From f08646a933776f72c128944bb9fed013e015959a Mon Sep 17 00:00:00 2001 From: Vitali Zaidman Date: Mon, 22 Sep 2025 09:54:18 -0700 Subject: [PATCH 3/3] remove connection hints from being logged for the user Summary: Changelog: [General][Internal] Don't display connection hints in Metro Now that there's a clear display for when HMR is disconnected, we don't want to show messages regardless devices connection and disconnection to the DevServer because they are too partial. Reviewed By: huntie Differential Revision: D82727105 --- .../src/inspector-proxy/InspectorProxy.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js index 23df229f056a..fd82442f2366 100644 --- a/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js +++ b/packages/dev-middleware/src/inspector-proxy/InspectorProxy.js @@ -372,12 +372,6 @@ export default class InspectorProxy implements InspectorProxyQueries { this.#devices.set(deviceId, newDevice); - this.#logger?.info( - "Connection established to app='%s' on device='%s'.", - appName, - deviceName, - ); - debug( "Got new device connection: name='%s', app=%s, device=%s, via=%s", deviceName, @@ -450,7 +444,7 @@ export default class InspectorProxy implements InspectorProxyQueries { ); socket.on('close', (code: number, reason: string) => { - this.#logger?.info( + debug( "Connection closed to device='%s' for app='%s' with code='%s' and reason='%s'.", deviceName, appName, @@ -526,7 +520,7 @@ export default class InspectorProxy implements InspectorProxyQueries { throw new Error(INTERNAL_ERROR_MESSAGES.UNREGISTERED_DEVICE); } - this.#logger?.info( + debug( "Connection established to DevTools for app='%s' on device='%s'.", device.getApp() || 'unknown', device.getName() || 'unknown', @@ -594,7 +588,7 @@ export default class InspectorProxy implements InspectorProxyQueries { }); socket.on('close', (code: number, reason: string) => { - this.#logger?.info( + debug( "Connection closed to DevTools for app='%s' on device='%s' with code='%s' and reason='%s'.", device.getApp() || 'unknown', device.getName() || 'unknown',