HomeGuidesReferenceLearn
ArchiveExpo SnackDiscord and ForumsNewsletter

Native project upgrade helper

View file-by-file diffs of all the changes you need to make to your native projects to upgrade them to the next Expo SDK version.


If you manage your native projects (previously known as bare workflow), to upgrade to the latest Expo SDK, you have to make changes to your native projects. It can be a complex process to find which native file changes and what to update in which file.

The following guide provides diffs to compare native project files between your project's current SDK version and the target SDK version you want to upgrade. You can use them to make changes to your project depending on the expo package version your project uses. The tools on this page are similar to React Native Upgrade Helper. However, they are oriented around projects that use Expo modules and related tooling.

Interested in avoiding upgrading native code altogether? See Continuous Native Generation (CNG) to learn how Expo Prebuild can generate your native projects before a build.

Upgrade native project files

Once you have upgraded your Expo SDK version and related dependencies, use the diff tool below to learn about changes you need to make to your native project and bring them up to date with the current Expo SDK version.

Choose your from SDK version and a to SDK version to see the diff. Then, apply those changes to your native projects by copying and pasting or manually making changes to the project files.

From SDK version:

To SDK version:

Native code changes from SDK 50 to 51

android/app/build.gradle

MODIFIED

9090 targetSdkVersion rootProject.ext.targetSdkVersion
9191 versionCode 1
9292 versionName "1.0"
93
94 buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
9593 }
9694 signingConfigs {
9795 debug {
163161 }
164162 }
165163
166 implementation("com.facebook.react:flipper-integration")
167
168164 if (hermesEnabled.toBoolean()) {
169165 implementation("com.facebook.react:hermes-android")
170166 } else {
android/app/src/main/AndroidManifest.xml

MODIFIED

1919 </queries>
2020
2121 <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">
22 <meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="YOUR-APP-URL-HERE"/>
23 <meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="YOUR-APP-SDK-VERSION-HERE"/>
2422 <activity android:name=".MainActivity" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:exported="true">
2523 <intent-filter>
2624 <action android:name="android.intent.action.MAIN"/>
2927 </activity>
3028 <activity android:name="com.facebook.react.devsupport.DevSettingsActivity" android:exported="false"/>
3129 </application>
32</manifest>
30</manifest>
android/app/src/main/java/com/helloworld/MainApplication.kt

MODIFIED

22
33import android.app.Application
44import android.content.res.Configuration
5import androidx.annotation.NonNull
65
76import com.facebook.react.PackageList
87import com.facebook.react.ReactApplication
98import com.facebook.react.ReactNativeHost
109import com.facebook.react.ReactPackage
1110import com.facebook.react.ReactHost
12import com.facebook.react.config.ReactFeatureFlags
1311import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
14import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
1512import com.facebook.react.defaults.DefaultReactNativeHost
16import com.facebook.react.flipper.ReactNativeFlipper
1713import com.facebook.soloader.SoLoader
1814
1915import expo.modules.ApplicationLifecycleDispatcher
4036 )
4137
4238 override val reactHost: ReactHost
43 get() = getDefaultReactHost(this.applicationContext, reactNativeHost)
39 get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)
4440
4541 override fun onCreate() {
4642 super.onCreate()
4743 SoLoader.init(this, false)
48 if (!BuildConfig.REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS) {
49 ReactFeatureFlags.unstable_useRuntimeSchedulerAlways = false
50 }
5144 if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
5245 // If you opted-in for the New Architecture, we load the native entry point for this app.
5346 load()
5447 }
55 if (BuildConfig.DEBUG) {
56 ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager)
57 }
5848 ApplicationLifecycleDispatcher.onApplicationCreate(this)
5949 }
6050
android/app/src/main/res/drawable/rn_edit_text_material.xml

MODIFIED

1717 android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
1818 android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
1919 android:insetTop="@dimen/abc_edit_text_inset_top_material"
20 android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
20 android:insetBottom="@dimen/abc_edit_text_inset_bottom_material"
21 >
2122
2223 <selector>
2324 <!--
android/build.gradle

MODIFIED

66 minSdkVersion = Integer.parseInt(findProperty('android.minSdkVersion') ?: '23')
77 compileSdkVersion = Integer.parseInt(findProperty('android.compileSdkVersion') ?: '34')
88 targetSdkVersion = Integer.parseInt(findProperty('android.targetSdkVersion') ?: '34')
9 kotlinVersion = findProperty('android.kotlinVersion') ?: '1.8.10'
9 kotlinVersion = findProperty('android.kotlinVersion') ?: '1.9.23'
1010
11 ndkVersion = "25.1.8937393"
11 ndkVersion = "26.1.10909125"
1212 }
1313 repositories {
1414 google()
1717 dependencies {
1818 classpath('com.android.tools.build:gradle')
1919 classpath('com.facebook.react:react-native-gradle-plugin')
20 classpath('org.jetbrains.kotlin:kotlin-gradle-plugin')
2021 }
2122}
2223
android/gradle/wrapper/gradle-wrapper.properties

MODIFIED

11distributionBase=GRADLE_USER_HOME
22distributionPath=wrapper/dists
3distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip
3distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
44networkTimeout=10000
55validateDistributionUrl=true
66zipStoreBase=GRADLE_USER_HOME
android/gradlew.bat

MODIFIED

2626
2727set DIRNAME=%~dp0
2828if "%DIRNAME%"=="" set DIRNAME=.
29@rem This is normally unused
2930set APP_BASE_NAME=%~n0
3031set APP_HOME=%DIRNAME%
3132
4243%JAVA_EXE% -version >NUL 2>&1
4344if %ERRORLEVEL% equ 0 goto execute
4445
45echo.
46echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47echo.
48echo Please set the JAVA_HOME variable in your environment to match the
49echo location of your Java installation.
46echo. 1>&2
47echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
48echo. 1>&2
49echo Please set the JAVA_HOME variable in your environment to match the 1>&2
50echo location of your Java installation. 1>&2
5051
5152goto fail
5253
5657
5758if exist "%JAVA_EXE%" goto execute
5859
59echo.
60echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61echo.
62echo Please set the JAVA_HOME variable in your environment to match the
63echo location of your Java installation.
60echo. 1>&2
61echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
62echo. 1>&2
63echo Please set the JAVA_HOME variable in your environment to match the 1>&2
64echo location of your Java installation. 1>&2
6465
6566goto fail
6667
ios/HelloWorld.xcodeproj/project.pbxproj

MODIFIED

212212 );
213213 runOnlyForDeploymentPostprocessing = 0;
214214 shellPath = /bin/sh;
215 shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios relative | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
215 shellScript = "if [[ -f \"$PODS_ROOT/../.xcode.env\" ]]; then\n source \"$PODS_ROOT/../.xcode.env\"\nfi\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n# The project root by default is one level up from the ios directory\nexport PROJECT_ROOT=\"$PROJECT_DIR\"/..\n\nif [[ \"$CONFIGURATION\" = *Debug* ]]; then\n export SKIP_BUNDLING=1\nfi\nif [[ -z \"$ENTRY_FILE\" ]]; then\n # Set the entry JS file using the bundler's entry resolution.\n export ENTRY_FILE=\"$(\"$NODE_BINARY\" -e \"require('expo/scripts/resolveAppEntry')\" \"$PROJECT_ROOT\" ios absolute | tail -n 1)\"\nfi\n\nif [[ -z \"$CLI_PATH\" ]]; then\n # Use Expo CLI\n export CLI_PATH=\"$(\"$NODE_BINARY\" --print \"require.resolve('@expo/cli', { paths: [require.resolve('expo/package.json')] })\")\"\nfi\nif [[ -z \"$BUNDLE_COMMAND\" ]]; then\n # Default Expo CLI command for bundling\n export BUNDLE_COMMAND=\"export:embed\"\nfi\n\n# Source .xcode.env.updates if it exists to allow\n# SKIP_BUNDLING to be unset if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.updates\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.updates\"\nfi\n# Source local changes to allow overrides\n# if needed\nif [[ -f \"$PODS_ROOT/../.xcode.env.local\" ]]; then\n source \"$PODS_ROOT/../.xcode.env.local\"\nfi\n\n`\"$NODE_BINARY\" --print \"require('path').dirname(require.resolve('react-native/package.json')) + '/scripts/react-native-xcode.sh'\"`\n\n";
216216 };
217217 08A4A3CD28434E44B6B9DE2E /* [CP] Check Pods Manifest.lock */ = {
218218 isa = PBXShellScriptBuildPhase;
ios/HelloWorld/AppDelegate.mm

MODIFIED

1818
1919- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
2020{
21 return [self getBundleURL];
21 return [self bundleURL];
2222}
2323
24- (NSURL *)getBundleURL
24- (NSURL *)bundleURL
2525{
2626#if DEBUG
2727 return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
ios/HelloWorld/Info.plist

MODIFIED

3535 <string>SplashScreen</string>
3636 <key>UIRequiredDeviceCapabilities</key>
3737 <array>
38 <string>armv7</string>
38 <string>arm64</string>
3939 </array>
4040 <key>UIStatusBarStyle</key>
4141 <string>UIStatusBarStyleDefault</string>
ios/HelloWorld/Supporting/Expo.plist

MODIFIED

22<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33<plist version="1.0">
44<dict>
5 <key>EXUpdatesSDKVersion</key>
6 <string>YOUR-APP-SDK-VERSION-HERE</string>
7 <key>EXUpdatesURL</key>
8 <string>YOUR-APP-URL-HERE</string>
95</dict>
106</plist>
ios/Podfile

MODIFIED

1313
1414prepare_react_native_project!
1515
16# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
17# because `react-native-flipper` depends on (FlipperKit,...), which will be excluded. To fix this,
18# you can also exclude `react-native-flipper` in `react-native.config.js`
19#
20# ```js
21# module.exports = {
22# dependencies: {
23# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
24# }
25# }
26# ```
27flipper_config = FlipperConfiguration.disabled
28if ENV['NO_FLIPPER'] == '1' then
29 # Explicitly disabled through environment variables
30 flipper_config = FlipperConfiguration.disabled
31elsif podfile_properties.key?('ios.flipper') then
32 # Configure Flipper in Podfile.properties.json
33 if podfile_properties['ios.flipper'] == 'true' then
34 flipper_config = FlipperConfiguration.enabled(["Debug", "Release"])
35 elsif podfile_properties['ios.flipper'] != 'false' then
36 flipper_config = FlipperConfiguration.enabled(["Debug", "Release"], { 'Flipper' => podfile_properties['ios.flipper'] })
37 end
38end
39
4016target 'HelloWorld' do
4117 use_expo_modules!
4218 config = use_native_modules!
4925 :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
5026 # An absolute path to your application root.
5127 :app_path => "#{Pod::Config.instance.installation_root}/..",
52 # Note that if you have use_frameworks! enabled, Flipper will not work if enabled
53 :flipper_configuration => flipper_config
5428 )
5529
5630 post_install do |installer|
package.json

MODIFIED

11{
22 "name": "expo-template-bare-minimum",
33 "description": "This bare project template includes a minimal setup for using unimodules with React Native.",
4 "version": "50.0.42",
4 "version": "51.0.2",
55 "main": "index.js",
66 "scripts": {
77 "start": "expo start --dev-client",
1010 "web": "expo start --web"
1111 },
1212 "dependencies": {
13 "expo": "~50.0.14",
14 "expo-status-bar": "~1.11.1",
13 "expo": "~51.0.0-preview.0",
14 "expo-status-bar": "~1.12.0",
1515 "react": "18.2.0",
16 "react-native": "0.73.6"
16 "react-native": "0.74.0"
1717 },
1818 "devDependencies": {
1919 "@babel/core": "^7.20.0"