I'm having trouble with EAS Update. The update is successfully published and visible on the Expo Dashboard, but the physical device (installed via TestFlight) is not receiving the changes.
Note on Project Environment: This is a Bare React Native project (not Managed Workflow). I am only using EAS Update for OTA updates.
What I've checked:
The update is listed under the correct branch/channel in the dashboard.
I've restarted the app multiple times.
My Configuration:
app.config.ts
import {ExpoConfig, ConfigContext} from 'expo/config';
import * as dotenv from 'dotenv';
import path from 'path';
import pkg from './package.json';
const APP_VARIANT = process.env.APP_VARIANT || 'prod';
dotenv.config({path: path.resolve(__dirname, `.env.${APP_VARIANT}`)});
interface CustomExpoConfig extends ExpoConfig {
'react-native-google-mobile-ads'?: {
android_app_id?: string;
ios_app_id?: string;
};
}
const convertVersionToNumber = (version: string) => {
const [major, minor, patch] = version.split('.').map(Number);
return major * 1000000 + minor * 1000 + patch + 2;
};
export default ({config}: ConfigContext): CustomExpoConfig => ({
...config,
name: 'GoalWith',
slug: 'goalwith',
version: pkg.version,
runtimeVersion: pkg.version,
ios: {
...config.ios,
bundleIdentifier: 'com.goalwith.goalwith',
buildNumber: convertVersionToNumber(pkg.version).toString(),
googleServicesFile: './ios/GoogleService-Info.plist',
},
android: {
package: 'com.goalwith',
versionCode: convertVersionToNumber(pkg.version),
},
extra: {
env: process.env.ENV,
apiUrl: process.env.API_URL,
kakaoAppKey: process.env.KAKAO_APP_KEY,
googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID,
admobIdAndroid: process.env.ADMOB_ID_ANDROID,
admobIdIos: process.env.ADMOB_ID_IOS,
eas: {
projectId: '8475b304-e536-458b-aa6a-6aea6e3e6939',
},
},
'react-native-google-mobile-ads': {
android_app_id: process.env.ADMOB_ID_ANDROID,
ios_app_id: process.env.ADMOB_ID_IOS,
},
updates: {
url: 'https://u.expo.dev/8475b304-e536-458b-aa6a-6aea6e3e6939',
requestHeaders: {
'expo-channel-name': 'production',
},
},
});
eas.json
{
"build": {
"development": {
"channel": "dev",
"env": {
"APP_VARIANT": "dev"
}
},
"production": {
"channel": "production",
"env": {
"APP_VARIANT": "prod"
}
}
}
}
AppDelegate.swift
import UIKit
import Expo
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import GoogleSignIn
import RNBootSplash
import KakaoSDKCommon
import KakaoSDKAuth
import FirebaseCore
import EXUpdates
@main
class AppDelegate: ExpoAppDelegate {
var window: UIWindow?
var reactNativeDelegate: ReactNativeDelegate?
var reactNativeFactory: RCTReactNativeFactory?
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
AppController.initializeWithoutStarting()
FirebaseApp.configure()
let delegate = ReactNativeDelegate()
let factory = ExpoReactNativeFactory(delegate: delegate)
delegate.dependencyProvider = RCTAppDependencyProvider()
reactNativeDelegate = delegate
reactNativeFactory = factory
bindReactNativeFactory(factory)
self.window = UIWindow(frame: UIScreen.main.bounds)
factory.startReactNative(
withModuleName: "main",
in: self.window,
launchOptions: launchOptions
)
if let rootView = self.window?.rootViewController?.view {
RNBootSplash.initWithStoryboard("BootSplash", rootView: rootView)
}
GIDSignIn.sharedInstance.restorePreviousSignIn { user, error in
if error != nil || user == nil {
// Show the app's signed-out state.
} else {
// Show the app's signed-in state.
}
}
if let kakaoAppKey = Bundle.main.object(forInfoDictionaryKey: "KAKAO_APP_KEY") as? String {
KakaoSDK.initSDK(appKey: kakaoAppKey)
} else {
print("Warning: KAKAO_APP_KEY not found in Info.plist")
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
var handled = false
handled = GIDSignIn.sharedInstance.handle(url)
if handled { return true }
if (AuthApi.isKakaoTalkLoginUrl(url)) {
handled = AuthController.handleOpenUrl(url: url)
if handled { return true }
}
return super.application(app, open: url, options: options)
}
}
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
override func sourceURL(for bridge: RCTBridge) -> URL? {
// needed to return the correct URL for expo-dev-client.
bridge.bundleURL ?? bundleURL()
}
override func bundleURL() -> URL? {
#if DEBUG
RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")
#else
if let updatesUrl = AppController.sharedInstance.launchAssetUrl() {
return updatesUrl
}
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
#endif
}
}
Expo.plist
<?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>EXUpdatesEnabled</key>
<true/>
<key>EXUpdatesCheckOnLaunch</key>
<string>ALWAYS</string>
<key>EXUpdatesLaunchWaitMs</key>
<integer>0</integer>
<key>EXUpdatesRequestHeaders</key>
<dict>
<key>expo-channel-name</key>
<string>production</string>
</dict>
<key>EXUpdatesURL</key>
<string>https://u.expo.dev/8475b304-e536-458b-aa6a-6aea6e3e6939</string>
<key>EXUpdatesRuntimeVersion</key>
<string>1.0.5</string>
</dict>
</plist>

Here is my expo dashboard screenshot. And I confirm that I install same version 1.0.5
If you need to see any other configuration files or specific logs, please let me know and I will update the post immediately.
Top comments (0)