expo-dev-menu 56.0.16 → 56.0.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/android/build.gradle +2 -2
- package/android/src/debug/java/expo/modules/devmenu/compose/ui/MenuIcons.kt +16 -0
- package/android/src/debug/java/expo/modules/devmenu/compose/ui/ToolsSection.kt +44 -25
- package/android/src/debug/java/expo/modules/devmenu/devtools/DevMenuDevToolsDelegate.kt +30 -2
- package/ios/DevMenuManager.swift +34 -2
- package/ios/DevMenuPackagerConnectionHandler.swift +1 -1
- package/ios/Modules/DevMenuModule.swift +1 -1
- package/package.json +5 -5
- package/plugin/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -10,6 +10,17 @@
|
|
|
10
10
|
|
|
11
11
|
### 💡 Others
|
|
12
12
|
|
|
13
|
+
## 56.0.18 — 2026-07-01
|
|
14
|
+
|
|
15
|
+
### 🐛 Bug fixes
|
|
16
|
+
|
|
17
|
+
- [Android] Restore the "Open React Native dev menu" entry in the dev menu's Tools section (regressed in [#38759](https://github.com/expo/expo/pull/38759)). ([#47047](https://github.com/expo/expo/pull/47047) by [@lindboe](https://github.com/lindboe))
|
|
18
|
+
- [Android] Re-enable the Fast Refresh toggle in the dev menu's Tools section. ([#47136](https://github.com/expo/expo/pull/47136) by [@lukmccall](https://github.com/lukmccall))
|
|
19
|
+
|
|
20
|
+
## 56.0.17 — 2026-06-10
|
|
21
|
+
|
|
22
|
+
_This version does not introduce any user-facing changes._
|
|
23
|
+
|
|
13
24
|
## 56.0.16 — 2026-05-29
|
|
14
25
|
|
|
15
26
|
_This version does not introduce any user-facing changes._
|
package/android/build.gradle
CHANGED
|
@@ -12,7 +12,7 @@ apply plugin: 'expo-module-gradle-plugin'
|
|
|
12
12
|
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
|
|
13
13
|
|
|
14
14
|
group = 'host.exp.exponent'
|
|
15
|
-
version = '56.0.
|
|
15
|
+
version = '56.0.18'
|
|
16
16
|
|
|
17
17
|
def hasDevLauncher = findProject(":expo-dev-launcher") != null
|
|
18
18
|
def configureInRelease = findProperty("expo.devmenu.configureInRelease") == "true"
|
|
@@ -29,7 +29,7 @@ android {
|
|
|
29
29
|
|
|
30
30
|
defaultConfig {
|
|
31
31
|
versionCode 10
|
|
32
|
-
versionName '56.0.
|
|
32
|
+
versionName '56.0.18'
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
buildTypes {
|
|
@@ -122,6 +122,22 @@ object MenuIcons {
|
|
|
122
122
|
)
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
@Composable
|
|
126
|
+
fun Gear(
|
|
127
|
+
size: Dp,
|
|
128
|
+
tint: Color,
|
|
129
|
+
modifier: Modifier = Modifier
|
|
130
|
+
) {
|
|
131
|
+
Icon(
|
|
132
|
+
painter = painterResource(R.drawable.gear_fill),
|
|
133
|
+
contentDescription = "React Native dev menu",
|
|
134
|
+
tint = tint,
|
|
135
|
+
modifier = Modifier
|
|
136
|
+
.size(size)
|
|
137
|
+
.then(modifier)
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
@Composable
|
|
126
142
|
fun Refresh(
|
|
127
143
|
size: Dp,
|
|
@@ -86,31 +86,50 @@ fun ToolsSection(
|
|
|
86
86
|
}
|
|
87
87
|
)
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
89
|
+
Divider(thickness = 0.5.dp)
|
|
90
|
+
|
|
91
|
+
NewMenuButton(
|
|
92
|
+
withSurface = false,
|
|
93
|
+
icon = {
|
|
94
|
+
MenuIcons.Gear(
|
|
95
|
+
size = 20.dp,
|
|
96
|
+
tint = NewAppTheme.colors.icon.tertiary
|
|
97
|
+
)
|
|
98
|
+
},
|
|
99
|
+
content = {
|
|
100
|
+
NewText(
|
|
101
|
+
text = "Open React Native dev menu"
|
|
102
|
+
)
|
|
103
|
+
},
|
|
104
|
+
onClick = {
|
|
105
|
+
onAction(DevMenuAction.OpenReactNativeDevMenu)
|
|
106
|
+
}
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
Divider(thickness = 0.5.dp)
|
|
110
|
+
|
|
111
|
+
NewMenuButton(
|
|
112
|
+
withSurface = false,
|
|
113
|
+
icon = {
|
|
114
|
+
MenuIcons.Refresh(
|
|
115
|
+
size = 20.dp,
|
|
116
|
+
tint = NewAppTheme.colors.icon.tertiary
|
|
117
|
+
)
|
|
118
|
+
},
|
|
119
|
+
content = {
|
|
120
|
+
NewText(
|
|
121
|
+
text = "Fast Refresh"
|
|
122
|
+
)
|
|
123
|
+
},
|
|
124
|
+
rightComponent = {
|
|
125
|
+
ToggleSwitch(
|
|
126
|
+
isToggled = devToolsSettings.isHotLoadingEnabled
|
|
127
|
+
)
|
|
128
|
+
},
|
|
129
|
+
onClick = {
|
|
130
|
+
onAction(DevMenuAction.ToggleFastRefresh(!devToolsSettings.isHotLoadingEnabled))
|
|
131
|
+
}
|
|
132
|
+
)
|
|
114
133
|
|
|
115
134
|
// Hide FAB toggle on Quest devices since FAB is always on there
|
|
116
135
|
if (!VRUtilities.isQuest()) {
|
|
@@ -19,6 +19,7 @@ import kotlinx.coroutines.Dispatchers
|
|
|
19
19
|
import kotlinx.coroutines.GlobalScope
|
|
20
20
|
import kotlinx.coroutines.launch
|
|
21
21
|
import java.lang.ref.WeakReference
|
|
22
|
+
import java.lang.reflect.Proxy
|
|
22
23
|
|
|
23
24
|
class DevMenuDevToolsDelegate(
|
|
24
25
|
private val weakDevSupportManager: WeakReference<out DevSupportManager>
|
|
@@ -77,9 +78,9 @@ class DevMenuDevToolsDelegate(
|
|
|
77
78
|
internalSettings.isHotModuleReplacementEnabled = nextEnabled
|
|
78
79
|
|
|
79
80
|
if (nextEnabled) {
|
|
80
|
-
reactContext?.
|
|
81
|
+
reactContext?.callHMRClientMethod("enable")
|
|
81
82
|
} else {
|
|
82
|
-
reactContext?.
|
|
83
|
+
reactContext?.callHMRClientMethod("disable")
|
|
83
84
|
}
|
|
84
85
|
|
|
85
86
|
if (nextEnabled && !internalSettings.isJSDevModeEnabled) {
|
|
@@ -88,6 +89,33 @@ class DevMenuDevToolsDelegate(
|
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Invokes a no-argument [HMRClient] method (`enable`/`disable`) without tripping a bug in React
|
|
94
|
+
* Native's bridgeless JS module proxy.
|
|
95
|
+
*
|
|
96
|
+
* When a zero-argument method is invoked through the proxy returned by [ReactContext.getJSModule],
|
|
97
|
+
* the JDK passes a `null` args array to the invocation handler. In bridgeless mode that handler
|
|
98
|
+
* (`BridgelessReactContext.BridgelessJSModuleInvocationHandler`) declares the parameter non-null,
|
|
99
|
+
* so Kotlin's null check throws a [NullPointerException] before the call ever reaches JS. We
|
|
100
|
+
* invoke the handler directly with an explicit empty args array to sidestep the null. The legacy
|
|
101
|
+
* (bridge) handler tolerates the empty array too, so this is safe on both architectures.
|
|
102
|
+
*/
|
|
103
|
+
private fun ReactContext.callHMRClientMethod(methodName: String) {
|
|
104
|
+
val hmrClient = getJSModule(HMRClient::class.java) ?: return
|
|
105
|
+
val method = HMRClient::class.java.getMethod(methodName)
|
|
106
|
+
if (Proxy.isProxyClass(hmrClient.javaClass)) {
|
|
107
|
+
Proxy
|
|
108
|
+
.getInvocationHandler(hmrClient)
|
|
109
|
+
.invoke(
|
|
110
|
+
hmrClient,
|
|
111
|
+
method,
|
|
112
|
+
emptyArray<Any?>()
|
|
113
|
+
)
|
|
114
|
+
} else {
|
|
115
|
+
method.invoke(hmrClient)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
91
119
|
@OptIn(DelicateCoroutinesApi::class)
|
|
92
120
|
fun openJSInspector() {
|
|
93
121
|
val devSettings = devSettings ?: return
|
package/ios/DevMenuManager.swift
CHANGED
|
@@ -110,6 +110,9 @@ open class DevMenuManager: NSObject {
|
|
|
110
110
|
|
|
111
111
|
private var isNavigatingHome = false
|
|
112
112
|
|
|
113
|
+
private var isReloading = false
|
|
114
|
+
private var lastReloadEventAt: Date?
|
|
115
|
+
|
|
113
116
|
weak var hostDelegate: DevMenuHostDelegate?
|
|
114
117
|
|
|
115
118
|
@objc
|
|
@@ -214,6 +217,8 @@ open class DevMenuManager: NSObject {
|
|
|
214
217
|
public func setAppContext(_ appContext: AppContext?) {
|
|
215
218
|
currentAppContext = appContext
|
|
216
219
|
if appContext != nil {
|
|
220
|
+
isReloading = false
|
|
221
|
+
lastReloadEventAt = Date()
|
|
217
222
|
isNavigatingHome = false
|
|
218
223
|
isReactAppRunning = true
|
|
219
224
|
// Re-run packager connection setup now that the app context (and devSettings) is available.
|
|
@@ -225,6 +230,20 @@ open class DevMenuManager: NSObject {
|
|
|
225
230
|
updateAutoLaunchObserver()
|
|
226
231
|
}
|
|
227
232
|
|
|
233
|
+
/**
|
|
234
|
+
Clears the app context, but only if `context` is still the active one.
|
|
235
|
+
On reload the incoming context's `OnCreate` can run before the outgoing context's
|
|
236
|
+
`OnDestroy`, so an unconditional reset would wipe the new context and leave the dev
|
|
237
|
+
menu unable to open.
|
|
238
|
+
*/
|
|
239
|
+
@objc
|
|
240
|
+
public func clearAppContext(current context: AppContext?) {
|
|
241
|
+
if currentAppContext != nil && currentAppContext !== context {
|
|
242
|
+
return
|
|
243
|
+
}
|
|
244
|
+
setAppContext(nil)
|
|
245
|
+
}
|
|
246
|
+
|
|
228
247
|
@objc
|
|
229
248
|
public func updateCurrentManifest(_ manifest: Manifest?, manifestURL: URL?) {
|
|
230
249
|
currentManifest = manifest
|
|
@@ -524,8 +543,21 @@ open class DevMenuManager: NSObject {
|
|
|
524
543
|
}
|
|
525
544
|
|
|
526
545
|
func reload() {
|
|
527
|
-
let
|
|
528
|
-
|
|
546
|
+
let now = Date()
|
|
547
|
+
if isReloading || (lastReloadEventAt.map { now.timeIntervalSince($0) < 0.5 } ?? false) {
|
|
548
|
+
lastReloadEventAt = now
|
|
549
|
+
return
|
|
550
|
+
}
|
|
551
|
+
guard let devToolsDelegate = getDevToolsDelegate() else {
|
|
552
|
+
return
|
|
553
|
+
}
|
|
554
|
+
isReloading = true
|
|
555
|
+
lastReloadEventAt = now
|
|
556
|
+
// Clear the guard even if a new app context never registers, for example a failed reload.
|
|
557
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 5) { [weak self] in
|
|
558
|
+
self?.isReloading = false
|
|
559
|
+
}
|
|
560
|
+
devToolsDelegate.reload()
|
|
529
561
|
}
|
|
530
562
|
|
|
531
563
|
func togglePerformanceMonitor() {
|
|
@@ -10,7 +10,7 @@ open class DevMenuModule: Module {
|
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
OnDestroy {
|
|
13
|
-
DevMenuManager.shared.
|
|
13
|
+
DevMenuManager.shared.clearAppContext(current: self.appContext)
|
|
14
14
|
// Cleanup registered callbacks when the module is destroyed to prevent leaking into other bridges.
|
|
15
15
|
if DevMenuManager.wasInitilized {
|
|
16
16
|
DevMenuManager.shared.registeredCallbacks = []
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-dev-menu",
|
|
3
|
-
"version": "56.0.
|
|
3
|
+
"version": "56.0.18",
|
|
4
4
|
"description": "Expo/React Native module with the developer menu.",
|
|
5
5
|
"main": "build/DevMenu.js",
|
|
6
6
|
"types": "build/DevMenu.d.ts",
|
|
@@ -32,15 +32,15 @@
|
|
|
32
32
|
"@types/node": "^22.14.0",
|
|
33
33
|
"react": "19.2.3",
|
|
34
34
|
"react-native": "0.85.3",
|
|
35
|
-
"
|
|
36
|
-
"expo": "56.0.
|
|
37
|
-
"
|
|
35
|
+
"babel-preset-expo": "56.0.16",
|
|
36
|
+
"expo-module-scripts": "56.0.3",
|
|
37
|
+
"expo": "56.0.13"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"expo": "*",
|
|
41
41
|
"react-native": "*"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "b293744c7b199c6cbe910188863e93a174c21250",
|
|
44
44
|
"scripts": {
|
|
45
45
|
"build": "expo-module build",
|
|
46
46
|
"clean": "expo-module clean",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/withdevmenu.ts"],"version":"
|
|
1
|
+
{"root":["./src/index.ts","./src/withdevmenu.ts"],"version":"6.0.3"}
|