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