xref: /expo/docs/pages/bare/installing-updates.mdx (revision 5697f03e)
1---
2title: Add expo-updates to an existing project
3description: Learn how to add expo-updates to an existing React Native project.
4---
5
6import { DiffBlock, Terminal } from '~/ui/components/Snippet';
7import { Collapsible } from '~/ui/components/Collapsible';
8
9The `expo-updates` library fetches and manages updates received from a remote server. It supports [EAS Update](/eas-update/introduction), a hosted service that serves updates for projects using the `expo-updates` library.
10
11> If you are creating a new project, we recommend using `npx create-react-native-app` instead of `npx react-native init` because it will handle the following configuration for you automatically. It includes the `expo-updates` [config plugin](/guides/config-plugins), which will handle the following steps for you.
12
13## Installation
14
15The `expo-updates` library requires that your project already has [Expo modules configured](/bare/installing-expo-modules). Be sure to install it before continuing.
16
17To get started, install `expo-updates`:
18
19<Terminal cmd={['$ npx expo install expo-updates']} />
20
21Then, install pods:
22
23<Terminal cmd={['$ npx pod-install']} />
24
25Once installation is complete, apply the changes from the following diffs to configure `expo-updates` in your project.
26
27## Configuration in JavaScript and JSON
28
29You'll need to modify **index.js** to import `expo-asset` early in your app, to be able to update assets with updates.
30
31```diff index.js
32+ import 'expo-asset';
33import { registerRootComponent } from 'expo';
34
35import App from './App';
36
37// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
38// It also ensures that whether you load the app in Expo Go or in a native build,
39// the environment is set up appropriately
40registerRootComponent(App);
41```
42
43## Configuration for iOS
44
45Add the **Supporting** directory containing **Expo.plist** to your project in Xcode with the following content:
46
47```xml Expo.plist
48<?xml version="1.0" encoding="UTF-8"?>
49<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
50<plist version="1.0">
51  <dict>
52    <key>EXUpdatesCheckOnLaunch</key>
53    <string>ALWAYS</string>
54    <key>EXUpdatesEnabled</key>
55    <true/>
56    <key>EXUpdatesLaunchWaitMs</key>
57    <integer>0</integer>
58    <key>EXUpdatesSDKVersion</key>
59    <string>46.0.0</string>
60    <key>EXUpdatesURL</key>
61    <string>https://exp.host/@my-expo-username/my-app</string>
62  </dict>
63</plist>
64```
65
66## Configuration for Android
67
68Apply the following configurations to your **AndroidManifest.xml** file:
69
70```diff AndroidManifest.xml
71+ <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@my-expo-username/my-app" />
72+ <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="46.0.0" />
73+ <meta-data android:name="expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY" android:value="{'expo-channel-name':'your-channel-name'}"/>
74+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_ENABLED" android:value="true" />
75+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0" />
76+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS" />
77```
78
79## Customizing Automatic Setup for iOS
80
81By default, `expo-updates` requires no additional setup. If you want to customize the installation, for example, to enable updates only in some build variants, you can instead follow these manual setup steps and then apply any customizations.
82
83### Update AppDelegate.h
84
85```diff apps/bare-update/ios/bareupdate/AppDelegate.h
86#import <Foundation/Foundation.h>
87+ #import <EXUpdates/EXUpdatesAppController.h>
88#import <React/RCTBridgeDelegate.h>
89#import <UIKit/UIKit.h>
90
91#import <ExpoModulesCore/EXAppDelegateWrapper.h>
92
93- @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate>
94+ @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>
95```
96
97### Update AppDelegate.mm
98
99There are multiple changes to apply to your project's **AppDelegate.mm**.
100
101```diff apps/bare-update/ios/bareupdate/AppDelegate.mm
102+ @interface AppDelegate () <RCTBridgeDelegate>
103+
104+ @property (nonatomic, strong) NSDictionary *launchOptions;
105+
106+ @end
107+
108 @implementation AppDelegate
109
110```
111
112```diff apps/bare-update/ios/bareupdate/AppDelegate.mm
113-  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
114+  self.launchOptions = launchOptions;
115+  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
116+  #ifdef DEBUG
117+    [self initializeReactNativeApp];
118+  #else
119+    EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
120+    controller.delegate = self;
121+    [controller startAndShowLaunchScreen:self.window];
122+  #endif
123+
124+  [super application:application didFinishLaunchingWithOptions:launchOptions];
125+
126+  return YES;
127+ }
128+
129+ - (RCTBridge *)initializeReactNativeApp
130+ {
131+  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
132   RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
133   id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTRootViewBackgroundColor"];
134   if (rootViewBackgroundColor != nil) {
135```
136
137```diff apps/bare-update/ios/bareupdate/AppDelegate.mm
138     rootView.backgroundColor = [UIColor whiteColor];
139   }
140
141-  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
142   UIViewController *rootViewController = [UIViewController new];
143   rootViewController.view = rootView;
144   self.window.rootViewController = rootViewController;
145   [self.window makeKeyAndVisible];
146
147-  [super application:application didFinishLaunchingWithOptions:launchOptions];
148-
149-  return YES;
150+  return bridge;
151  }
152
153 - (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
154```
155
156```diff apps/bare-update/ios/bareupdate/AppDelegate.mm
157  #ifdef DEBUG
158   return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
159  #else
160-  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
161+  return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
162  #endif
163 }
164
165+ - (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
166+  appController.bridge = [self initializeReactNativeApp];
167+ }
168
169 // Linking API
170 - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
171   return [RCTLinkingManager application:application openURL:url options:options];
172```
173
174### Expo.plist
175
176```diff apps/bare-update/ios/bareupdate/Supporting/Expo.plist
177     <key>EXUpdatesURL</key>
178     <string>https://exp.host/@my-expo-username/my-app</string>
179+    <key>EXUpdatesAutoSetup</key>
180+    <false/>
181   </dict>
182 </plist>
183```
184
185## Customizing Automatic Setup for Android
186
187By default, `expo-updates` requires no additional setup. If you want to customize the installation, for example, to enable updates only in some build variants, you can instead follow these manual setup steps and then apply any customizations.
188
189### AndroidManifest.xml
190
191```diff apps/bare-update/android/app/src/main/AndroidManifest.xml
192   <application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="false" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
193     <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@my-expo-username/my-app"/>
194     <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="46.0.0"/>
195+    <meta-data android:name="expo.modules.updates.AUTO_SETUP" android:value="false"/>
196     <activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen">
197       <intent-filter>
198         <action android:name="android.intent.action.MAIN"/>
199```
200
201### MainApplication.java
202
203There are multiple changes to apply to your project's **MainApplication.java**
204
205```diff apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
206 import android.app.Application;
207 import android.content.Context;
208 import android.content.res.Configuration;
209+ import android.net.Uri;
210
211 import com.facebook.react.PackageList;
212 import com.facebook.react.ReactApplication;
213```
214
215```diff apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
216 import expo.modules.ApplicationLifecycleDispatcher;
217 import expo.modules.ReactNativeHostWrapper;
218+ import expo.modules.updates.UpdatesController;
219
220 import com.facebook.react.bridge.JSIModulePackage;
221 import com.swmansion.reanimated.ReanimatedJSIModulePackage;
222```
223
224```diff apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
225 import java.lang.reflect.InvocationTargetException;
226 import java.util.Arrays;
227 import java.util.List;
228+ import javax.annotation.Nullable;
229
230 public class MainApplication extends Application implements ReactApplication {
231   private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(
232```
233
234```diff apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
235     protected JSIModulePackage getJSIModulePackage() {
236       return new ReanimatedJSIModulePackage();
237     }
238+
239+    @Override
240+    protected @Nullable String getJSBundleFile() {
241+      if (BuildConfig.DEBUG) {
242+        return super.getJSBundleFile();
243+      } else {
244+        return UpdatesController.getInstance().getLaunchAssetFile();
245+      }
246+    }
247+
248+    @Override
249+    protected @Nullable String getBundleAssetName() {
250+      if (BuildConfig.DEBUG) {
251+        return super.getBundleAssetName();
252+      } else {
253+        return UpdatesController.getInstance().getBundleAssetName();
254+      }
255+    }
256   });
257```
258
259```diff apps/bare-update/android/app/src/main/java/com/bareupdate/MainApplication.java
260     super.onCreate();
261     SoLoader.init(this, /* native exopackage */ false);
262
263+    if (!BuildConfig.DEBUG) {
264+      UpdatesController.initialize(this);
265+    }
266+
267     initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
268     ApplicationLifecycleDispatcher.onApplicationCreate(this);
269   }
270```
271
272## Usage
273
274See more information about usage in the [`expo-updates` README](https://github.com/expo/expo/tree/main/packages/expo-updates/README.md).
275
276## FAQ
277
278<Collapsible summary="How do I customize which assets are included in an update bundle?">
279
280If you have 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 that point to the assets you want to be bundled. For example: `"assetBundlePatterns": ["**/*"]`
281
282</Collapsible>
283