Learn how to add expo-updates to an existing React Native project.
The expo-updates
library fetches and manages updates received from a remote server. It supports EAS Update, a hosted service that serves updates for projects using the expo-updates
library.
If you are creating a new project, we recommend using
npx create-react-native-app
instead ofnpx react-native init
because it will handle the following configuration for you automatically. It includes theexpo-updates
config plugin, which will handle the following steps for you.
The expo-updates
library requires that your project already has Expo modules configured. Be sure to install it before continuing.
To get started, install expo-updates
:
-Â
npx expo install expo-updates
Then, install pods:
-Â
npx pod-install
Once installation is complete, apply the changes from the following diffs to configure expo-updates
in your project.
You'll need to modify index.js to import expo-asset
early in your app, to be able to update assets with updates.
+ import 'expo-asset';
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
Add the Supporting directory containing Expo.plist to your project in Xcode with the following content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EXUpdatesCheckOnLaunch</key>
<string>ALWAYS</string>
<key>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesSDKVersion</key>
<string>46.0.0</string>
<key>EXUpdatesURL</key>
<string>https://exp.host/@my-expo-username/my-app</string>
</dict>
</plist>
Apply the following configurations to your AndroidManifest.xml file:
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@my-expo-username/my-app" />
+ <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="46.0.0" />
+ <meta-data android:name="expo.modules.updates.UPDATES_CONFIGURATION_REQUEST_HEADERS_KEY" android:value="{'expo-channel-name':'your-channel-name'}"/>
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_ENABLED" android:value="true" />
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0" />
+ <meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS" />
By 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.
#import <Foundation/Foundation.h>
+ #import <EXUpdates/EXUpdatesAppController.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <ExpoModulesCore/EXAppDelegateWrapper.h>
- @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate>
+ @interface AppDelegate : EXAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>
There are multiple changes to apply to your project's AppDelegate.mm.
+ @interface AppDelegate () <RCTBridgeDelegate>
+
+ @property (nonatomic, strong) NSDictionary *launchOptions;
+
+ @end
+
@implementation AppDelegate
- RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
+ self.launchOptions = launchOptions;
+ self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
+ #ifdef DEBUG
+ [self initializeReactNativeApp];
+ #else
+ EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
+ controller.delegate = self;
+ [controller startAndShowLaunchScreen:self.window];
+ #endif
+
+ [super application:application didFinishLaunchingWithOptions:launchOptions];
+
+ return YES;
+ }
+
+ - (RCTBridge *)initializeReactNativeApp
+ {
+ RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
id rootViewBackgroundColor = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"RCTRootViewBackgroundColor"];
if (rootViewBackgroundColor != nil) {
rootView.backgroundColor = [UIColor whiteColor];
}
- self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
- [super application:application didFinishLaunchingWithOptions:launchOptions];
-
- return YES;
+ return bridge;
}
- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
#ifdef DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
- return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
+ return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
#endif
}
+ - (void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
+ appController.bridge = [self initializeReactNativeApp];
+ }
// Linking API
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
return [RCTLinkingManager application:application openURL:url options:options];
<key>EXUpdatesURL</key>
<string>https://exp.host/@my-expo-username/my-app</string>
+ <key>EXUpdatesAutoSetup</key>
+ <false/>
</dict>
</plist>
By 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.
<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">
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@my-expo-username/my-app"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="46.0.0"/>
+ <meta-data android:name="expo.modules.updates.AUTO_SETUP" android:value="false"/>
<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">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
There are multiple changes to apply to your project's MainApplication.java
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
+ import android.net.Uri;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import expo.modules.ApplicationLifecycleDispatcher;
import expo.modules.ReactNativeHostWrapper;
+ import expo.modules.updates.UpdatesController;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
+ import javax.annotation.Nullable;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHostWrapper(
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
}
+
+ @Override
+ protected @Nullable String getJSBundleFile() {
+ if (BuildConfig.DEBUG) {
+ return super.getJSBundleFile();
+ } else {
+ return UpdatesController.getInstance().getLaunchAssetFile();
+ }
+ }
+
+ @Override
+ protected @Nullable String getBundleAssetName() {
+ if (BuildConfig.DEBUG) {
+ return super.getBundleAssetName();
+ } else {
+ return UpdatesController.getInstance().getBundleAssetName();
+ }
+ }
});
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
+ if (!BuildConfig.DEBUG) {
+ UpdatesController.initialize(this);
+ }
+
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
ApplicationLifecycleDispatcher.onApplicationCreate(this);
}
See more information about usage in the expo-updates
README.
If 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": ["**/*"]