DEV Community

Aswin Andro
Aswin Andro

Posted on

How to Fix Expo Push Token Timeouts and 'EXNotificationsDelegate' Build Errors

๐Ÿš€ Solving Expo Push Notification Headaches

If you are running a React Native project using the Expo Bare Workflow (SDK 50+), you've likely hit a wall with iOS push notifications. Youโ€™ve set up your APNs .p8 key, but things still break.

๐Ÿ”ด The Problems

  1. The JS Timeout: getExpoPushTokenAsync consistently throws Token fetch timed out after 6000ms.
  2. The Xcode Error: error: cannot find 'EXNotificationsDelegate' in scope.

๐Ÿ›  Phase 1: Fixing the JavaScript Race Condition

On iOS, the first token generation is slow. If multiple components call getExpoPushToken at the same time, the native bridge can hang.

The Solution: A Global Fetch Lock

We use a Singleton Promise pattern. This ensures that even if 10 components call your token function at once, only one actual request is sent to the native side.


typescript
import * as Notifications from 'expo-notifications';

// ๐Ÿ”’ Global lock to prevent bridge congestion
let globalTokenFetchPromise: Promise<string | null> | null = null;

export async function getExpoPushToken() {
  if (globalTokenFetchPromise) {
    return await globalTokenFetchPromise;
  }

  globalTokenFetchPromise = (async () => {
    try {
      // We wrap the Expo call in a race to control the timeout manually
      const token = await Promise.race([
        Notifications.getExpoPushTokenAsync({ 
          projectId: 'your-project-id' 
        }),
        new Promise((_, reject) => 
          setTimeout(() => reject(new Error('Push Token Timeout')), 10000)
        )
      ]) as Notifications.ExpoPushToken;

      return token.data;
    } catch (error) {
      console.error("โŒ Push Token Error:", error);
      return null;
    }
  })();

  try {
    return await globalTokenFetchPromise;
  } finally {
    // Reset so the next call can try again if the first failed
    globalTokenFetchPromise = null;
  }
}

## ๐Ÿ›  Phase 2: Solving the 'EXNotificationsDelegate' Build Error

In Expo SDK 50+, many developers encounter the error: `error: cannot find 'EXNotificationsDelegate' in scope`. This happens because `EXNotificationsDelegate` is an internal Objective-C class that is no longer exposed to Swift in the same way.

### The Modern Solution: Inherit from `ExpoAppDelegate`

Instead of manually trying to bridge Objective-C delegates, you should inherit your `AppDelegate` from `ExpoAppDelegate`. This class is part of the newer Expo Modules architecture and automatically handles the plumbing for APNs tokens and notification events.
---
import UIKit
import Expo
import ExpoModulesCore
import React
import React_RCTAppDelegate

@main
class AppDelegate: ExpoAppDelegate { 
  var window: UIWindow?

  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
  ) -> Bool {

    let delegate = ReactNativeDelegate()
    let factory = RCTReactNativeFactory(delegate: delegate)
    self.reactNativeDelegate = delegate
    self.factory = factory 

    window = UIWindow(frame: UIScreen.main.bounds)

    // โš ๏ธ Replace "YourAppName" with the name in your app.json
    factory.startReactNative(withModuleName: "YourAppName", in: window, launchOptions: launchOptions)

    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  override func application(_ application: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    return super.application(application, open: url, options: options) || RCTLinkingManager.application(application, open: url, options: options)
  }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)