expo-dev-launcher 6.1.0-canary-20251009-9919e08 → 6.1.0-canary-20251015-a6a1272

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.
Files changed (21) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/android/build.gradle +2 -2
  3. package/android/src/debug/java/expo/modules/devlauncher/DevLauncherController.kt +14 -9
  4. package/android/src/debug/java/expo/modules/devlauncher/helpers/DevLauncherReactUtils.kt +4 -78
  5. package/android/src/debug/java/expo/modules/devlauncher/launcher/errors/DevLauncherUncaughtExceptionHandler.kt +1 -1
  6. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoader.kt +2 -2
  7. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherAppLoaderFactory.kt +2 -2
  8. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherExpoAppLoader.kt +2 -2
  9. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherLocalAppLoader.kt +2 -2
  10. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherPublishedAppLoader.kt +2 -2
  11. package/android/src/debug/java/expo/modules/devlauncher/launcher/loaders/DevLauncherReactNativeAppLoader.kt +2 -2
  12. package/android/src/debug/java/expo/modules/devlauncher/react/DevLauncherDevSupportManagerSwapper.kt +1 -34
  13. package/android/src/main/java/expo/modules/devlauncher/launcher/DevLauncherControllerInterface.kt +2 -2
  14. package/android/src/release/java/expo/modules/devlauncher/DevLauncherController.kt +9 -5
  15. package/ios/EXDevLauncherController.m +33 -2
  16. package/ios/SwiftUI/Data.swift +2 -0
  17. package/ios/SwiftUI/DevLauncherViewModel.swift +0 -1
  18. package/ios/SwiftUI/HomeTabView.swift +0 -1
  19. package/ios/SwiftUI/SettingsTabView.swift +1 -1
  20. package/package.json +4 -4
  21. package/ios/EXDevLauncherDevMenuExtensions.m +0 -38
package/CHANGELOG.md CHANGED
@@ -10,11 +10,14 @@
10
10
 
11
11
  ### 🐛 Bug fixes
12
12
 
13
+ - [iOS] Fixes updates not being viewable on first launch. ([#40324](https://github.com/expo/expo/pull/40324) by [@alanjhughes](https://github.com/alanjhughes))
14
+
13
15
  ### 💡 Others
14
16
 
15
17
  - [Android] Migrated from `kotlinOptions` to `compilerOptions` DSL. ([#39794](https://github.com/expo/expo/pull/39794) by [@huextrat](https://github.com/huextrat))
16
18
  - [android] Make reactNativeHost optional in ReactHostWrapper ([#40085](https://github.com/expo/expo/pull/40085) by [@gabrieldonadel](https://github.com/gabrieldonadel))
17
19
  - [Android] Prevented the app from crashing during the initialization of the `ErrorViewModel`. ([#40148](https://github.com/expo/expo/pull/40148) by [@lukmccall](https://github.com/lukmccall))
20
+ - [Android] Remove `ReactHostWrapper` ([#40295](https://github.com/expo/expo/pull/40295) by [@gabrieldonadel](https://github.com/gabrieldonadel))
18
21
 
19
22
  ## 6.0.13 - 2025-10-01
20
23
 
@@ -20,13 +20,13 @@ expoModule {
20
20
  }
21
21
 
22
22
  group = "host.exp.exponent"
23
- version = "6.1.0-canary-20251009-9919e08"
23
+ version = "6.1.0-canary-20251015-a6a1272"
24
24
 
25
25
  android {
26
26
  namespace "expo.modules.devlauncher"
27
27
  defaultConfig {
28
28
  versionCode 9
29
- versionName "6.1.0-canary-20251009-9919e08"
29
+ versionName "6.1.0-canary-20251015-a6a1272"
30
30
  }
31
31
 
32
32
  buildTypes {
@@ -13,9 +13,9 @@ import androidx.core.net.toUri
13
13
  import com.facebook.react.ReactActivity
14
14
  import com.facebook.react.ReactActivityDelegate
15
15
  import com.facebook.react.ReactApplication
16
+ import com.facebook.react.ReactHost
16
17
  import com.facebook.react.ReactPackage
17
18
  import com.facebook.react.bridge.ReactContext
18
- import expo.interfaces.devmenu.ReactHostWrapper
19
19
  import expo.modules.devlauncher.helpers.DevLauncherInstallationIDHelper
20
20
  import expo.modules.devlauncher.helpers.DevLauncherMetadataHelper
21
21
  import expo.modules.devlauncher.helpers.DevLauncherUrl
@@ -64,7 +64,7 @@ class DevLauncherController private constructor() :
64
64
  private val context: Context by lazy {
65
65
  DevLauncherKoinContext.app.koin.get()
66
66
  }
67
- override val appHost: ReactHostWrapper by inject()
67
+ override val appHost: ReactHost by inject()
68
68
  private val httpClient: OkHttpClient by inject()
69
69
  private val lifecycle: DevLauncherLifecycle by inject()
70
70
  private val pendingIntentRegistry: DevLauncherIntentRegistryInterface by inject()
@@ -302,8 +302,8 @@ class DevLauncherController private constructor() :
302
302
  return false
303
303
  }
304
304
 
305
- private fun ensureHostWasCleared(host: ReactHostWrapper, activityToBeInvalidated: ReactActivity? = null) {
306
- if (host.hasInstance) {
305
+ private fun ensureHostWasCleared(host: ReactHost, activityToBeInvalidated: ReactActivity? = null) {
306
+ if (host.currentReactContext?.hasActiveReactInstance() == true) {
307
307
  runBlockingOnMainThread {
308
308
  networkInterceptor?.close()
309
309
  networkInterceptor = null
@@ -328,8 +328,8 @@ class DevLauncherController private constructor() :
328
328
  }
329
329
 
330
330
  @UiThread
331
- private fun clearHost(host: ReactHostWrapper, activityToBeInvalidated: ReactActivity?) {
332
- host.destroy()
331
+ private fun clearHost(host: ReactHost, activityToBeInvalidated: ReactActivity?) {
332
+ host.destroy("DevLauncher reloading app", null)
333
333
  activityToBeInvalidated?.let {
334
334
  invalidateActivity(it)
335
335
  }
@@ -423,7 +423,7 @@ class DevLauncherController private constructor() :
423
423
  }
424
424
 
425
425
  @JvmStatic
426
- internal fun initialize(context: Context, reactHost: ReactHostWrapper) {
426
+ internal fun initialize(context: Context, reactHost: ReactHost) {
427
427
  try {
428
428
  val splashScreenManagerClass = Class.forName("expo.modules.splashscreen.SplashScreenManager")
429
429
  val splashScreenManager = splashScreenManagerClass
@@ -464,14 +464,19 @@ class DevLauncherController private constructor() :
464
464
  }
465
465
 
466
466
  @JvmStatic
467
- fun initialize(context: Context, reactHost: ReactHostWrapper, launcherClass: Class<*>? = null) {
467
+ fun initialize(context: Context, reactHost: ReactHost, launcherClass: Class<*>? = null) {
468
468
  initialize(context, reactHost)
469
469
  sLauncherClass = launcherClass
470
470
  }
471
471
 
472
472
  @JvmStatic
473
473
  fun initialize(reactApplication: ReactApplication, additionalPackages: List<ReactPackage>? = null, launcherClass: Class<*>? = null) {
474
- initialize(reactApplication as Context, ReactHostWrapper(null, { reactApplication.reactHost }))
474
+ val reactHost = reactApplication.reactHost
475
+ checkNotNull(reactHost) {
476
+ "DevLauncherController.initialize() was called before reactHost was initialized"
477
+ }
478
+
479
+ initialize(reactApplication as Context, reactHost)
475
480
  sAdditionalPackages = additionalPackages
476
481
  sLauncherClass = launcherClass
477
482
  }
@@ -17,7 +17,6 @@ import com.facebook.react.devsupport.interfaces.DevSupportManager
17
17
  import com.facebook.react.modules.systeminfo.AndroidInfoHelpers
18
18
  import com.facebook.react.runtime.ReactHostDelegate
19
19
  import com.facebook.react.runtime.ReactHostImpl
20
- import expo.interfaces.devmenu.ReactHostWrapper
21
20
  import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
22
21
  import expo.modules.devlauncher.react.DevLauncherBridgeDevSupportManager
23
22
  import expo.modules.devlauncher.react.DevLauncherBridgelessDevSupportManager
@@ -31,7 +30,7 @@ private const val EXPO_REACT_HOST_DELEGATE_CLASS = "expo.modules.ExpoReactHostFa
31
30
 
32
31
  fun injectReactInterceptor(
33
32
  context: Context,
34
- reactHost: ReactHostWrapper,
33
+ reactHost: ReactHost,
35
34
  url: Uri
36
35
  ): Boolean {
37
36
  val (debugServerHost, appBundleName) = parseUrl(url)
@@ -44,15 +43,11 @@ fun injectReactInterceptor(
44
43
  debugServerHost,
45
44
  appBundleName
46
45
  )
47
- if (reactHost.isBridgelessMode) {
48
- (reactHost.devSupportManager as? DevLauncherBridgelessDevSupportManager)?.startInspectorWhenDevLauncherReady()
49
- } else {
50
- (reactHost.devSupportManager as? DevLauncherBridgeDevSupportManager)?.startInspectorWhenDevLauncherReady()
51
- }
46
+ (reactHost.devSupportManager as? DevLauncherBridgelessDevSupportManager)?.startInspectorWhenDevLauncherReady()
52
47
  return result
53
48
  }
54
49
 
55
- private fun injectDevSupportManager(reactHost: ReactHostWrapper) {
50
+ private fun injectDevSupportManager(reactHost: ReactHost) {
56
51
  DevLauncherDevSupportManagerSwapper().swapDevSupportManagerImpl(reactHost)
57
52
 
58
53
  // Swapping dev support manager overrides dev menu setup.
@@ -60,41 +55,6 @@ private fun injectDevSupportManager(reactHost: ReactHostWrapper) {
60
55
  DevMenuManager.initializeWithReactHost(reactHost)
61
56
  }
62
57
 
63
- fun injectDebugServerHost(
64
- context: Context,
65
- reactHost: ReactHostWrapper,
66
- debugServerHost: String,
67
- appBundleName: String
68
- ): Boolean {
69
- return if (reactHost.isBridgelessMode) {
70
- injectDebugServerHost(context, reactHost.reactHost, debugServerHost, appBundleName)
71
- } else {
72
- injectDebugServerHost(context, reactHost.reactNativeHost, debugServerHost, appBundleName)
73
- }
74
- }
75
-
76
- fun injectDebugServerHost(
77
- context: Context,
78
- reactNativeHost: ReactNativeHost,
79
- debugServerHost: String,
80
- appBundleName: String
81
- ): Boolean {
82
- return try {
83
- val instanceManager = reactNativeHost.reactInstanceManager
84
- val devSupportManager = instanceManager.devSupportManager
85
- injectDebugServerHost(context, devSupportManager, debugServerHost, appBundleName)
86
-
87
- // set useDeveloperSupport to true in case it was previously set to false from loading a published app
88
- val mUseDeveloperSupportField = instanceManager.javaClass.getDeclaredField("mUseDeveloperSupport")
89
- mUseDeveloperSupportField.isAccessible = true
90
- mUseDeveloperSupportField[instanceManager] = true
91
- true
92
- } catch (e: Exception) {
93
- Log.e("DevLauncher", "Unable to inject debug server host settings.", e)
94
- false
95
- }
96
- }
97
-
98
58
  fun injectDebugServerHost(
99
59
  context: Context,
100
60
  reactHost: ReactHost,
@@ -140,42 +100,8 @@ private fun injectDebugServerHost(
140
100
  packagerConnectionSettingsField[devServerHelper] = settings.public_getPackagerConnectionSettings()
141
101
  }
142
102
 
143
- fun injectLocalBundleLoader(
144
- reactHost: ReactHostWrapper,
145
- bundlePath: String
146
- ): Boolean {
147
- return if (reactHost.isBridgelessMode) {
148
- injectLocalBundleLoader(reactHost.reactHost, bundlePath)
149
- } else {
150
- injectLocalBundleLoader(reactHost.reactNativeHost, bundlePath)
151
- }
152
- }
153
-
154
- private fun injectLocalBundleLoader(
155
- reactNativeHost: ReactNativeHost,
156
- bundlePath: String
157
- ): Boolean {
158
- return try {
159
- val instanceManager = reactNativeHost.reactInstanceManager
160
- val instanceManagerClass = instanceManager.javaClass
161
-
162
- val jsBundleLoader = JSBundleLoader.createFileLoader(bundlePath)
163
- val mBundleLoaderField = instanceManagerClass.getDeclaredField("mBundleLoader")
164
- mBundleLoaderField.isAccessible = true
165
- mBundleLoaderField[instanceManager] = jsBundleLoader
166
-
167
- val mUseDeveloperSupportField = instanceManagerClass.getDeclaredField("mUseDeveloperSupport")
168
- mUseDeveloperSupportField.isAccessible = true
169
- mUseDeveloperSupportField[instanceManager] = false
170
- true
171
- } catch (e: Exception) {
172
- Log.e("DevLauncher", "Unable to load local bundle file", e)
173
- false
174
- }
175
- }
176
-
177
103
  @OptIn(UnstableReactNativeAPI::class)
178
- private fun injectLocalBundleLoader(
104
+ fun injectLocalBundleLoader(
179
105
  reactHost: ReactHost,
180
106
  bundlePath: String
181
107
  ): Boolean {
@@ -102,7 +102,7 @@ class DevLauncherUncaughtExceptionHandler(
102
102
  private fun tryToSendExceptionToBundler(exception: Throwable) {
103
103
  if (
104
104
  controller.mode != DevLauncherController.Mode.APP ||
105
- !controller.appHost.hasInstance ||
105
+ controller.appHost.currentReactContext?.hasActiveReactInstance() != true ||
106
106
  controller.appHost.currentReactContext === null
107
107
  ) {
108
108
  return
@@ -5,9 +5,9 @@ import android.content.Intent
5
5
  import androidx.lifecycle.DefaultLifecycleObserver
6
6
  import androidx.lifecycle.LifecycleOwner
7
7
  import com.facebook.react.ReactActivity
8
+ import com.facebook.react.ReactHost
8
9
  import com.facebook.react.ReactInstanceEventListener
9
10
  import com.facebook.react.bridge.ReactContext
10
- import expo.interfaces.devmenu.ReactHostWrapper
11
11
  import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
12
12
  import kotlinx.coroutines.Dispatchers
13
13
  import kotlinx.coroutines.withContext
@@ -35,7 +35,7 @@ import kotlin.coroutines.suspendCoroutine
35
35
  * - `onReactContext` - is called after the `ReactContext` was loaded.
36
36
  */
37
37
  abstract class DevLauncherAppLoader(
38
- private val appHost: ReactHostWrapper,
38
+ private val appHost: ReactHost,
39
39
  private val context: Context,
40
40
  private val controller: DevLauncherControllerInterface
41
41
  ) {
@@ -2,8 +2,8 @@ package expo.modules.devlauncher.launcher.loaders
2
2
 
3
3
  import android.content.Context
4
4
  import android.net.Uri
5
+ import com.facebook.react.ReactHost
5
6
  import expo.modules.devlauncher.helpers.DevLauncherInstallationIDHelper
6
- import expo.interfaces.devmenu.ReactHostWrapper
7
7
  import expo.modules.devlauncher.helpers.createUpdatesConfigurationWithUrl
8
8
  import expo.modules.devlauncher.helpers.loadUpdate
9
9
  import expo.modules.devlauncher.koin.DevLauncherKoinComponent
@@ -22,7 +22,7 @@ interface DevLauncherAppLoaderFactoryInterface {
22
22
 
23
23
  class DevLauncherAppLoaderFactory : DevLauncherKoinComponent, DevLauncherAppLoaderFactoryInterface {
24
24
  private val context: Context by inject()
25
- private val appHost: ReactHostWrapper by inject()
25
+ private val appHost: ReactHost by inject()
26
26
  private val updatesInterface: UpdatesInterface? by optInject()
27
27
  private val controller: DevLauncherControllerInterface by inject()
28
28
  private val installationIDHelper: DevLauncherInstallationIDHelper by inject()
@@ -5,9 +5,9 @@ import android.graphics.Color
5
5
  import android.util.Log
6
6
  import android.view.View
7
7
  import com.facebook.react.ReactActivity
8
+ import com.facebook.react.ReactHost
8
9
  import com.facebook.react.bridge.ReactContext
9
10
  import com.facebook.react.modules.appearance.AppearanceModule
10
- import expo.interfaces.devmenu.ReactHostWrapper
11
11
  import expo.modules.devlauncher.helpers.isValidColor
12
12
  import expo.modules.devlauncher.helpers.setProtectedDeclaredField
13
13
  import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
@@ -17,7 +17,7 @@ import expo.modules.manifests.core.Manifest
17
17
 
18
18
  abstract class DevLauncherExpoAppLoader(
19
19
  private val manifest: Manifest,
20
- appHost: ReactHostWrapper,
20
+ appHost: ReactHost,
21
21
  context: Context,
22
22
  controller: DevLauncherControllerInterface,
23
23
  private val activityConfigurator: DevLauncherExpoActivityConfigurator =
@@ -2,7 +2,7 @@ package expo.modules.devlauncher.launcher.loaders
2
2
 
3
3
  import android.content.Context
4
4
  import android.net.Uri
5
- import expo.interfaces.devmenu.ReactHostWrapper
5
+ import com.facebook.react.ReactHost
6
6
  import expo.modules.devlauncher.helpers.injectReactInterceptor
7
7
  import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
8
8
  import expo.modules.manifests.core.Manifest
@@ -13,7 +13,7 @@ import expo.modules.manifests.core.Manifest
13
13
  */
14
14
  class DevLauncherLocalAppLoader(
15
15
  private val manifest: Manifest,
16
- private val appHost: ReactHostWrapper,
16
+ private val appHost: ReactHost,
17
17
  private val context: Context,
18
18
  controller: DevLauncherControllerInterface
19
19
  ) : DevLauncherExpoAppLoader(manifest, appHost, context, controller) {
@@ -1,7 +1,7 @@
1
1
  package expo.modules.devlauncher.launcher.loaders
2
2
 
3
3
  import android.content.Context
4
- import expo.interfaces.devmenu.ReactHostWrapper
4
+ import com.facebook.react.ReactHost
5
5
  import expo.modules.devlauncher.helpers.injectLocalBundleLoader
6
6
  import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
7
7
  import expo.modules.manifests.core.Manifest
@@ -13,7 +13,7 @@ import expo.modules.manifests.core.Manifest
13
13
  class DevLauncherPublishedAppLoader(
14
14
  manifest: Manifest,
15
15
  private val localBundlePath: String,
16
- private val appHost: ReactHostWrapper,
16
+ private val appHost: ReactHost,
17
17
  context: Context,
18
18
  controller: DevLauncherControllerInterface
19
19
  ) : DevLauncherExpoAppLoader(manifest, appHost, context, controller) {
@@ -2,13 +2,13 @@ package expo.modules.devlauncher.launcher.loaders
2
2
 
3
3
  import android.content.Context
4
4
  import android.net.Uri
5
- import expo.interfaces.devmenu.ReactHostWrapper
5
+ import com.facebook.react.ReactHost
6
6
  import expo.modules.devlauncher.helpers.injectReactInterceptor
7
7
  import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
8
8
 
9
9
  class DevLauncherReactNativeAppLoader(
10
10
  private val url: Uri,
11
- private val appHost: ReactHostWrapper,
11
+ private val appHost: ReactHost,
12
12
  private val context: Context,
13
13
  controller: DevLauncherControllerInterface
14
14
  ) : DevLauncherAppLoader(appHost, context, controller) {
@@ -11,7 +11,6 @@ import com.facebook.react.devsupport.ReleaseDevSupportManager
11
11
  import com.facebook.react.devsupport.interfaces.DevSupportManager
12
12
  import com.facebook.react.packagerconnection.JSPackagerClient
13
13
  import com.facebook.react.runtime.ReactHostImpl
14
- import expo.interfaces.devmenu.ReactHostWrapper
15
14
  import expo.modules.devlauncher.helpers.getProtectedFieldValue
16
15
  import expo.modules.devlauncher.helpers.setProtectedDeclaredField
17
16
  import expo.modules.devlauncher.koin.DevLauncherKoinComponent
@@ -23,39 +22,7 @@ import org.koin.core.component.inject
23
22
  internal class DevLauncherDevSupportManagerSwapper : DevLauncherKoinComponent {
24
23
  private val controller: DevLauncherControllerInterface by inject()
25
24
 
26
- fun swapDevSupportManagerImpl(reactHost: ReactHostWrapper) {
27
- if (reactHost.isBridgelessMode) {
28
- swapDevSupportManagerImpl(reactHost.reactHost)
29
- } else {
30
- swapDevSupportManagerImpl(reactHost.reactNativeHost)
31
- }
32
- }
33
-
34
- private fun swapDevSupportManagerImpl(reactNativeHost: ReactNativeHost) {
35
- val reactInstanceManager = reactNativeHost.reactInstanceManager
36
- val currentDevSupportManager = reactInstanceManager.devSupportManager
37
- if (currentDevSupportManager is DevLauncherBridgeDevSupportManager) {
38
- // DevSupportManager was swapped by the DevLauncherReactNativeHostHandler
39
- return
40
- }
41
- if (currentDevSupportManager is ReleaseDevSupportManager) {
42
- Log.i("DevLauncher", "DevSupportManager is disabled. So we don't want to override it.")
43
- return
44
- }
45
-
46
- try {
47
- val devManagerClass = DevSupportManagerBase::class.java
48
- val newDevSupportManager = createDevLauncherBridgeDevSupportManager(devManagerClass, currentDevSupportManager)
49
-
50
- ReactInstanceManager::class.java.setProtectedDeclaredField(reactInstanceManager, "devSupportManager", newDevSupportManager)
51
-
52
- closeExistingConnection(devManagerClass, currentDevSupportManager)
53
- } catch (e: Exception) {
54
- Log.i("DevLauncher", "Couldn't inject `DevLauncherDevSupportManager`.", e)
55
- }
56
- }
57
-
58
- private fun swapDevSupportManagerImpl(reactHost: ReactHost) {
25
+ fun swapDevSupportManagerImpl(reactHost: ReactHost) {
59
26
  val currentDevSupportManager = requireNotNull(reactHost.devSupportManager)
60
27
  if (currentDevSupportManager is DevLauncherBridgelessDevSupportManager) {
61
28
  // DevSupportManager was swapped by the DevLauncherReactNativeHostHandler
@@ -4,9 +4,9 @@ import android.content.Intent
4
4
  import android.net.Uri
5
5
  import com.facebook.react.ReactActivity
6
6
  import com.facebook.react.ReactActivityDelegate
7
+ import com.facebook.react.ReactHost
7
8
  import com.facebook.react.bridge.ReactContext
8
9
  import expo.modules.devlauncher.DevLauncherController
9
- import expo.interfaces.devmenu.ReactHostWrapper
10
10
  import expo.modules.manifests.core.Manifest
11
11
  import expo.modules.updatesinterface.UpdatesInterface
12
12
  import expo.modules.updatesinterface.UpdatesInterfaceCallbacks
@@ -27,7 +27,7 @@ interface DevLauncherControllerInterface :
27
27
  val manifest: Manifest?
28
28
  val manifestURL: Uri?
29
29
  val mode: DevLauncherController.Mode
30
- val appHost: ReactHostWrapper
30
+ val appHost: ReactHost
31
31
  val latestLoadedApp: Uri?
32
32
  val useDeveloperSupport: Boolean
33
33
  var updatesInterface: UpdatesInterface?
@@ -6,8 +6,8 @@ import android.net.Uri
6
6
  import com.facebook.react.ReactActivity
7
7
  import com.facebook.react.ReactActivityDelegate
8
8
  import com.facebook.react.ReactApplication
9
+ import com.facebook.react.ReactHost
9
10
  import com.facebook.react.bridge.ReactContext
10
- import expo.interfaces.devmenu.ReactHostWrapper
11
11
  import expo.modules.devlauncher.launcher.DevLauncherAppEntry
12
12
  import expo.modules.devlauncher.launcher.DevLauncherControllerInterface
13
13
  import expo.modules.devlauncher.launcher.DevLauncherReactActivityDelegateSupplier
@@ -34,7 +34,7 @@ class DevLauncherController private constructor() : DevLauncherControllerInterfa
34
34
  override val manifestURL: Uri
35
35
  get() = throw IllegalStateException(DEV_LAUNCHER_IS_NOT_AVAILABLE)
36
36
 
37
- override val appHost: ReactHostWrapper
37
+ override val appHost: ReactHost
38
38
  get() = throw IllegalStateException(DEV_LAUNCHER_IS_NOT_AVAILABLE)
39
39
 
40
40
  override var updatesInterface: UpdatesInterface?
@@ -96,19 +96,23 @@ class DevLauncherController private constructor() : DevLauncherControllerInterfa
96
96
  }
97
97
 
98
98
  @JvmStatic
99
- internal fun initialize(context: Context, reactHost: ReactHostWrapper) {
99
+ internal fun initialize(context: Context, reactHost: ReactHost) {
100
100
  check(sInstance == null) { "DevelopmentClientController was initialized." }
101
101
  sInstance = DevLauncherController()
102
102
  }
103
103
 
104
104
  @JvmStatic
105
- fun initialize(context: Context, reactHost: ReactHostWrapper, launcherClass: Class<*>? = null) {
105
+ fun initialize(context: Context, reactHost: ReactHost, launcherClass: Class<*>? = null) {
106
106
  initialize(context, reactHost)
107
107
  }
108
108
 
109
109
  @JvmStatic
110
110
  fun initialize(reactApplication: ReactApplication, additionalPackages: List<*>? = null, launcherClass: Class<*>? = null) {
111
- initialize(reactApplication as Context, ReactHostWrapper(reactApplication.reactNativeHost, { reactApplication.reactHost }))
111
+ val reactHost = reactApplication.reactHost
112
+ checkNotNull(reactHost) {
113
+ "DevLauncherController.initialize() was called before reactHost was initialized"
114
+ }
115
+ initialize(reactApplication as Context, reactHost)
112
116
  }
113
117
 
114
118
  @JvmStatic
@@ -686,10 +686,28 @@
686
686
  NSString *appVersion = [self getFormattedAppVersion];
687
687
  NSString *appName = [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleDisplayName"] ?: [[NSBundle mainBundle] objectForInfoDictionaryKey: @"CFBundleExecutable"];
688
688
 
689
+ NSString *sdkVersion = nil;
690
+ if (self.manifest != nil) {
691
+ NSDictionary *expoConfig = [self.manifest expoClientConfigRootObject];
692
+ id sdk = expoConfig[@"sdkVersion"];
693
+ if ([sdk isKindOfClass:[NSString class]]) {
694
+ sdkVersion = (NSString *)sdk;
695
+ } else {
696
+ NSDictionary *rawManifest = [self.manifest rawManifestJSON];
697
+ id sdkFromManifest = rawManifest[@"sdkVersion"];
698
+ if ([sdkFromManifest isKindOfClass:[NSString class]]) {
699
+ sdkVersion = (NSString *)sdkFromManifest;
700
+ }
701
+ }
702
+ }
703
+
689
704
  [buildInfo setObject:appName forKey:@"appName"];
690
705
  [buildInfo setObject:appIcon forKey:@"appIcon"];
691
706
  [buildInfo setObject:appVersion forKey:@"appVersion"];
692
707
  [buildInfo setObject:runtimeVersion forKey:@"runtimeVersion"];
708
+ if (sdkVersion) {
709
+ [buildInfo setObject:sdkVersion forKey:@"sdkVersion"];
710
+ }
693
711
 
694
712
  return buildInfo;
695
713
  }
@@ -775,17 +793,30 @@
775
793
 
776
794
  // the project url field is added to app.json.updates when running `eas update:configure`
777
795
  // the `u.expo.dev` determines that it is the modern manifest protocol
796
+ NSURL *updateURL = _updatesInterface ? _updatesInterface.updateURL : nil;
778
797
  NSString *projectUrl = @"";
779
798
  if (_updatesInterface) {
780
- projectUrl = [[self.manifest updatesInfo] valueForKey:@"url"];
799
+ projectUrl = [[self.manifest updatesInfo] valueForKey:@"url"] ?: @"";
800
+ if (projectUrl.length == 0 && updateURL) {
801
+ projectUrl = updateURL.absoluteString ?: @"";
802
+ }
781
803
  }
782
804
 
783
- NSURL *url = [NSURL URLWithString:projectUrl];
805
+ NSURL *url = projectUrl.length > 0 ? [NSURL URLWithString:projectUrl] : updateURL;
784
806
 
785
807
  BOOL isModernManifestProtocol = [[url host] isEqualToString:@"u.expo.dev"] || [[url host] isEqualToString:@"staging-u.expo.dev"];
786
808
  BOOL expoUpdatesInstalled = EXDevLauncherController.sharedInstance.updatesInterface != nil;
787
809
 
788
810
  NSString *appId = [constants valueForKeyPath:@"manifest.extra.eas.projectId"] ?: [self.manifest easProjectId];
811
+ if (appId.length == 0 && updateURL) {
812
+ NSString *possibleAppId = updateURL.lastPathComponent ?: @"";
813
+ if (possibleAppId.length == 0 && updateURL.pathComponents.count > 0) {
814
+ possibleAppId = updateURL.pathComponents.lastObject ?: @"";
815
+ }
816
+ if (possibleAppId.length > 0 && ![possibleAppId isEqualToString:@"/"]) {
817
+ appId = possibleAppId;
818
+ }
819
+ }
789
820
  BOOL hasAppId = appId.length > 0;
790
821
 
791
822
  BOOL usesEASUpdates = isModernManifestProtocol && expoUpdatesInstalled && hasAppId;
@@ -16,12 +16,14 @@ struct BuildInfo {
16
16
  let runtimeVersion: String
17
17
  let usesEASUpdates: Bool
18
18
  let projectUrl: String?
19
+ let sdkVersion: String?
19
20
 
20
21
  init(buildInfo: [AnyHashable: Any], updatesConfig: [AnyHashable: Any]) {
21
22
  self.appId = (updatesConfig["appId"] as? String) ?? (buildInfo["appId"] as? String) ?? ""
22
23
  self.runtimeVersion = (updatesConfig["runtimeVersion"] as? String) ?? (buildInfo["runtimeVersion"] as? String) ?? ""
23
24
  self.usesEASUpdates = updatesConfig["usesEASUpdates"] as? Bool ?? false
24
25
  self.projectUrl = updatesConfig["projectUrl"] as? String
26
+ self.sdkVersion = buildInfo["sdkVersion"] as? String
25
27
  }
26
28
  }
27
29
 
@@ -212,7 +212,6 @@ class DevLauncherViewModel: ObservableObject {
212
212
 
213
213
  private func saveMenuPreference(key: String, value: Bool) {
214
214
  UserDefaults.standard.set(value, forKey: key)
215
- UserDefaults.standard.synchronize()
216
215
  }
217
216
 
218
217
  private func checkAuthenticationStatus() {
@@ -5,7 +5,6 @@ import SwiftUI
5
5
 
6
6
  struct HomeTabView: View {
7
7
  @EnvironmentObject var viewModel: DevLauncherViewModel
8
- @State private var showingQRScanner = false
9
8
  @State private var showingInfoDialog = false
10
9
 
11
10
  var body: some View {
@@ -14,7 +14,7 @@ struct SettingsTabView: View {
14
14
  private func createBuildInfoJSON() -> String {
15
15
  let buildInfoDict: [String: Any] = [
16
16
  "runtimeVersion": viewModel.buildInfo["runtimeVersion"] as? String ?? "",
17
- "sdkVersion": "53.0.0",
17
+ "sdkVersion": viewModel.structuredBuildInfo.sdkVersion ?? "",
18
18
  "appName": viewModel.buildInfo["appName"] as? String ?? "",
19
19
  "appVersion": viewModel.buildInfo["appVersion"] as? String ?? ""
20
20
  ]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "expo-dev-launcher",
3
3
  "title": "Expo Development Launcher",
4
- "version": "6.1.0-canary-20251009-9919e08",
4
+ "version": "6.1.0-canary-20251015-a6a1272",
5
5
  "description": "Pre-release version of the Expo development launcher package for testing.",
6
6
  "repository": {
7
7
  "type": "git",
@@ -15,10 +15,10 @@
15
15
  "license": "MIT",
16
16
  "homepage": "https://docs.expo.dev",
17
17
  "dependencies": {
18
- "expo-dev-menu": "7.0.14-canary-20251009-9919e08",
19
- "expo-manifests": "1.0.9-canary-20251009-9919e08"
18
+ "expo-dev-menu": "7.0.15-canary-20251015-a6a1272",
19
+ "expo-manifests": "1.0.9-canary-20251015-a6a1272"
20
20
  },
21
21
  "peerDependencies": {
22
- "expo": "55.0.0-canary-20251009-9919e08"
22
+ "expo": "55.0.0-canary-20251015-a6a1272"
23
23
  }
24
24
  }
@@ -1,38 +0,0 @@
1
- #import <EXDevLauncher/EXDevLauncherController.h>
2
-
3
- @import EXDevMenuInterface;
4
-
5
- @interface EXDevLauncherDevMenuExtensions : NSObject <RCTBridgeModule>
6
-
7
- @end
8
-
9
- @implementation EXDevLauncherDevMenuExtensions
10
-
11
-
12
- // Need to explicitly define `moduleName` here for dev menu to pick it up
13
- RCT_EXTERN void RCTRegisterModule(Class);
14
-
15
- + (NSString *)moduleName
16
- {
17
- return @"EXDevLauncherExtension";
18
- }
19
-
20
- + (void)load
21
- {
22
- RCTRegisterModule(self);
23
- }
24
-
25
- + (BOOL)requiresMainQueueSetup {
26
- return YES;
27
- }
28
-
29
- RCT_EXPORT_METHOD(navigateToLauncherAsync:(RCTPromiseResolveBlock)resolve
30
- rejecter:(RCTPromiseRejectBlock)reject)
31
- {
32
- dispatch_async(dispatch_get_main_queue(), ^{
33
- [[EXDevLauncherController sharedInstance] navigateToLauncher];
34
- });
35
- resolve(nil);
36
- }
37
-
38
- @end