Skip to content

Commit 53d3839

Browse files
committed
docs: add expo updates
1 parent e2ba7d0 commit 53d3839

5 files changed

Lines changed: 479 additions & 1 deletion

File tree

docs/docs/docs/guides/_meta.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,9 @@
1-
["guidelines", "troubleshooting"]
1+
[
2+
"guidelines",
3+
"troubleshooting",
4+
{
5+
"type": "dir",
6+
"name": "expo-updates",
7+
"label": "Expo Updates"
8+
}
9+
]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[
2+
"how-to",
3+
"expo-54",
4+
"expo-55"
5+
]
Lines changed: 241 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,241 @@
1+
# Expo 54: Manual Setup
2+
3+
:::danger Important
4+
The Expo Team does not plan to provide support for SDK54. Follow the patch below to enable the expo-updates integration manually.
5+
:::
6+
7+
8+
### iOS:
9+
10+
##### Step 1:
11+
12+
In `ios/EXUpdates/UpdatesConfig.swift` :
13+
14+
```diff
15+
@@ -141,7 +141,8 @@ public final class UpdatesConfig: NSObject {
16+
}
17+
18+
private static func configDictionaryWithExpoPlist(mergingOtherDictionary: [String: Any]?) throws -> [String: Any] {
19+
- guard let configPlistPath = Bundle.main.path(forResource: PlistName, ofType: "plist") else {
20+
+ let bundle = Bundle(for: UpdatesConfig.self)
21+
+ guard let configPlistPath = bundle.path(forResource: PlistName, ofType: "plist") else {
22+
throw UpdatesConfigError.ExpoUpdatesConfigPlistError
23+
}
24+
```
25+
26+
##### Step 2:
27+
28+
In `ios/EXUpdates/UpdatesUtils.swift` :
29+
30+
```diff
31+
@@ -108,18 +108,22 @@ public final class UpdatesUtils: NSObject {
32+
return assetFilesMap
33+
}
34+
35+
+ private static func getBundle() -> Bundle {
36+
+ return Bundle(for: UpdatesUtils.self)
37+
+ }
38+
+
39+
internal static func url(forBundledAsset asset: UpdateAsset) -> URL? {
40+
guard let mainBundleDir = asset.mainBundleDir else {
41+
- return Bundle.main.url(forResource: asset.mainBundleFilename, withExtension: asset.type)
42+
+ return getBundle().url(forResource: asset.mainBundleFilename, withExtension: asset.type)
43+
}
44+
- return Bundle.main.url(forResource: asset.mainBundleFilename, withExtension: asset.type, subdirectory: mainBundleDir)
45+
+ return getBundle().url(forResource: asset.mainBundleFilename, withExtension: asset.type, subdirectory: mainBundleDir)
46+
}
47+
48+
internal static func path(forBundledAsset asset: UpdateAsset) -> String? {
49+
guard let mainBundleDir = asset.mainBundleDir else {
50+
- return Bundle.main.path(forResource: asset.mainBundleFilename, ofType: asset.type)
51+
+ return getBundle().path(forResource: asset.mainBundleFilename, ofType: asset.type)
52+
}
53+
- return Bundle.main.path(forResource: asset.mainBundleFilename, ofType: asset.type, inDirectory: mainBundleDir)
54+
+ return getBundle().path(forResource: asset.mainBundleFilename, ofType: asset.type, inDirectory: mainBundleDir)
55+
}
56+
57+
```
58+
59+
### Android:
60+
61+
##### Step 1:
62+
63+
In `expo-updates/android/**/UpdatesController.kt` :
64+
65+
```diff
66+
@@ -2,6 +2,7 @@ package expo.modules.updates
67+
68+
import android.content.Context
69+
import com.facebook.react.ReactApplication
70+
+import com.facebook.react.ReactHost
71+
import expo.modules.updates.events.IUpdatesEventManagerObserver
72+
import expo.modules.updates.loader.LoaderTask
73+
import expo.modules.updates.logging.UpdatesErrorCode
74+
@@ -25,6 +26,19 @@ object UpdatesController {
75+
@Volatile
76+
private var overrideConfiguration: UpdatesConfiguration? = null
77+
78+
+ @Volatile
79+
+ private var reactHost: ReactHost? = null
80+
+
81+
+ @JvmStatic
82+
+ fun setReactHost(reactHost: ReactHost) {
83+
+ this.reactHost = reactHost
84+
+ }
85+
+
86+
+ @JvmStatic
87+
+ fun getReactHost(): ReactHost? {
88+
+ return reactHost
89+
+ }
90+
+
91+
```
92+
93+
##### Step 2:
94+
95+
In `expo-updates/android/**/RecreateReactContextProcedure.kt` :
96+
97+
```diff
98+
import android.content.Context
99+
-import com.facebook.react.ReactApplication
100+
import expo.modules.updates.launcher.Launcher
101+
import expo.modules.updates.statemachine.UpdatesStateEvent
102+
import kotlinx.coroutines.CoroutineScope
103+
@@ -20,16 +19,11 @@ class RecreateReactContextProcedure(
104+
override val loggerTimerLabel = "timer-recreate-react-context"
105+
106+
override suspend fun run(procedureContext: ProcedureContext) {
107+
- val reactApplication = context.applicationContext as? ReactApplication ?: run inner@{
108+
- callback.onFailure(Exception("Could not reload application. Ensure you have passed the correct instance of ReactApplication into UpdatesController.initialize()."))
109+
- return
110+
- }
111+
-
112+
procedureContext.processStateEvent(UpdatesStateEvent.Restart())
113+
callback.onSuccess()
114+
procedureScope.launch {
115+
withContext(Dispatchers.Main) {
116+
- reactApplication.restart(weakActivity?.get(), "Restart from RecreateReactContextProcedure")
117+
+ RestartReactAppExtensions.restart(weakActivity?.get(), "Restart from RecreateReactContextProcedure")
118+
}
119+
}
120+
```
121+
122+
##### Step 3:
123+
124+
In `expo-updates/android/**/RelaunchProcedure.kt` :
125+
126+
```diff
127+
@@ -43,11 +43,6 @@ class RelaunchProcedure(
128+
override val loggerTimerLabel = "timer-relaunch"
129+
130+
override suspend fun run(procedureContext: ProcedureContext) {
131+
- val reactApplication = context as? ReactApplication ?: run inner@{
132+
- callback.onFailure(Exception("Could not reload application. Ensure you have passed the correct instance of ReactApplication into UpdatesController.initialize()."))
133+
- return
134+
- }
135+
-
136+
procedureContext.processStateEvent(UpdatesStateEvent.Restart())
137+
138+
val oldLaunchAssetFile = getCurrentLauncher().launchAssetFile
139+
@@ -74,7 +69,7 @@ class RelaunchProcedure(
140+
val newLaunchAssetFile = getCurrentLauncher().launchAssetFile
141+
if (newLaunchAssetFile != null && newLaunchAssetFile != oldLaunchAssetFile) {
142+
try {
143+
- replaceLaunchAssetFileIfNeeded(reactApplication, newLaunchAssetFile)
144+
+ replaceLaunchAssetFileIfNeeded(newLaunchAssetFile)
145+
} catch (e: Exception) {
146+
logger.error("Could not reset launchAssetFile for the ReactApplication", e, UpdatesErrorCode.Unknown)
147+
}
148+
@@ -84,7 +79,7 @@ class RelaunchProcedure(
149+
procedureScope.launch {
150+
withContext(Dispatchers.Main) {
151+
reloadScreenManager?.show(weakActivity?.get())
152+
- reactApplication.restart(weakActivity?.get(), "Restart from RelaunchProcedure")
153+
+ RestartReactAppExtensions.restart(weakActivity?.get(), "Restart from RelaunchProcedure")
154+
}
155+
}
156+
157+
@@ -127,13 +122,16 @@ class RelaunchProcedure(
158+
* [com.facebook.react.ReactInstanceManager].
159+
*/
160+
private fun replaceLaunchAssetFileIfNeeded(
161+
- reactApplication: ReactApplication,
162+
launchAssetFile: String
163+
) {
164+
if (ReactNativeFeatureFlags.enableBridgelessArchitecture) {
165+
return
166+
}
167+
168+
+ val reactApplication = context as? ReactApplication ?: run inner@{
169+
+ callback.onFailure(Exception("Could not reload application. Ensure you have passed the correct instance of ReactApplication into UpdatesController.initialize()."))
170+
+ return
171+
+ }
172+
val instanceManager = reactApplication.reactNativeHost.reactInstanceManager
173+
```
174+
175+
##### Step 4:
176+
177+
In `expo-updates/android/**/RestartReactAppExtensions.kt` :
178+
179+
```diff
180+
import com.facebook.react.ReactApplication
181+
+import com.facebook.react.ReactHost
182+
+import com.facebook.react.ReactNativeHost
183+
import com.facebook.react.common.LifecycleState
184+
import expo.modules.rncompatibility.ReactNativeFeatureFlags
185+
+import expo.modules.updates.UpdatesController
186+
187+
-/**
188+
- * An extension for [ReactApplication] to restart the app
189+
- *
190+
- * @param activity For bridgeless mode if the ReactHost is destroyed, we need an Activity to resume it.
191+
- * @param reason The restart reason. Only used on bridgeless mode.
192+
- */
193+
-internal fun ReactApplication.restart(activity: Activity?, reason: String) {
194+
- if (ReactNativeFeatureFlags.enableBridgelessArchitecture) {
195+
- val reactHost = this.reactHost
196+
- check(reactHost != null)
197+
- if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
198+
- reactHost.onHostResume(activity)
199+
+private fun getReactHostReflectively(app: ReactApplication): ReactHost? {
200+
+ return runCatching {
201+
+ app.javaClass.getMethod("getReactHost").invoke(app) as? ReactHost
202+
+ }.getOrNull()
203+
+}
204+
+
205+
+private fun getReactNativeHostReflectively(app: ReactApplication): ReactNativeHost? {
206+
+ return runCatching {
207+
+ app.javaClass.getMethod("getReactNativeHost").invoke(app) as? ReactNativeHost
208+
+ }.getOrNull()
209+
+}
210+
+
211+
+object RestartReactAppExtensions {
212+
+ /**
213+
+ * A function to restart the app
214+
+ *
215+
+ * @param activity For bridgeless mode if the ReactHost is destroyed, we need an Activity to resume it.
216+
+ * @param reason The restart reason. Only used on bridgeless mode.
217+
+ */
218+
+ fun restart(activity: Activity?, reason: String) {
219+
+ if (ReactNativeFeatureFlags.enableBridgelessArchitecture) {
220+
+ val reactHost = UpdatesController.getReactHost() ?: getReactHostReflectively(activity?.application as ReactApplication)
221+
+ check(reactHost != null)
222+
+ if (reactHost.lifecycleState != LifecycleState.RESUMED && activity != null) {
223+
+ reactHost.onHostResume(activity)
224+
+ }
225+
+ reactHost.reload(reason)
226+
+ return
227+
}
228+
- reactHost.reload(reason)
229+
- return
230+
- }
231+
232+
- reactNativeHost.reactInstanceManager.recreateReactContextInBackground()
233+
+ getReactNativeHostReflectively(activity?.application as ReactApplication)?.reactInstanceManager?.recreateReactContextInBackground()
234+
+ }
235+
```
236+
237+
<hr/>
238+
239+
To see a complete patch, please visit:
240+
241+
- [SDK54](https://github.com/callstack/react-native-brownfield/tree/main/.yarn/patches/expo-updates-npm-29.0.16-1c5c89eb83.patch)

0 commit comments

Comments
 (0)