Skip to content

🔥 [🐛] Notifications can't be received in testflight, simulator and real device build works. #8472

@benevolentsocialplanner

Description

I built the app with react native cli and the notifications successfully do work in simulator whether it's a debug or release build. However, on testflight it won't work. I think that it's related to bundling of the ios folders. I had problems with bundling and what fixed that is the following:

    "prod": "npx react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ./ios/main.jsbundle --assets-dest ./assets",
    "flight": "npx react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ./ios/main.jsbundle --assets-dest ./ios",

I added the main.jsbundle file to the xcode, otherwise the changes wouldn't be noticed by the new build.
After fixing this, i have an issue that the notifications are never delivered to the device using testflight.
Let me explain better, i attach the apns token to device token in app delegate file below. And registerDeviceForRemoteNotifications function will throw an empty object error.

import UIKit
import React
import React_RCTAppDelegate
import ReactAppDependencyProvider
import FirebaseCore
import FirebaseMessaging
import UserNotifications

@main
class AppDelegate: RCTAppDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
  override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    self.moduleName = "mobile_nakliye"
    self.dependencyProvider = RCTAppDependencyProvider()

    // You can add your custom initial props in the dictionary below.
    // They will be passed down to the ViewController used by React Native.
    self.initialProps = [:]

    // Configure Firebase (minimal setup)
    FirebaseApp.configure()
    
    // Set messaging delegate
    Messaging.messaging().delegate = self
    
    // Request permission for notifications
    UNUserNotificationCenter.current().delegate = self
    let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
    UNUserNotificationCenter.current().requestAuthorization(
        options: authOptions,
        completionHandler: { _, _ in }
    )
    application.registerForRemoteNotifications()

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

  override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
    Messaging.messaging().apnsToken = deviceToken
    // Removed super call that was causing the error
  }
  
  func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String?) {
    // Store this token for sending FCM messages to this device
    print("Firebase registration token: \(String(describing: fcmToken))")
  }

  override func sourceURL(for bridge: RCTBridge) -> URL? {
    return self.bundleURL()
  }

  override func bundleURL() -> URL? {
    #if DEBUG
      return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: "index")
    #else
      return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
    #endif
  }
}
require_relative File.join(__dir__, '../node_modules/react-native/scripts/react_native_pods')

platform :ios, min_ios_version_supported
prepare_react_native_project!


linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
  Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
  use_frameworks! :linkage => linkage.to_sym
else
  use_frameworks!
  $RNFirebaseAsStaticFramework = false
end

target 'mobile_nakliye' do
  config = use_native_modules!

  use_react_native!(
    :path => config[:reactNativePath],
    :app_path => "#{Pod::Config.instance.installation_root}/.."
  )

  pod 'FirebaseCore', :modular_headers => true
  pod 'Firebase/Messaging', :modular_headers => true

  pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'

  pre_install do |installer|
    installer.pod_targets.each do |pod|
      if pod.name.eql?('RNReanimated')
        def pod.build_type
          Pod::BuildType.static_library
        end
      end
     end
   end

  post_install do |installer|
    react_native_post_install(
      installer,
      config[:reactNativePath],
      :mac_catalyst_enabled => false,
      # :ccache_enabled => true
    )
  end
end

The js hook that I handle the firebase notifications:

import { useEffect } from 'react';
import messaging from '@react-native-firebase/messaging';
import { updateUser } from '../services/api';

export const useNotifications = (firebaseInitialized: boolean) => {
  useEffect(() => {
    console.log('firebaseInitialized', firebaseInitialized);
    if (!firebaseInitialized) return;

    const setupNotifications = async () => {
      try {
        console.log('Setting up notifications...');
        const authStatus = await messaging().requestPermission();

        const enabled =
          authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
          authStatus === messaging.AuthorizationStatus.PROVISIONAL;

          console.log('Notification permission status:', enabled);
        if (!enabled) {
          console.log('Notification permissions not granted');
          return;
        }
        let isPermitted = await messaging().hasPermission();
        console.log(isPermitted, "isPermitted");
        if (isPermitted == 0 || isPermitted == 1) {
          //ask for permission
          const authStatus = await messaging().requestPermission();
          const result =
            authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
            authStatus === messaging.AuthorizationStatus.PROVISIONAL;

            if (result == true) {
              isPermitted = true
            } else {
              isPermitted = false
            }
        }

        console.log('Notification permission status:', isPermitted);
        if (!isPermitted) return;
        
        await messaging().registerDeviceForRemoteMessages();

        // 3. Add delay to ensure APNS token is available
        await new Promise(resolve => setTimeout(resolve, 1000));

        const regularToken = await messaging().getToken();
        console.log(regularToken, " regularToken");

        messaging().onTokenRefresh(token => {
          console.log('Token refreshed:', token);
          if (token) {
            updateUser({ notificationId: token });
            messaging().subscribeToTopic('allDevices');
          }
        });

        if (regularToken) {
          
          const res = await updateUser({ notificationId: regularToken });
          console.log('fcmtoken updated', res)
          await messaging().subscribeToTopic('allDevices');
        }
      } catch (error) {
        console.error("Notification setup error:", error);
      }
    };

    // Setup message handlers
    const unsubscribeOnMessage = messaging().onMessage((remoteMessage) => {
      console.log("Foreground message:", remoteMessage);
    });

    setupNotifications();

    return () => {
      unsubscribeOnMessage();
    };
  }, [firebaseInitialized]);
};

I'm assuming the problem lays in the native files or however the javascript is bundled into the native code.
Before making a new archive build to testflight i ran npm run flight command as shown at the top.
Any help is appreciated.

package.json:

{
  "name": "mobile_nakliye",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "lint": "eslint .",
    "start": "react-native start",
    "test": "jest",
    "postinstall": "patch-package",
    "dev": "npx react-native bundle --platform ios --dev true --entry-file index.js --bundle-output ./ios/main.jsbundle --assets-dest ./assets",
    "prod": "npx react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ./ios/main.jsbundle --assets-dest ./assets",
    "flight": "npx react-native bundle --platform ios --dev false --entry-file index.js --bundle-output ./ios/main.jsbundle --assets-dest ./ios",
    "pods": "cd ios; rm -rf Pods Podfile.lock; rm -rf build/; bundle exec pod install --verbose; cd .."
  },
  "dependencies": {
    "@react-native-async-storage/async-storage": "^2.1.1",
    "@react-native-community/geolocation": "^3.4.0",
    "@react-native-firebase/app": "^21.7.4",
    "@react-native-firebase/messaging": "^21.7.4",
    "@react-navigation/bottom-tabs": "^7.2.0",
    "@react-navigation/native": "^7.0.14",
    "@react-navigation/native-stack": "^7.2.0",
    "@react-navigation/stack": "^7.1.1",
    "@tanstack/react-query": "^5.66.0",
    "axios": "^1.7.9",
    "dotenv": "^16.4.7",
    "patch-package": "^8.0.0",
    "postinstall-postinstall": "^2.1.0",
    "react": "^18.3.1",
    "react-native": "^0.77.0",
    "react-native-agora": "^4.5.1",
    "react-native-geolocation-service": "^5.3.1",
    "react-native-gesture-handler": "2.23.0",
    "react-native-get-random-values": "^1.11.0",
    "react-native-google-places-autocomplete": "^2.5.7",
    "react-native-image-picker": "^8.0.0",
    "react-native-linear-gradient": "^2.8.3",
    "react-native-maps": "^1.20.1",
    "react-native-reanimated": "^3.16.7",
    "react-native-safe-area-context": "^5.2.0",
    "react-native-screens": "^4.6.0",
    "react-native-svg": "^15.11.2",
    "react-native-vector-icons": "^10.2.0",
    "react-native-webview": "^13.13.2",
    "socket.io-client": "^4.8.1"
  },
  "devDependencies": {
    "@babel/core": "^7.25.2",
    "@babel/preset-env": "^7.25.3",
    "@babel/runtime": "^7.25.0",
    "@react-native-community/cli": "15.0.1",
    "@react-native-community/cli-platform-android": "15.0.1",
    "@react-native-community/cli-platform-ios": "15.0.1",
    "@react-native/babel-preset": "0.77.0",
    "@react-native/eslint-config": "0.77.0",
    "@react-native/metro-config": "0.77.0",
    "@react-native/typescript-config": "0.77.0",
    "@types/jest": "^29.5.13",
    "@types/react": "^18.2.6",
    "@types/react-native-vector-icons": "^6.4.18",
    "@types/react-test-renderer": "^18.0.0",
    "eslint": "^8.19.0",
    "has-flag": "^5.0.1",
    "jest": "^29.6.3",
    "patch-package": "^8.0.0",
    "prettier": "2.8.8",
    "react-native-dotenv": "^3.4.11",
    "react-test-renderer": "18.3.1",
    "typescript": "5.0.4"
  },
  "engines": {
    "node": ">=18"
  }
}

react-native info output:

System:
  OS: macOS 15.3.2
  CPU: (8) arm64 Apple M2
  Memory: 166.00 MB / 8.00 GB
  Shell:
    version: "5.9"
    path: /bin/zsh
Binaries:
  Node:
    version: 18.18.2
    path: ~/.nvm/versions/node/v18.18.2/bin/node
  Yarn:
    version: 1.22.22
    path: ~/.nvm/versions/node/v18.18.2/bin/yarn
  npm:
    version: 10.8.2
    path: ~/.nvm/versions/node/v18.18.2/bin/npm
  Watchman:
    version: 2025.03.10.00
    path: /opt/homebrew/bin/watchman
Managers:
  CocoaPods:
    version: 1.16.2
    path: /Users/atakanyesilkayali/.rbenv/shims/pod
SDKs:
  iOS SDK:
    Platforms:
      - DriverKit 24.2
      - iOS 18.2
      - macOS 15.2
      - tvOS 18.2
      - visionOS 2.2
      - watchOS 11.2
  Android SDK:
    API Levels:
      - "30"
      - "33"
      - "34"
      - "35"
    Build Tools:
      - 30.0.3
      - 34.0.0
      - 35.0.0
    System Images:
      - android-34 | Google APIs ARM 64 v8a
      - android-34 | Google Play ARM 64 v8a
      - android-35 | Google Play ARM 64 v8a
      - android-35 | Pre-Release 16 KB Page Size Google APIs ARM 64 v8a
      - android-TiramisuPrivacySandbox | Google APIs ARM 64 v8a
    Android NDK: Not Found
IDEs:
  Android Studio: 2024.1 AI-241.18034.62.2411.12169540
  Xcode:
    version: 16.2/16C5032a
    path: /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild
Languages:
  Java:
    version: 17.0.14
    path: /opt/homebrew/Cellar/openjdk@17/17.0.14/libexec/openjdk.jdk/Contents/Home/bin/javac
  Ruby:
    version: 3.1.0
    path: /Users/atakanyesilkayali/.rbenv/shims/ruby
npmPackages:
  "@react-native-community/cli":
    installed: 15.0.1
    wanted: 15.0.1
  react:
    installed: 18.3.1
    wanted: ^18.3.1
  react-native:
    installed: 0.77.2
    wanted: ^0.77.0
  react-native-macos: Not Found
npmGlobalPackages:
  "*react-native*": Not Found
Android:
  hermesEnabled: true
  newArchEnabled: true
iOS:
  hermesEnabled: true
  newArchEnabled: false```
- **Platform that you're experiencing the issue on**:
  - [x] iOS
  - [ ] Android
  - [x] **iOS** but have not tested behavior on Android
  - [ ] **Android** but have not tested behavior on iOS
  - [ ] Both

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions