React-native-firebase: [馃悰] Bug Report Title - setBackgroundMessageHandler doesn't work - Messaging v7.9.0 iOS

Created on 8 Oct 2020  路  11Comments  路  Source: invertase/react-native-firebase


Issue



Hi guys,

I was working with "@react-native-firebase/app": "^8.4.1" and "@react-native-firebase/messaging": "^7.8.4", few days ago and everything was fine, except for setBackgroundHandler that never was called when app is closed.

Yesterday, I updated my dependencies searching some solution to handler problem but now I'm stunned: setBackgroundHandler doesn't work at any case. No when app is on background and less when it is killed.

Today, I've made a new cleaning project with only RNFB dependencies, but this problem persists. Maybe, I'm on the wrong way. Please check my files.


Project Files






Javascript

Click To Expand

#### `package.json`:

# N/A

{
  "name": "notisimple",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint .",
    "build:ios": "react-native bundle --entry-file='index.js' --bundle-output='./ios/main.jsbundle' --dev=false --platform='ios'"
  },
  "dependencies": {
    "@react-native-firebase/app": "^8.4.5",
    "@react-native-firebase/messaging": "^7.9.0",
    "react": "16.13.1",
    "react-native": "0.63.3"
  },
  "devDependencies": {
    "@babel/core": "^7.8.4",
    "@babel/runtime": "^7.8.4",
    "@react-native-community/eslint-config": "^1.1.0",
    "babel-jest": "^25.1.0",
    "eslint": "^6.5.1",
    "jest": "^25.1.0",
    "metro-react-native-babel-preset": "^0.59.0",
    "react-test-renderer": "16.13.1"
  },
  "jest": {
    "preset": "react-native"
  }
}

#### `index.js`:
# N/A

import React from 'react' 
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import messaging from '@react-native-firebase/messaging'; 

messaging().setBackgroundMessageHandler(async (remoteMessage) => { 
    console.log('let me know'); 
});

function HeadlessCheck({ isHeadless }){
    console.log(isHeadless);
    if (isHeadless) { 
        return null;
    } 
    return <App/>;
} 
AppRegistry.registerComponent(appName, ()=> HeadlessCheck);  

#### `App.tsx`:

import React, {
  useEffect,
  useState
} from 'react';
import { AppState,View } from 'react-native'   
import ServiceFCM from './src/functions/FCM.service';    

let onLoad = false;

const App: React.FC = () => {
  const [onNotificationRecieved, setNofiticationRecieved] = useState(
    false,
  );
  const [notificationBehaviour, setNotificationBehaviour] = useState(
    '',
  );

  const [permissions, setPermissions] = useState({});

  useEffect(() => {
    onLoad = false;
    async function init() {  
      ServiceFCM.requestOnlyUserPermission().then(async (auth) => { 
        await ServiceFCM.registerDeviceAPNS(auth);
        await ServiceFCM.registerFCM(onRegister, onNoti, onOpenNoti);
      });

      function onRegister(token) {
      }

      function onNoti(remoteMessage) { 
        console.log(remoteMessage, 'llegadaNoti', AppState.currentState);  
      }

      async function onOpenNoti(noti) { 
        console.log(noti, 'abierto'); 
      }

      return;
    }

    if (!onLoad) {
      onLoad = true;
      init().then((x) => {
      }).catch((err) => {  
      }).finally(() => { 
      });
    }

    return () => { 
    };
  }, [notificationBehaviour]);

  return (
    <> 
        <View /> 
    </>
  );
};

export default App;

#### `src/functions/FCM.services.ts`:
# N/A
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';   
import { callFunction } from './UtilNet.service';

class ServiceFCM { 
    registerDeviceAPNS = async (auth) => {
        if (Platform.OS === 'ios') {
            try {
                let atl = await messaging().registerDeviceForRemoteMessages();
                console.log('Register APNS Success', auth ? auth.toString() : '(Re requerired)');
                await messaging().setAutoInitEnabled(true);

                let response = await callFunction('APN Token', {
                    actioner: async () => {
                        return await messaging().getAPNSToken();
                    },
                    successCondition: (resp) => {
                        return resp !== null;
                    },
                    attemps: 4
                });
            }
            catch (err) { 
                console.log('Register APNS / Error Message ', err.message); 
            }
        }
    };

    registerFCM = async (
        onRegister,
        onNotification,
        onOpenNotification
    ) => {
        await this.checkPermission(onRegister);

        messaging().onTokenRefresh(async (fcmToken) => { 
            console.log('token invalidado');
        });

        messaging().onNotificationOpenedApp(async (remoteMessage) => {
            // writeDebug('Abierto Noti', remoteMessage);
            await onOpenNotification(remoteMessage); 
        });

        messaging()
            .getInitialNotification()
            .then((remoteMessage) => {
                if (remoteMessage) { 
                }
            });

        messaging().onMessage((remoteMessage) => { 
            if (remoteMessage) {
                onNotification(remoteMessage);
            }
        });
    };

    refreshToken = async () => {
        await messaging().deleteToken();
        await this.requestUserPermission();
    };

    requestUserPermission = async (onRegister) => {
        let userPermission = await messaging().requestPermission();
        console.log('Permiso cedido', userPermission);
        if (userPermission == messaging.AuthorizationStatus.AUTHORIZED || userPermission == messaging.AuthorizationStatus.PROVISIONAL)
            await this.getFcmToken(onRegister); 

    };

    requestOnlyUserPermission = async () => {
        return messaging().requestPermission();
    };

    getFcmToken = async (onRegister) => {
        let fcmToken = null;
        try {
            fcmToken = await messaging().getToken();
        }
        catch (err) { 
            console.log('Get Token FCM / Error Message ', err.message); 
        }
        if (fcmToken) { 
            console.log('Token en Memoria', fcmToken); 
        } else {
            console.log('Token FCM', 'No token received');
        }
    };

    getCurrentToken = async () => {
        let token = await messaging().getAPNSToken();
        return token;
    }

    checkPermission = async (onRegister) => {
        try {
            let enabled = await messaging().hasPermission();
            console.log('Tiene Permiso Noti', enabled);
            if (enabled == messaging.AuthorizationStatus.AUTHORIZED || enabled == messaging.AuthorizationStatus.PROVISIONAL) {
                await this.getFcmToken(onRegister);
            } else {
                await this.requestUserPermission(onRegister);
            }
        } catch (err) { 
            console.log('Permission/ Error Message ', err.message); 
        }
    };
}

export default new ServiceFCM();

#### `src/functions/UtilNet.service.ts`

let TIME_OUT = 60000;

type ConfigCallFunction = {
    actioner: any,
    successCondition: any,
    attemps?: number
}


export const callFunction = async function (name: string, config: ConfigCallFunction) {
    if (!(config.attemps > 0))
        config.attemps = 1;
    let currentAttemp = 0;
    let response = null;
    let formatAttemp = () => {
        return config.attemps > 1 ? `Att. ${currentAttemp + 1}` : '';
    }

    let start = null, finish = null;
    let callFunctions = async () => {
        try { 
            start = Date.now();
            response = await config.actioner();
            finish = Date.now() - start; 
        }
        catch (err) {
            if (!finish)
                finish = Date.now() - start;  
        }
        return response;
    }

    while (currentAttemp < config.attemps) {
        if (config.successCondition(await callFunctions()))
            break;
        currentAttemp++;
    }

    return response;
}

### iOS
Click To Expand

#### `ios/Podfile`: - [ ] I'm not using Pods - [x] I'm using Pods and my Podfile looks like:

# N/A

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '10.0'

target 'notisimple' do
  config = use_native_modules!

  use_react_native!(:path => config["reactNativePath"])

  target 'notisimpleTests' do
    inherit! :complete
    # Pods for testing
  end

  # Enables Flipper.
  #
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable these next few lines.
  use_flipper!
  post_install do |installer|
    flipper_post_install(installer)
  end
end

target 'notisimple-tvOS' do
  # Pods for notisimple-tvOS

  target 'notisimple-tvOSTests' do
    inherit! :search_paths
    # Pods for testing
  end
end


#### `AppDelegate.m`:
// N/A
#import "AppDelegate.h"

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

#import <Firebase.h>
#import "RNFBMessagingModule.h"

#if RCT_DEV
#import <React/RCTDevLoadingView.h>
#endif

#ifdef FB_SONARKIT_ENABLED
#import <FlipperKit/FlipperClient.h>
#import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
#import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
#import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
#import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
#import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

static void InitializeFlipper(UIApplication *application) {
  FlipperClient *client = [FlipperClient sharedClient];
  SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
  [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
  [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
  [client addPlugin:[FlipperKitReactPlugin new]];
  [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
  [client start];
}
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif

  [FIRApp configure];
  NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];

#ifdef DEBUG

  NSURL *jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];

#else
  NSURL *jsCodeLocation = [CodePush bundleURL];
#endif

  RCTBridge *bridge = [[RCTBridge alloc] initWithBundleURL:jsCodeLocation
                                              moduleProvider:nil
                                               launchOptions:launchOptions];
#if RCT_DEV
  [bridge moduleForClass:[RCTDevLoadingView class]];
#endif
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"notisimple"
                                            initialProperties:appProperties];

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

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

@end



Environment

Click To Expand

**`react-native info` output:**

System:
    OS: macOS 10.15.7
    CPU: (4) x64 Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
    Memory: 411.21 MB / 11.32 GB
    Shell: 5.7.1 - /bin/zsh
  Binaries:
    Node: 14.9.0 - /usr/local/bin/node
    Yarn: Not Found
    npm: 6.14.8 - /usr/local/bin/npm
    Watchman: 4.9.0 - /usr/local/bin/watchman
  Managers:
    CocoaPods: 1.9.3 - /usr/local/bin/pod
  SDKs:
    iOS SDK:
      Platforms: iOS 14.0, DriverKit 19.0, macOS 10.15, tvOS 14.0, watchOS 7.0
    Android SDK:
      API Levels: 29, 30
      Build Tools: 28.0.3, 29.0.2, 30.0.2
      System Images: android-29 | Intel x86 Atom_64, android-29 | Google APIs Intel x86 Atom, android-29 | Google APIs Intel x86 Atom_64
      Android NDK: Not Found
  IDEs:
    Android Studio: 4.0 AI-193.6911.18.40.6626763
    Xcode: 12.0.1/12A7300 - /usr/bin/xcodebuild
  Languages:
    Java: 1.8.0_265 - /usr/bin/javac
    Python: 2.7.16 - /usr/bin/python
  npmPackages:
    @react-native-community/cli: Not Found
    react: 16.13.1 => 16.13.1 
    react-native: 0.63.3 => 0.63.3 
    react-native-macos: Not Found
  npmGlobalPackages:
    *react-native*: Not Found
- **Platform that you're experiencing the issue on**: - [ ] iOS - [ ] Android - [x] **iOS** but have not tested behavior on Android - [ ] **Android** but have not tested behavior on iOS - [ ] Both - **`react-native-firebase` version you're using that has this issue:** - `8.4.5` - **`Firebase` module(s) you're using that has the issue:** - `Messaging` - **Are you using `TypeScript`?** - `Yes` & `4.0.3`




Needs Triage Bug Needs Repro

Most helpful comment

also if you send mixed notifications + data messages the SDK will handle them because of the notification payload, so it will likely go to the ios notification center and you will get no control until user taps

for this reason I do data-only, it cleans up the matrix of payload types / ways things are handled

Hi Mike,
I could solve the problem, my error was not provide the content-available property at this format: "content_available" : true, and seems that this property is strictily neccesary to call setBackgroundMessageHandler when i'm sending mixed notifications. It's fine in case when is a clean project.

By other hand, I'm mixing react-native-notification-push library with this library, because I need to show local notifications in foreground, but it seems that in some way that library is causing lose setbackgroundMessageHandler. So I decided to remove it, and modify the RNFB event where foreground event onReceiveMessage is completed, to show a local notification in foreground. Now it works without use any other third-party library :).

Finally, about to call functions or made tasks when App is terminated, is it possible? I was reading about that and I found diverses responses. I want to implement it but i don't know if maybe it's a system limitation.

All 11 comments

I can't reproduce this on android or iOS - I just tested both with current stable versions of everything

android with a data-only push in foreground, background and headless
iOS in foreground and background/quit

all on real devices, everything works

I see that you've got all this registration and permission stuff. I'm aware you need to ask for permission as a hard requirement, but the rest of it should all be default settings, so you should just need to get the token and use it

You may need the results of https://github.com/invertase/react-native-firebase/issues/4299#issuecomment-705128266 to get them on a fresh install (or you'll need to force quit and restart app)

    "@react-native-firebase/analytics": "^7.6.7",
    "@react-native-firebase/app": "^8.4.5",
    "@react-native-firebase/auth": "^9.3.0",
    "@react-native-firebase/crashlytics": "^8.4.9",
    "@react-native-firebase/dynamic-links": "^7.5.9",
    "@react-native-firebase/firestore": "^7.8.6",
    "@react-native-firebase/functions": "^7.4.8",
    "@react-native-firebase/iid": "^7.4.8",
    "@react-native-firebase/in-app-messaging": "^7.5.6",
    "@react-native-firebase/messaging": "^7.9.0",
    "@react-native-firebase/perf": "^7.4.8",
    "@react-native-firebase/remote-config": "^9.0.10",
    "@react-native-firebase/storage": "^7.4.9",

I can't reproduce this on android or iOS - I just tested both with current stable versions of everything

android with a data-only push in foreground, background and headless
iOS in foreground and background/quit

all on real devices, everything works

I see that you've got all this registration and permission stuff. I'm aware you need to ask for permission as a hard requirement, but the rest of it should all be default settings, so you should just need to get the token and use it

You may need the results of #4299 (comment) to get them on a fresh install (or you'll need to force quit and restart app)

    "@react-native-firebase/analytics": "^7.6.7",
    "@react-native-firebase/app": "^8.4.5",
    "@react-native-firebase/auth": "^9.3.0",
    "@react-native-firebase/crashlytics": "^8.4.9",
    "@react-native-firebase/dynamic-links": "^7.5.9",
    "@react-native-firebase/firestore": "^7.8.6",
    "@react-native-firebase/functions": "^7.4.8",
    "@react-native-firebase/iid": "^7.4.8",
    "@react-native-firebase/in-app-messaging": "^7.5.6",
    "@react-native-firebase/messaging": "^7.9.0",
    "@react-native-firebase/perf": "^7.4.8",
    "@react-native-firebase/remote-config": "^9.0.10",
    "@react-native-firebase/storage": "^7.4.9",

I'm testing in a real device like iPhone 5s. I'll get the correct token and I use it with postman. Foreground and background notifications works fine, but.. setBackgroundMessageHandler never is called when app is in background.

this is my json notification:

{  
     "data": { 
        "rp_campaign_id": "36-001",
        "rp_sequential_id": "36-001",
        "rp_notification_id": "20200921045058910"
    },
    "notification": {
        "title": "Notificaci贸n de Prueba",
        "body": "Saludos Cordiales"
    },
    "priority": "high", 
    "to" : "cNG1XorNNEVXla6lyVvvPl:APA91bH6drg8pd05Cksk4CIgb9l2Mv-WEGs1A7-vA2uqa1MzvTvbmzK4T-2FidY6bge-ttlY3wJ2nvqAtmdyPFglAQYHYIK1qg_uHr63ZiLqksS8w7c5TTpr9QKI9KtaWmng9FV6zWZJ"
} 

Edit: Now, I'm testing your additions on my dependencies code.

missing content-available

also if you send mixed notifications + data messages the SDK will handle them because of the notification payload, so it will likely go to the ios notification center and you will get no control until user taps

for this reason I do data-only, it cleans up the matrix of payload types / ways things are handled

also if you send mixed notifications + data messages the SDK will handle them because of the notification payload, so it will likely go to the ios notification center and you will get no control until user taps

for this reason I do data-only, it cleans up the matrix of payload types / ways things are handled

Hi Mike,
I could solve the problem, my error was not provide the content-available property at this format: "content_available" : true, and seems that this property is strictily neccesary to call setBackgroundMessageHandler when i'm sending mixed notifications. It's fine in case when is a clean project.

By other hand, I'm mixing react-native-notification-push library with this library, because I need to show local notifications in foreground, but it seems that in some way that library is causing lose setbackgroundMessageHandler. So I decided to remove it, and modify the RNFB event where foreground event onReceiveMessage is completed, to show a local notification in foreground. Now it works without use any other third-party library :).

Finally, about to call functions or made tasks when App is terminated, is it possible? I was reading about that and I found diverses responses. I want to implement it but i don't know if maybe it's a system limitation.

Update: I was able to receive notifications and make additional functionality, even when app it's closed, at this case I used Notification Service Extension with AppGroups to make call functions and share data between the service and main app.

Hello @game8149, when you mentioned app it's "closed". Do you actually "SWIPE the app away" from app switcher?

Hello @game8149, when you mentioned app it's "closed". Do you actually "SWIPE the app away" from app switcher?

Hi, yes. I can make additional functionality with Notification Service Extension

@game8149 can you describe how you handle the payload with Notification Service Extension + App Group while you app is killed (Swiped away)? Do you still rely on setBackgroundMessageHandler method or what? From what I've understand and tested so far, none of the code is being triggered after app is killed until user launch the app again.

Appreciate and thanks in advance! 馃檹

@game8149 can you describe how you handle the payload with Notification Service Extension + App Group while you app is killed (Swiped away)? Do you still rely on setBackgroundMessageHandler method or what? From what I've understand and tested so far, none of the code is being triggered after app is killed until user launch the app again.

Appreciate and thanks in advance! 馃檹

Well, yeah, but only with native code, not js code. I could send a API call request to update the notification state on my external service. If you need info about implementation, message me.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ODelibalta picture ODelibalta  路  3Comments

joecaraccio picture joecaraccio  路  3Comments

rtman picture rtman  路  3Comments

alizahid picture alizahid  路  3Comments

escobar5 picture escobar5  路  3Comments