UNPKG

24.4 kBMarkdownView Raw
1# expo-updates
2
3`expo-updates` fetches and manages updates to your app stored on a remote server.
4
5## API documentation
6
7- [Documentation for the master branch](https://github.com/expo/expo/blob/master/docs/pages/versions/unversioned/sdk/updates.md)
8- [Documentation for the latest stable release](https://docs.expo.io/versions/latest/sdk/updates/)
9
10Additionally, for an introduction to this module and tooling around OTA updates, you can watch [this talk](https://www.youtube.com/watch?v=Si909la3rLk) by [@esamelson](https://github.com/esamelson) from ReactEurope 2020.
11
12## Compatibility
13
14This module requires `expo-cli@3.17.6` or later; make sure your global installation is at least this version before proceeding.
15
16Additionally, this module is only compatible with Expo SDK 37 or later. For bare workflow projects, if the `expo` package is installed, it must be version `37.0.2` or later.
17
18Finally, this module is not compatible with ExpoKit. Make sure you do not have `expokit` listed as a dependency in package.json before adding this module.
19
20## Upgrading
21
22If you're upgrading from `expo-updates@0.1.x`, you can opt into the **no-publish workflow**. In this workflow, release builds of both iOS and Android apps will create and embed a new update at build-time from the JS code currently on disk, rather than embedding a copy of the most recently published update. For instructions and more information, see the [CHANGELOG](https://github.com/expo/expo/blob/master/packages/expo-updates/CHANGELOG.md). (For new projects, the no-publish workflow is enabled by default.)
23
24# Installation in managed Expo projects
25
26For managed [managed](https://docs.expo.io/versions/latest/introduction/managed-vs-bare/) Expo projects, please follow the installation instructions in the [API documentation for the latest stable release](https://docs.expo.io/versions/latest/sdk/media-library/).
27
28# Installation in bare React Native projects
29
30For bare React Native projects, you must ensure that you have [installed and configured the `react-native-unimodules` package](https://github.com/unimodules/react-native-unimodules) before continuing.
31
32### Add the package to your npm dependencies
33
34```
35expo install expo-updates
36```
37
38### `expo-asset`
39
40(If you have the `expo` package installed in your project already and use `registerRootComponent` in your project entry point, you can skip this section!)
41
42If you have assets (such as images or other media) that are `require`d in your application code, and you'd like these to be included in updates, you'll also need to install the `expo-asset` helper package:
43
44```
45expo install expo-asset
46```
47
48Additionally, add the following line in your root `index.js` or `App.js` file:
49
50```js
51import 'expo-asset';
52```
53
54### metro.config.js
55
56You need to add a metro.config.js to your project with the following contents:
57
58```js
59module.exports = {
60 transformer: {
61 assetPlugins: ['expo-asset/tools/hashAssetFiles'],
62 },
63};
64```
65
66### Set up app.json
67
68If you're going to be using Expo CLI to package your updates (either with `expo export` or `expo publish`), you will need to add some fields to your app.json. If not, you can skip this section.
69
70First, if your app.json file does not yet include an `expo` key, add it with the following fields:
71
72```json
73 "expo": {
74 "name": "<your app name -- must match your iOS project folder name>",
75 "slug": "<string that uniquely identifies your app>",
76 "privacy": "unlisted",
77 "sdkVersion": "<SDK version of your app. See note below>",
78 }
79```
80
81Currently, all apps published to Expo's servers must be configured with a valid SDK version. We use the SDK version to determine which app binaries a particular update is compatible with. If your app has the `expo` package installed in package.json, your SDK version should match the major version number of this package. Otherwise, you can just use the latest Expo SDK version number (at least `37.0.0`).
82
83If you installed `expo-asset` and have other assets (such as images or other media) that are imported in your application code, and you would like these to be downloaded atomically as part of an update, add the `assetBundlePatterns` field under the `expo` key in your project's app.json. This field should be an array of file glob strings which point to the assets you want bundled. For example:
84
85```json
86 "assetBundlePatterns": ["**/*"],
87```
88
89Finally, if you're migrating from an ExpoKit project to the bare workflow with `expo-updates`, remove the `ios.publishBundlePath`, `ios.publishManifestPath`, `android.publishBundlePath`, and `android.publishManifestPath` keys from your app.json.
90
91### Configure for iOS
92
93Run `npx pod-install` after installing the npm package.
94
95#### Build Phases
96
97In Xcode, under the `Build Phases` tab of your main project, expand the phase entitled "Bundle React Native code and images." Add the following line to the bottom of the script:
98
99```
100../node_modules/expo-updates/scripts/create-manifest-ios.sh
101```
102
103This provides expo-updates with some important metadata about the update and assets that are embedded in your IPA.
104
105#### `Expo.plist`
106
107Create the file `ios/<your-project-name>/Supporting/Expo.plist` with the following contents, and add it to your Xcode project.
108
109```xml
110<?xml version="1.0" encoding="UTF-8"?>
111<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
112<plist version="1.0">
113<dict>
114 <key>EXUpdatesSDKVersion</key>
115 <string>YOUR-APP-SDK-VERSION-HERE</string>
116 <key>EXUpdatesURL</key>
117 <string>YOUR-APP-URL-HERE</string>
118</dict>
119</plist>
120```
121
122EXUpdatesURL is the remote URL at which your app will be hosted, and to which expo-updates will query for new updates. EXUpdatesSDKVersion should match the SDK version in your app.json.
123
124If you use `expo export` or `expo publish` to create your update, it will fill in the proper values here for you (given the file exists), so you don't need to set these values right now.
125
126#### `AppDelegate.h`
127
128In this file, you need to import the `EXUpdatesAppController` header and add `EXUpdatesAppControllerDelegate` as a protocol of your `AppDelegate`. A diff for a typical bare project might look like this (but yours might look different):
129
130```diff
131+#import <EXUpdates/EXUpdatesAppController.h>
132 #import <React/RCTBridgeDelegate.h>
133 #import <UMCore/UMAppDelegateWrapper.h>
134
135-@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate>
136+@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>
137
138 @property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
139 @property (nonatomic, strong) UIWindow *window;
140 ```
141
142#### `AppDelegate.m`
143
144Make the following changes to `AppDelegate.m`.
145
146If your `AppDelegate` has been customized and the diff doesn't apply cleanly, the important part is calling `[[EXUpdatesAppController sharedInstance] startAndShowLaunchScreen:self.window]` in the `application:didFinishLaunchingWithOptions` method, and moving the initialization of the `RCTBridge` to the `EXUpdatesAppControllerDelegate`.
147
148In general, iOS will only show your app's splash screen for a few seconds, after which you must provide a UI. If you use the `startAndShowLaunchScreen:` method, expo-updates will attempt to create a view from your `LaunchScreen.nib` file in order to continue showing the splash screen if the update is taking a long time to load. If you have custom logic around your splash screen and do not want this, feel free to use the `start` method instead.
149
150Providing `EXUpdatesAppController` with a reference to the `RCTBridge` is optional, but required in order for reloading and updates events to work.
151
152```diff
153 #import <UMReactNativeAdapter/UMNativeModulesProxy.h>
154 #import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
155
156+@interface AppDelegate ()
157+
158+@property (nonatomic, strong) NSDictionary *launchOptions;
159+
160+@end
161+
162 @implementation AppDelegate
163
164...
165
166 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
167 {
168 self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
169- RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
170+ self.launchOptions = launchOptions;
171+
172+ self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
173+#ifdef DEBUG
174+ [self initializeReactNativeApp];
175+#else
176+ EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
177+ controller.delegate = self;
178+ [controller startAndShowLaunchScreen:self.window];
179+#endif
180+
181+ [super application:application didFinishLaunchingWithOptions:launchOptions];
182+
183+ return YES;
184+}
185+
186+- (RCTBridge *)initializeReactNativeApp
187+{
188+ RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
189 RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"YOUR-APP-NAME" initialProperties:nil];
190 rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
191
192- self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
193 UIViewController *rootViewController = [UIViewController new];
194 rootViewController.view = rootView;
195 self.window.rootViewController = rootViewController;
196 [self.window makeKeyAndVisible];
197
198- [super application:application didFinishLaunchingWithOptions:launchOptions];
199-
200- return YES;
201+ return bridge;
202 }
203
204...
205
206 #ifdef DEBUG
207 return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
208 #else
209- return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
210+ return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
211 #endif
212 }
213
214+- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success
215+{
216+ appController.bridge = [self initializeReactNativeApp];
217+}
218+
219 @end
220```
221
222#### `expo-splash-screen`
223
224If you have `expo-splash-screen` installed in your bare workflow project, you'll need to make the following additional change to `AppDelegate.m`:
225
226```diff
227+#import <EXSplashScreen/EXSplashScreenService.h>
228+#import <UMCore/UMModuleRegistryProvider.h>
229
230 ...
231
232 - (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success
233 {
234 appController.bridge = [self initializeReactNativeApp];
235+ EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
236+ [splashScreenService showSplashScreenFor:self.window.rootViewController];
237 }
238```
239
240### Configure for Android
241
242#### `app/build.gradle`
243
244Add the following Gradle build script. This provides expo-updates with some important metadata about the update and assets that are embedded in your APK.
245
246```diff
247 apply from: "../../node_modules/react-native/react.gradle"
248+apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle"
249```
250
251#### `AndroidManifest.xml`
252
253Add the following lines inside of the `MainApplication`'s `<application>` tag.
254
255```xml
256<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="YOUR-APP-URL-HERE" />
257<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="YOUR-APP-SDK-VERSION-HERE" />
258```
259
260EXPO_UPDATE_URL is the remote URL at which your app will be hosted, and to which expo-updates will query for new updates. EXPO_SDK_VERSION should match the SDK version in your app.json.
261
262As with iOS, if you use `expo export` or `expo publish` to create your update, it will fill in the proper values here for you (given the file exists), so you don't need to set these values right now.
263
264#### `MainApplication.java`
265
266Make the following changes to `MainApplication.java` (or whichever file you instantiate your `ReactNativeHost`). `UpdatesController.initialize()` expects to be given an instance of `ReactApplication`, but if not, you can also call `UpdatesController.getInstance().setReactNativeHost()` to directly set the host. Providing `UpdatesController` with a reference to the `ReactNativeHost` is optional, but required in order for reloading and updates events to work.
267
268If the diff doesn't apply cleanly, the important parts here are (1) overriding `getJSBundleFile()` and `getBundleAssetName()` from `ReactNativeHost` with the values provided by expo-updates, and (2) initializing `UpdatesController` as early as possible in the application's lifecycle.
269
270```diff
271+import android.net.Uri;
272+import expo.modules.updates.UpdatesController;
273+import javax.annotation.Nullable;
274+
275 public class MainApplication extends Application implements ReactApplication {
276 private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
277 new BasePackageList().getPackageList(),
278
279...
280
281 protected String getJSMainModuleName() {
282 return "index";
283 }
284+
285+ @Override
286+ protected @Nullable String getJSBundleFile() {
287+ if (BuildConfig.DEBUG) {
288+ return super.getJSBundleFile();
289+ } else {
290+ return UpdatesController.getInstance().getLaunchAssetFile();
291+ }
292+ }
293+
294+ @Override
295+ protected @Nullable String getBundleAssetName() {
296+ if (BuildConfig.DEBUG) {
297+ return super.getBundleAssetName();
298+ } else {
299+ return UpdatesController.getInstance().getBundleAssetName();
300+ }
301+ }
302 };
303
304...
305
306 public void onCreate() {
307 super.onCreate();
308 SoLoader.init(this, /* native exopackage */ false);
309+
310+ if (!BuildConfig.DEBUG) {
311+ UpdatesController.initialize(this);
312+ }
313+
314 initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
315 }
316 }
317```
318
319## Embedded Assets
320
321In certain situations, assets that are `require`d by your JavaScript are embedded into your application binary by Xcode/Android Studio. This allows these assets to load when the packager server running locally on your machine is not available.
322
323Debug builds of Android apps do not, by default, have any assets bundled into the APK; they are always loaded at runtime from the Metro packager.
324
325Debug builds of iOS apps built for the iOS simulator also do not have assets bundled into the app. They are loaded at runtime from Metro. Debug builds of iOS apps built for a real device **do** have assets bundled into the app binary, so they can be loaded from disk if they cannot be loaded from the packager at runtime.
326
327Release builds of both iOS and Android apps include a full embedded update, including manifest, JavaScript bundle, and all imported assets. This is critical to ensure that your app can load for all users immediately upon installation, without needing to talk to a server first.
328
329## Configuration
330
331Some build-time configuration options are available to allow your app to update automatically on launch. On iOS, these properties are set as keys in `Expo.plist` and on Android as `meta-data` tags in `AndroidManifest.xml`, adjacent to the tags added during installation.
332
333On Android, you may also define these properties at runtime by passing a `Map` as the second parameter of `UpdatesController.initialize()`. If provided, the values in this Map will override any values specified in `AndroidManifest.xml`. On iOS, you may set these properties at runtime by calling `[UpdatesController.sharedInstance setConfiguration:]` at any point _before_ calling `start` or `startAndShowLaunchScreen`, and the values in this dictionary will override Expo.plist.
334
335| iOS plist/dictionary key | Android Map key | Android meta-data name | Default | Required? |
336| --- | --- | --- | --- | --- |
337| `EXUpdatesEnabled` | `enabled` | `expo.modules.updates.ENABLED` | `true` | ❌ |
338
339Whether updates are enabled. Setting this to `false` disables all update functionality, all module methods, and forces the app to load with the manifest and assets bundled into the app binary.
340
341| iOS plist/dictionary key | Android Map key | Android meta-data name | Default | Required? |
342| --- | --- | --- | --- | --- |
343| `EXUpdatesURL` | `updateUrl` | `expo.modules.updates.EXPO_UPDATE_URL` | (none) | ✅ |
344
345URL to the remote server where the app should check for updates
346
347| iOS plist/dictionary key | Android Map key | Android meta-data name | Default | Required? |
348| --- | --- | --- | --- | --- |
349| `EXUpdatesSDKVersion` | `sdkVersion` | `expo.modules.updates.EXPO_SDK_VERSION` | (none) | (exactly one of `sdkVersion` or `runtimeVersion` is required) |
350
351SDK version to send under the `Expo-SDK-Version` header in the manifest request. Required for apps hosted on Expo's server.
352
353| iOS plist/dictionary key | Android Map key | Android meta-data name | Default | Required? |
354| --- | --- | --- | --- | --- |
355| `EXUpdatesRuntimeVersion` | `runtimeVersion` | `expo.modules.updates.EXPO_RUNTIME_VERSION` | (none) | (exactly one of `sdkVersion` or `runtimeVersion` is required) |
356
357Runtime version to send under the `Expo-Runtime-Version` header in the manifest request.
358
359| iOS plist/dictionary key | Android Map key | Android meta-data name | Default | Required? |
360| --- | --- | --- | --- | --- |
361| `EXUpdatesReleaseChannel` | `releaseChannel` | `expo.modules.updates.EXPO_RELEASE_CHANNEL` | `default` | ❌ |
362
363Release channel to send under the `Expo-Release-Channel` header in the manifest request
364
365| iOS plist/dictionary key | Android Map key | Android meta-data name | Default | Required? |
366| --- | --- | --- | --- | --- |
367| `EXUpdatesCheckOnLaunch` | `checkOnLaunch` | `expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH` | `ALWAYS` | ❌ |
368
369Condition under which expo-updates should automatically check for (and download, if one exists) an update upon app launch. Possible values are `ALWAYS`, `NEVER` (if you want to exclusively control updates via this module's JS API), or `WIFI_ONLY` (if you want the app to automatically download updates only if the device is on an unmetered Wi-Fi connection when it launches).
370
371| iOS plist/dictionary key | Android Map key | Android meta-data name | Default | Required? |
372| --- | --- | --- | --- | --- |
373| `EXUpdatesLaunchWaitMs` | `launchWaitMs` | `expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS` | `0` | ❌ |
374
375Number of milliseconds expo-updates should delay the app launch and stay on the splash screen while trying to download an update, before falling back to a previously downloaded version. Setting this to `0` will cause the app to always launch with a previously downloaded update and will result in the fastest app launch possible.
376
377# Removing pre-installed expo-updates
378
379Projects created by `expo init` and `expo eject` come with expo-updates pre-installed, because we anticipate most users will want this functionality. However, if you do not intend to use OTA updates, you can disable or uninstall the module.
380
381### Disabling expo-updates
382
383If you disable updates, the module will stay installed in case you ever want to use it in the future, but none of the OTA-updating code paths will ever be executed in your builds. To disable OTA updates, add the `EXUpdatesEnabled` key to Expo.plist with a boolean value of `NO`, and add the following line to AndroidManifest.xml:
384
385```xml
386<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
387```
388
389### Uninstalling expo-updates
390
391Uninstalling the module will entirely remove all expo-updates related code from your codebase. To do so, complete the following steps:
392
393- Remove `expo-updates` from your package.json and reinstall your node modules.
394- Remove the line `../node_modules/expo-updates/scripts/create-manifest-ios.sh` from the "Bundle React Native code and images" Build Phase in Xcode.
395- Delete Expo.plist from your Xcode project and file system.
396- Remove the line `apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle"` from `android/app/build.gradle`.
397- Remove all `meta-data` tags with `expo.modules.updates` in the `android:name` field from AndroidManifest.xml.
398- Apply the following three diffs:
399
400#### `AppDelegate.h`
401
402Remove`EXUpdatesAppControllerDelegate` as a protocol of your `AppDelegate`.
403
404```diff
405-#import <EXUpdates/EXUpdatesAppController.h>
406 #import <React/RCTBridgeDelegate.h>
407 #import <UMCore/UMAppDelegateWrapper.h>
408
409-@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>
410+@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate>
411
412 @property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
413 @property (nonatomic, strong) UIWindow *window;
414 ```
415
416#### `AppDelegate.m`
417
418```diff
419 #import <UMReactNativeAdapter/UMNativeModulesProxy.h>
420 #import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
421
422-@interface AppDelegate ()
423-
424-@property (nonatomic, strong) NSDictionary *launchOptions;
425-
426-@end
427-
428 @implementation AppDelegate
429
430...
431
432 - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
433 {
434 self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
435- self.launchOptions = launchOptions;
436-
437- self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
438-#ifdef DEBUG
439- [self initializeReactNativeApp];
440-#else
441- EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
442- controller.delegate = self;
443- [controller startAndShowLaunchScreen:self.window];
444-#endif
445-
446- [super application:application didFinishLaunchingWithOptions:launchOptions];
447-
448- return YES;
449-}
450-
451-- (RCTBridge *)initializeReactNativeApp
452-{
453- RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
454+ RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
455 RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"YOUR-APP-NAME" initialProperties:nil];
456 rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
457
458+ self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
459 UIViewController *rootViewController = [UIViewController new];
460 rootViewController.view = rootView;
461 self.window.rootViewController = rootViewController;
462 [self.window makeKeyAndVisible];
463
464- return bridge;
465+ [super application:application didFinishLaunchingWithOptions:launchOptions];
466+
467+ return YES;
468 }
469
470...
471
472 #ifdef DEBUG
473 return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
474 #else
475- return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
476+ return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
477 #endif
478 }
479
480-- (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success
481-{
482- appController.bridge = [self initializeReactNativeApp];
483-}
484-
485 @end
486```
487
488#### `MainApplication.java`
489
490```diff
491-import android.net.Uri;
492-import expo.modules.updates.UpdatesController;
493-import javax.annotation.Nullable;
494-
495 public class MainApplication extends Application implements ReactApplication {
496 private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
497 new BasePackageList().getPackageList(),
498
499...
500
501 protected String getJSMainModuleName() {
502 return "index";
503 }
504-
505- @Override
506- protected @Nullable String getJSBundleFile() {
507- if (BuildConfig.DEBUG) {
508- return super.getJSBundleFile();
509- } else {
510- return UpdatesController.getInstance().getLaunchAssetFile();
511- }
512- }
513-
514- @Override
515- protected @Nullable String getBundleAssetName() {
516- if (BuildConfig.DEBUG) {
517- return super.getBundleAssetName();
518- } else {
519- return UpdatesController.getInstance().getBundleAssetName();
520- }
521- }
522 };
523
524...
525
526 public void onCreate() {
527 super.onCreate();
528 SoLoader.init(this, /* native exopackage */ false);
529-
530- if (!BuildConfig.DEBUG) {
531- UpdatesController.initialize(this);
532- }
533-
534 initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
535 }
536 }
537 ```
538
539#### Remove Pods Target EXUpdates (Optional)
540
541If, after following above steps, your `npm run ios` or `yarn ios` fails and you see `EXUpdates` in logs, follow the steps below:
542
543- Open the iOS directory in Xcode
544- Go to Pods module on right side
545- In the targets, find `EXUpdates`, right click and delete
546
\No newline at end of file