Native project upgrade helper

Edit this page

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 to SDK version to see the generated 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
44
55def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()
66
7static def versionToNumber(major, minor, patch) {
8 return patch * 100 + minor * 10000 + major * 1000000
9}
10
11def getRNVersion() {
12 def version = providers.exec {
13 workingDir(projectDir)
14 commandLine("node", "-e", "console.log(require('react-native/package.json').version);")
15 }.standardOutput.asText.get().trim()
16
17 def coreVersion = version.split("-")[0]
18 def (major, minor, patch) = coreVersion.tokenize('.').collect { it.toInteger() }
19
20 return versionToNumber(
21 major,
22 minor,
23 patch
24 )
25}
26def rnVersion = getRNVersion()
27
728/**
829 * This is the configuration block to customize your React Native Android app.
930 * By default you don't need to apply any configuration, just uncomment the lines you need.
5778 //
5879 // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
5980 // hermesFlags = ["-O", "-output-source-map"]
81
82 if (rnVersion >= versionToNumber(0, 75, 0)) {
83 /* Autolinking */
84 autolinkLibrariesWithApp()
85 }
6086}
6187
6288/**
90116 targetSdkVersion rootProject.ext.targetSdkVersion
91117 versionCode 1
92118 versionName "1.0"
93
94 buildConfigField("boolean", "REACT_NATIVE_UNSTABLE_USE_RUNTIME_SCHEDULER_ALWAYS", (findProperty("reactNative.unstable_useRuntimeSchedulerAlways") ?: true).toString())
95119 }
96120 signingConfigs {
97121 debug {
112136 shrinkResources (findProperty('android.enableShrinkResourcesInReleaseBuilds')?.toBoolean() ?: false)
113137 minifyEnabled enableProguardInReleaseBuilds
114138 proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
139 crunchPngs (findProperty('android.enablePngCrunchInReleaseBuilds')?.toBoolean() ?: true)
115140 }
116141 }
117142 packagingOptions {
163188 }
164189 }
165190
166 implementation("com.facebook.react:flipper-integration")
167
168191 if (hermesEnabled.toBoolean()) {
169192 implementation("com.facebook.react:hermes-android")
170193 } else {
172195 }
173196}
174197
175apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
176applyNativeModulesAppBuildGradle(project)
198if (rnVersion < versionToNumber(0, 75, 0)) {
199 apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
200 applyNativeModulesAppBuildGradle(project)
201}
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.properties
MODIFIED
2525# Automatically convert third-party libraries to use AndroidX
2626android.enableJetifier=true
2727
28# Enable AAPT2 PNG crunching
29android.enablePngCrunchInReleaseBuilds=true
30
2831# Use this property to specify which architecture you want to build.
2932# You can also override it from the CLI using
3033# ./gradlew <task> -PreactNativeArchitectures=x86_64
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
android/react-settings-plugin/build.gradle.kts
ADDED
1import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2
3plugins {
4 kotlin("jvm") version "1.9.24"
5 id("java-gradle-plugin")
6}
7
8repositories {
9 mavenCentral()
10}
11
12gradlePlugin {
13 plugins {
14 create("reactSettingsPlugin") {
15 id = "com.facebook.react.settings"
16 implementationClass = "expo.plugins.ReactSettingsPlugin"
17 }
18 }
19}
android/settings.gradle
MODIFIED
1pluginManagement {
2 def version = providers.exec {
3 commandLine("node", "-e", "console.log(require('react-native/package.json').version);")
4 }.standardOutput.asText.get().trim()
5 def (_, reactNativeMinor, reactNativePatch) = version.split("-")[0].tokenize('.').collect { it.toInteger() }
6
7 includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json')"].execute(null, rootDir).text.trim()).getParentFile().toString())
8 if(reactNativeMinor == 74 && reactNativePatch <= 3){
9 includeBuild("react-settings-plugin")
10 }
11}
12
13plugins { id("com.facebook.react.settings") }
14
15def getRNMinorVersion() {
16 def version = providers.exec {
17 commandLine("node", "-e", "console.log(require('react-native/package.json').version);")
18 }.standardOutput.asText.get().trim()
19
20 def coreVersion = version.split("-")[0]
21 def (major, minor, patch) = coreVersion.tokenize('.').collect { it.toInteger() }
22
23 return minor
24}
25
26if (getRNMinorVersion() >= 75) {
27 extensions.configure(com.facebook.react.ReactSettingsExtension) { ex ->
28 if (System.getenv('EXPO_UNSTABLE_CORE_AUTOLINKING') == '1') {
29 println('\u001B[32mUsing expo-modules-autolinking as core autolinking source\u001B[0m')
30 def command = [
31 'node',
32 '--no-warnings',
33 '--eval',
34 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
35 'react-native-config',
36 '--json',
37 '--platform',
38 'android'
39 ].toList()
40 ex.autolinkLibrariesFromCommand(command)
41 } else {
42 ex.autolinkLibrariesFromCommand()
43 }
44 }
45}
46
147rootProject.name = 'HelloWorld'
248
349dependencyResolutionManagement {
1157apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle");
1258useExpoModules()
1359
14apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
15applyNativeModulesSettingsGradle(settings)
60if (getRNMinorVersion() < 75) {
61 apply from: new File(["node", "--print", "require.resolve('@react-native-community/cli-platform-android/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim(), "../native_modules.gradle");
62 applyNativeModulesSettingsGradle(settings)
63}
1664
1765include ':app'
1866includeBuild(new File(["node", "--print", "require.resolve('@react-native/gradle-plugin/package.json', { paths: [require.resolve('react-native/package.json')] })"].execute(null, rootDir).text.trim()).getParentFile())
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/Podfile
MODIFIED
77ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
88ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
99
10use_autolinking_method_symbol = ('use' + '_native' + '_modules!').to_sym
11origin_autolinking_method = self.method(use_autolinking_method_symbol)
12self.define_singleton_method(use_autolinking_method_symbol) do |*args|
13 if ENV['EXPO_UNSTABLE_CORE_AUTOLINKING'] == '1'
14 Pod::UI.puts('Using expo-modules-autolinking as core autolinking source'.green)
15 config_command = [
16 'node',
17 '--no-warnings',
18 '--eval',
19 'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
20 'react-native-config',
21 '--json',
22 '--platform',
23 'ios'
24 ]
25 origin_autolinking_method.call(config_command)
26 else
27 origin_autolinking_method.call()
28 end
29end
30
1031platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4'
1132install! 'cocoapods',
1233 :deterministic_uuids => false
1334
1435prepare_react_native_project!
1536
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
4037target 'HelloWorld' do
4138 use_expo_modules!
4239 config = use_native_modules!
4946 :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
5047 # An absolute path to your application root.
5148 :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
49 :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
5450 )
5551
5652 post_install do |installer|
5753 react_native_post_install(
5854 installer,
5955 config[:reactNativePath],
60 :mac_catalyst_enabled => false
56 :mac_catalyst_enabled => false,
57 :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
6158 )
6259
6360 # This is necessary for Xcode 14, because it signs resource bundles by default
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.43",
4 "version": "51.0.56",
55 "main": "index.js",
66 "scripts": {
77 "start": "expo start --dev-client",
1010 "web": "expo start --web"
1111 },
1212 "dependencies": {
13 "expo": "~50.0.17",
14 "expo-status-bar": "~1.11.1",
13 "expo": "~51.0.28",
14 "expo-status-bar": "~1.12.1",
1515 "react": "18.2.0",
16 "react-native": "0.73.6"
16 "react-native": "0.74.5"
1717 },
1818 "devDependencies": {
1919 "@babel/core": "^7.20.0"