DEV Community

Sang
Sang

Posted on

EAS Update not downloading on production build despite matching channels and successful publish

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',
    },
  },
});

Enter fullscreen mode Exit fullscreen mode

eas.json

{
  "build": {
    "development": {
      "channel": "dev",
      "env": {
        "APP_VARIANT": "dev"
      }
    },
    "production": {
      "channel": "production",
      "env": {
        "APP_VARIANT": "prod"
      }
    }
  }
}

Enter fullscreen mode Exit fullscreen mode

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
  }
}

Enter fullscreen mode Exit fullscreen mode

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>

Enter fullscreen mode Exit fullscreen mode

Here is my expo dashboard screenshot. And I confirm that I install same version 1.0.5
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)