invertase / react-native-firebase

🔥 A well-tested feature-rich modular Firebase implementation for React Native. Supports both iOS & Android platforms for all Firebase services.
https://rnfirebase.io
Other
11.69k stars 2.21k forks source link

🔥firebase.messaging().ios.getAPNSToken() always returns null #1990

Closed naorzr closed 5 years ago

naorzr commented 5 years ago

Issue

On the server side we are using amazon sns service for the fcm communication with the devices, so the token returned from firebase.messaging().getToken() is just too long. So we've figured we should get the token with getAPNStoken call

import firebase from 'react-native-firebase'

const token = await firebase.messaging().ios.getAPNSToken() // null

only that this call returns null no matter what I try, the documentation isn't very clear about how to use and use cases, but I guess that it should be inferred from the name. anyhow, I've also tried to register first using

firebase.messaging().ios.registerForRemoteNotifications().then(() => 
const token = firebase.messaging().ios.getAPNSToken()) // null

still no success. Am I doing something wrong, or is that a bug?

Project Files

iOS

ios/Podfile:

# Uncomment the next line to define a global platform for your project
platform :ios, '10.0'

require_relative '../node_modules/@unimodules/core/podfile-macro'

# The target name is most likely the name of your project.
target 'broadcaster' do

  # Your 'node_modules' directory is probably in the root of your project,
  # but if not, adjust the `:path` accordingly
  pod 'React', :path => '../node_modules/react-native', :subspecs => [
    'Core',
    'CxxBridge', # Include this for RN >= 0.47
    'DevSupport', # Include this to enable In-App Devmenu if RN >= 0.43
    'RCTText',
    'RCTNetwork',
    'RCTWebSocket', # Needed for debugging
    'RCTAnimation', # Needed for FlatList and animations running on native UI thread
    'RCTImage'
    # Add any other subspecs you want to use in your project
  ]
  # Explicitly include Yoga if you are using RN >= 0.42.0
  pod 'yoga', :path => '../node_modules/react-native/ReactCommon/yoga'

  # Third party deps podspec link
  pod 'DoubleConversion', :podspec => '../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec'
  pod 'glog', :podspec => '../node_modules/react-native/third-party-podspecs/glog.podspec'
  pod 'Folly', :podspec => '../node_modules/react-native/third-party-podspecs/Folly.podspec'
  pod 'RNVectorIcons', :path => '../node_modules/react-native-vector-icons'
  pod 'EXFont', path: '../node_modules/expo-font/ios'
  pod 'EXImagePicker', path: '../node_modules/expo-image-picker/ios'
  pod 'react-native-orientation-locker', :path => '../node_modules/react-native-orientation-locker/react-native-orientation-locker.podspec'
  pod 'EXAV', path: '../node_modules/expo-av/ios'
  pod 'RNImageCropPicker', :path =>  '../node_modules/react-native-image-crop-picker'
  pod 'RNDeviceInfo', :path => '../node_modules/react-native-device-info'
  pod 'Firebase/Core', '~> 5.15.0'
  pod 'Firebase/Messaging', '~> 5.15.0'
  use_unimodules!

end

AppDelegate.m:

/**
 * Copyright (c) 2015-present, Facebook, Inc.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import <EXCore/EXModuleRegistry.h>
#import <EXReactNativeAdapter/EXNativeModulesProxy.h>
#import <EXReactNativeAdapter/EXModuleRegistryAdapter.h>
#import "Orientation.h"
#import <Firebase.h>
#import "RNFirebaseNotifications.h"
#import "RNFirebaseMessaging.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [FIRApp configure];
  [RNFirebaseNotifications configure];
  self.moduleRegistryAdapter = [[EXModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[EXModuleRegistryProvider alloc] init]];
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"broadcaster" initialProperties:nil];
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  return YES;
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
  [[RNFirebaseNotifications instance] didReceiveLocalNotification:notification];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(nonnull NSDictionary *)userInfo
fetchCompletionHandler:(nonnull void (^)(UIBackgroundFetchResult))completionHandler{
  [[RNFirebaseNotifications instance] didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}

- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
  [[RNFirebaseMessaging instance] didRegisterUserNotificationSettings:notificationSettings];
}

- (NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
  NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge andExperience:nil];
  // If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
  return extraModules;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
}

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
  return [Orientation getOrientation];
}

@end

Environment

nigel-smk commented 5 years ago

I have seen the same issue. I was able to get the APNs token only on first launch though.

naorzr commented 5 years ago

@nigel-smk the first launch of the app? cause I didn't managed to even make that happen

nigel-smk commented 5 years ago

@naorzr I think I have solved this.

I found this issue for a competing library that had the same issue

I don't understand it entirely but it seems that we need to registerForRemoteNotifications first.

The react-native-firebase messaging reference docs are currently broken. But if we dig into the docs in the repo we find that react-native-firebase has the registerForRemoteNotifications method as well.

This reliably got me the APNs Token:

    firebase.messaging().ios.registerForRemoteNotifications().then(() => {
      firebase.messaging().ios.getAPNSToken().then(token => {
        // token not null
      });
    })

While this did not:

    firebase.messaging().ios.getAPNSToken().then(token => {
      // token always null
    });
nigel-smk commented 5 years ago

Based on my reading of the solution to https://github.com/evollu/react-native-fcm/issues/510 I think that this issue is a bug with react-native-firebase

naorzr commented 5 years ago

@nigel-smk thanks for taking your time with the comment though I have done it, and still getting null..(as written in the original post) is the token not being null behaviour persistent for you?

nigel-smk commented 5 years ago

@naorzr Are you using a real device? The simulator will never get and APNs token

When running on a real device I get the above described behaviour every time. I have switched between the two appraoches 3-4 times now and without the register call I always get null.

naorzr commented 5 years ago

@nigel-smk yea I am

naorzr commented 5 years ago

@nigel-smk any chance you could please post here your AppDelegate.m and Podfile? Can't get this to work no matter what, reinstalled and refollowed the tutorials over and over.

naorzr commented 5 years ago

well in the end i've reinstalled, reconfiged everything from scratch and it was resolved.

evanjmg commented 5 years ago

I'm having the same issue

luatvudinh commented 5 years ago

Add this function to AppDelegate.m:

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [FIRMessaging messaging].APNSToken = deviceToken;
}

Hope this will be helpful.

ivangr1 commented 5 years ago

I was having the same issue for days and nothing from the above helped, but I got it working myself. I just added this to didFinishLaunchingWithOptions method in AppDelegate.m:

[[UIApplication sharedApplication] registerForRemoteNotifications];

ghost commented 5 years ago

This is kind of strange, but for me this was behavior on ios device:

  console.log(await firebase.messaging().hasPermission())         // false

  console.log(await firebase.messaging().getToken())              // got FCM token
  console.log(await firebase.messaging().ios.getAPNSToken())      // null

When I requested permissions:

  console.log(await firebase.messaging().hasPermission())           // false
  console.log(await firebase.messaging().requestPermission())       // allow on popup

  console.log(await firebase.messaging().getToken())                // got FCM token
  console.log(await firebase.messaging().ios.getAPNSToken())        // got APNS token

So also try to check permissions 😄 It is kind of weird that you will get FCM token but no APNS I would expect the same behavior for both

The point here seems to be that even if method hasPermission() returns true, you need to call requestPermission() in order to get the APNS token

Jcbgid commented 5 years ago

I was running into this issue despite trying everything above until just recently.

Turns out my GoogleService-Info.plist file was not correctly added to my ios project folder. Once I added it at the same level as AppDelegate.m in XCode firebase.messaging().ios.getAPNSToken() returned an actual APNS token instead of null.

alipetarian commented 5 years ago

This helped me get the APNS token:

  firebase
    .messaging()
    .ios.registerForRemoteNotifications()
    .then(() => {
      console.log('REGISTER FOR REMOTE NOTIFICATIONS');

      firebase
        .messaging()
        .ios.getAPNSToken()
        .then(token => {
          console.log(
            'APNS TOKEN AFTER REGISTRATION FOR BACKGROUND',
            token,
          );
        });
    });
luatnd commented 4 years ago

In my case, calling ios.registerForRemoteNotifications() in release build:

firebase.messaging().ios.registerForRemoteNotifications().then(() => {
     firebase.messaging().ios.getAPNSToken().then((apnsToken) => {
              console.log(apnsToken)
      })
})

apnsToken sometime null, is this normal behavior?

MinskLeo commented 4 years ago

I was having the same issue for days and nothing from the above helped, but I got it working myself. I just added this to didFinishLaunchingWithOptions method in AppDelegate.m:

[[UIApplication sharedApplication] registerForRemoteNotifications];

This really helped me! Thank you so much buddy!

mifi commented 4 years ago

Even using registerForRemoteNotifications before calling getAPNSToken I get null the first time after the app launches.

    await firebase.messaging().ios.registerForRemoteNotifications();
    const apnsToken = await firebase.messaging().ios.getAPNSToken();
    apnsToken === null

Then when calling getAPNSToken again later, it correctly returns the token.

I'm suspecting that the promise returned from registerForRemoteNotifications does not wait for the token to be registered before resolving, so I think it shouldn't be returning a promise at all.

I think adding a registerForRemoteNotifications or didRegisterForRemoteNotificationsWithDeviceToken in AppDelegate.m is not a solid solution because it may still cause a race condition where the token has not been registered before getAPNSToken is called. (if registering for token takes longer than the JS code takes to call getAPNSToken). This is probably why people are seeing that it works sometimes and other times not.

mifi commented 4 years ago

Although very hacky, this seems to work and will never return null:

async function getAPNSToken() {
  while (true) {
    const apnsToken = await firebase.messaging().ios.getAPNSToken();
    if (apnsToken !== null) return apnsToken;
    await new Promise((resolve) => setTimeout(resolve, 1000));
  }
}

 await firebase.messaging().ios.registerForRemoteNotifications();
 const apnsToken = await getAPNSToken();
ridawidchlodnicki commented 2 years ago

Add this function to AppDelegate.m:

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
  [FIRMessaging messaging].APNSToken = deviceToken;
}

Hope this will be helpful.

Many thanks, you saved my day!

dalybouba commented 2 years ago

Hi, I used firebase to get device token but always return null : askNotificationPermission(){ if (this.platform.is('cordova') && this.platform.is('ios')) { this.firebase.grantPermission().then(function(hasPermission){ console.log("Permission was " + (hasPermission ? "granted" : "denied")); }).finally(()=>{
this.firebase.onApnsTokenReceived() .subscribe(token => { const deviceData = { reg_id: token, os: this.device.platform }; this.services.device_data = deviceData; localStorage.setItem('deviceData', JSON.stringify(deviceData)); })})}}

this is my code in ionic application

idylmz commented 8 months ago

I was having the same issue for days and nothing from the above helped, but I got it working myself. I just added this to didFinishLaunchingWithOptions method in AppDelegate.m:

[[UIApplication sharedApplication] registerForRemoteNotifications];

I have no idea why, but this also solved my issue. The strange thing is, I did not changed anything in my project. But I believe it happened right after I implemented react-native-splash-screen.

My AppDelegate file was changed from this

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [FIRApp configure];
  self.moduleName = @"Gardrows";
  // 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 = @{};

  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

To this after RN Splash Screen:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [FIRApp configure];
  self.moduleName = @"Gardrows";
  // 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 = @{};
  bool didFinish = [super application:application didFinishLaunchingWithOptions:launchOptions];
  [RNSplashScreen show];
  return didFinish;
}

I believe somehow I messed with Firebase initial configuration by changing return of the function even though the type and value checks out.

Now the successful solution of the AppDelegate goes like:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [FIRApp configure];
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  self.moduleName = @"Gardrows";
  // 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 = @{};
  bool didFinish = [super application:application didFinishLaunchingWithOptions:launchOptions];
  [RNSplashScreen show];
  return didFinish;
}