Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

messaging.setBackgroundMessageHandler: How to invoke when app is in quit state on iOS #5656

Closed
waqas-syed opened this issue Aug 27, 2021 · 52 comments · Fixed by #6259
Closed
Labels
Help: Good First Issue Issues that are non-critical issues & well defined for first time contributors. Type: Docs

Comments

@waqas-syed
Copy link
Contributor

waqas-syed commented Aug 27, 2021

Documentation Feedback

This is a solution for invoking messaging().setBackgroundMessageHandler() when the app is in quit state on iOS.

In the past couple of days, I have gone through a ton of issues on the same topic: #3530, #5057, #4104, #3367 just to name a few.

I was able to receive notifications in the foreground, background and also when the app was in quit state. Handlers worked in every scenario too, except for one.

My only problem was: setBackgroundMessageHandler not being invoked when the app was in quit state, even though I was receiving the notification. I also tried to send data-only messages but the result was the same. Even the 8 second delay wasn't working for me.
I was even able to invoke the app in a headless state using content-available: 1 in the message's apns section.

Then I realised that the setBackgroundMessageHandler was invoked if I mount the App component instead of returning null when the app was invoked in the headless state. So I started digging what exactly was required in the App component and to my surprise, it only required a component to be mounted, even when the component contained nothing and rendered nothing.

So to sum up what I did:

Index.js

messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('HEADLESS BACKGROUND: ' + JSON.stringify(remoteMessage));
});

function HeadlessCheck({ isHeadless }) {
  if (isHeadless) {
    console.log("Headless");
    return <AppFake/>;  {/* Notice this component, it is not the App Component but a different one*/}
  }

  return <App />;
}

AppRegistry.registerComponent(appName, () => HeadlessCheck);

The AppFake component simply contains this:

import {useEffect} from "react";
import SplashScreen from "react-native-splash-screen";

const AppFake = () => {
    useEffect(() => {
        SplashScreen.hide();
    }, []);

    return null;
}

export default AppFake;

It's doing nothing, apart from hiding the SplashScreen that I am using, I found that this was essential in my case.

This is the message payload:

const message = {
        notification: {
            title: title,
            body: body
        },
        data: { campaignId: campaignId, title: title, body: body },
        token: registrationId,
        android: {
            priority: "high",
            notification: {
                title: title,
                body: body,
                sound: "default"
            }
        },
        apns: {
            payload: {
                aps: {
                    sound: "default",
                    "content-available": 1
                }
            },
            headers: {
                "apns-priority": "5"
            }
        }
    }

let response = admin.messaging().send(message)
        .then((res) => {
            console.log(res);
        })
        .catch((err) => {
            console.log(err);
        });

This worked in my case, although I am not sure whether this will work for everyone. Just sharing it so anyone else can test and see if it gets their background handler working as well.

I have tested this on version 12.7.3, but the scenario was the same on version 10.8.1, so it should probably work on that too.


@waqas-syed waqas-syed added Help: Good First Issue Issues that are non-critical issues & well defined for first time contributors. Type: Docs labels Aug 27, 2021
@waqas-syed waqas-syed changed the title messaging.setBackgroundMessageHandler: How to invoke when app is in quit state messaging.setBackgroundMessageHandler: How to invoke when app is in quit state on iOS Aug 27, 2021
@mikehardy
Copy link
Collaborator

I love it, with the exception of react-native-splash-screen there - save yourself future pain crazycodeboy/react-native-splash-screen#289 (comment), use https://github.com/zoontek/react-native-bootsplash :-)

If you want to hit the edit button on the tips-n-tricks page to share this more permanently, I think it would be a good add

@waqas-syed
Copy link
Contributor Author

@mikehardy To make sure we are on the same page, you are talking about this tips page on the documentation site where I can submit an edit?

@mikehardy
Copy link
Collaborator

@waqas-syed yes!

@Aexonic-Abhishek
Copy link

This is awesome. Solved my issue facing for almost 3 days now. Thanks! :)

@saahiil-pny
Copy link

Hi @waqas-syed , I tried the method and same configuration by sending notifications through firebase admin SDK. I got it working in foreground and background, but I had no success getting it working when the app is quit state. I just get the remote notification and not any data when in quit state.

@saahiil-pny
Copy link

This is awesome. Solved my issue facing for almost 3 days now. Thanks! :)

Hey @Aexonic-Abhishek , are you getting the data when the app is in quit state, I tried firing up a local notification but it didn't work out for me well. Can you please share what approach has worked for you?

@Aexonic-Abhishek
Copy link

Aexonic-Abhishek commented Oct 19, 2021

@saahiil-pny
Yes, I'm getting the data. Below is the code:

  1. Index.js
    let HeadlessCheck = ({ isHeadless }) => {
    if (isHeadless) {
    // App has been launched in the background by iOS, ignore
    return <AppFake/>;
    }
    return <App/>;
    }

messaging().setBackgroundMessageHandler(async remoteMessage => {
console.log("inn setBackgroundMessageHandler", remoteMessage)
PermissionService.showLocalNotification(remoteMessage)
});

AppRegistry.registerComponent(appName, () => HeadlessCheck);

  1. AppFake.js
    const AppFake = () => {
    return null;
    }

export default AppFake;

Note: I have created a new file(AppFake.js) for returning null. It wasnt working for me when I was directly returning the null in index.js file.

@saahiil-pny
Copy link

@saahiil-pny Yes, I'm getting the data. Below is the code:

  1. Index.js
    let HeadlessCheck = ({ isHeadless }) => {
    if (isHeadless) {
    // App has been launched in the background by iOS, ignore
    return <AppFake/>;
    }
    return <App/>;
    }

messaging().setBackgroundMessageHandler(async remoteMessage => { console.log("inn setBackgroundMessageHandler", remoteMessage) PermissionService.showLocalNotification(remoteMessage) });

AppRegistry.registerComponent(appName, () => HeadlessCheck);

  1. AppFake.js
    const AppFake = () => {
    return null;
    }

export default AppFake;

Note: I have created a new file(AppFake.js) for returning null. It wasnt working for me when I was directly returning the null in index.js file.

This is specifically for iOS. This doesn't work when app is in quit/killed state. Can you please confirm that this works when app is quit/killed state?

@waqas-syed
Copy link
Contributor Author

@saahiil-pny Yes, this solution works specifically for when app is in quit/killed state in iOS.

@jko12130
Copy link

Just wanted to drop by and say this suggestion is absolutely awesome. Spent last couple of days on this and was scratching my head, but this works perfectly.

@barnamumtyan
Copy link

barnamumtyan commented Nov 29, 2021

I also have a similar problem in ios
using @react-native-firebase/messaging@12.9.0

here are some terms I would like to define

  • foreground state
    • same as in rnfirebase.io documentation
  • background state
    • same as in rnfirebase.io documentation
  • quit state 1
    • the app was killed in the app switcher
  • quit state 2
    • the app was killed in the app switcher
    • and after that another app was used
    • this is not the same as quit state 1
    • what works in quit state 1 does not work in quit state 2

The was I test if the app calls setBackgroundMessageHandler is that I append the notification in an array in async-storage

using

const message = {
notification: {...},
data: {...},
token: registrationId,
android: {
priority: "high",
notification: {
title: title,
body: body,
sound: "default"
}
},
apns: {
payload: {
aps: {
sound: "default",
"content-available": 1
}
},
headers: {
"apns-priority": "5"
}
}
}

does not work for quit state

however, this works

const message = {
notification: {
title: title,
body: body
},
data: { campaignId: campaignId, title: title, body: body },
token: registrationId,
"content_available": true,
"priority": "high"
}

note:

  • I am sending this message as normal json to https://fcm.googleapis.com/fcm/send with postman
  • I am not using the firebase admin package
  • "contentAvailable" as it is mentioned in the rnfirebase.io docs in camelCase does not work in quit state 1
  • "content_available" in snake case does work in quit state 1, meaning the notification appears in notification center, and it also correctly calls setBackgroundMessageHandler

however, the problem I have is that
what works in quit state 1 does not work in quit state 2, meaning

  • I kill the app in the app switcher
  • I send a notification "notification1"
  • it appears in notification center, and it also calls setBackgroundMessageHandler
  • with the app not in app switcher, I use another app for about a minute
  • I send another notification "notification2"
  • the notification appears in notification center
  • I send another 2 notifications "notification3" "notification4"
  • they both appear in the notification center
  • I open the app normally
  • the app wakes up and calls setBackgroundMessageHandler with only "notification4"
  • in the app I only see "notification1" and "notification4"
  • "notification2" "notification4" got lost somewhere

@mikehardy
Copy link
Collaborator

Hmm - you should definitely carefully watch the logs from the phone to see what they say about message receipt and delivery, iOS logs are pretty noisy when watched from Console.app (or Xcode, a bit less noisy) but they may have some clues.

@NayChiLynn
Copy link

NayChiLynn commented Dec 8, 2021

@barnamumtyan I'm also facing the same issue like you.
For me, with this message payload doesn't work as well.

const message = {
notification: {
title: title,
body: body
},
data: { .... },
token: registrationId,
"content_available": true,
"priority": "high"
}

I got this error in firebase function logs :
Error sending message: FirebaseMessagingError: Invalid JSON payload received. Unknown name "content_available" at 'message': Cannot find field.
Invalid JSON payload received. Unknown name "priority" at 'message': Cannot find field.

Could you advice plz?

@patels9-vf
Copy link

patels9-vf commented Dec 9, 2021

@waqas-syed @saahiil-pny @mikehardy any thoughts? This is following the documentation and suggestions above. Messages are sent through FCM console and payloads are not modified.

I'm getting a similar issue to the above, however, it is not triggering the setBackgroundMessageHandler() method when receiving a notification in background state.

I can receive notifications fine, it is just that the handler is not called when trying to add headless for iOS in background/quit state. I've tried to follow the approach suggested in the documentation above, however, it hasn't worked. Are there any suggestions of triggering the messaging().setBackgroundMessageHandler function like previously?

messaging().setBackgroundMessageHandler(async (remoteMessage) => {
console.log('Message handled in the background', remoteMessage);
});

function getIfHeadless() {

if (Platform.OS === 'android') {
return <App />
}
messaging()
.getIsHeadless()
.then((isHeadless) => {
if (isHeadless) {
return <AppFake />;
}
return <App />;
});
return <App />;
}

AppRegistry.registerComponent(appName, () => getIfHeadless);

const AppFake = (): null => {
return null;
};

export default AppFake;

@Wimmind
Copy link

Wimmind commented Dec 9, 2021

A similar problem, I registered everything in the same way as described above, but nothing works, help

const Wrapper = ({ isHeadless }) => {
  if (isHeadless) {
    console.log("Headless");
    return <AppFake />;
  }

  return (<App />)
};
AppRegistry.registerComponent(appName, () => Wrapper);

AppFake:
const AppFake = () => {
  return null;
}
export default AppFake;

AppDelegate:

#import <Firebase.h>
#import "RNFBMessagingModule.h"
...
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  [FIRApp configure];
  NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
#ifdef FB_SONARKIT_ENABLED
  InitializeFlipper(application);
#endif

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"project"
                                            initialProperties:appProperties];
...
}

@Wimmind
Copy link

Wimmind commented Dec 9, 2021

mikehardy

what could be the problem?

@barnamumtyan
Copy link

@NayChiLynn
How are you sending that message?

are you sending the request yourself using fetch, or using a rest cilent like postman?
1.
fetch(https://fcm.googleapis.com/fcm/send, { method: 'post', headers: yourHeaders, body: message });

OR

are you using the firebase library (on your server) like in https://rnfirebase.io/messaging/usage#main?
2.
admin.messaging().sendToDevice([token], message) // or admin.messaging().send(message)

In case 1 as I mentioned in my earlier post, I think this is the most straightforward way
use "content_available": true

In case 2, using the firebase library on the server, which is very confusing
in the documentation https://rnfirebase.io/messaging/usage#main
you will find content-available in the text
but contentAvailable in the code
I am not sure how it should be used with the firebase server side library.

@mikehardy
Copy link
Collaborator

@Wimmind based on your other post, you are sending incorrect FCM JSON and I would not expect anything to work.

@patels9-vf
Copy link

Hey @mikehardy any suggestions on this? Just can't seem to understand what the issue is

This is following the documentation and suggestions above. Messages are sent through FCM console and payloads are not modified.

I'm getting a similar issue to the above, however, it is not triggering the setBackgroundMessageHandler() method when receiving a notification in background state.

I can receive notifications fine, it is just that the handler is not called when iOS is in background/quit state. I've tried to follow the approach suggested in the documentation above, however, it hasn't worked. Are there any suggestions of triggering the messaging().setBackgroundMessageHandler function like previously?

messaging().setBackgroundMessageHandler(async (remoteMessage) => { console.log('Message handled in the background', remoteMessage); });

function getIfHeadless() {

messaging() .getIsHeadless() .then((isHeadless) => { if (isHeadless) { return <AppFake />; } return <App />; }); return <App />; }

AppRegistry.registerComponent(appName, () => getIfHeadless);

const AppFake = (): null => { return null; };

export default AppFake;

@louisencontre
Copy link

louisencontre commented Dec 15, 2021

Great idea for the solution guys!

I was just wondering: with proposed solution, what happens if the user opens the app after it was awaken with the Fake prop? How would we need to manage this case? If I'm understanding this right, the code inside the isHeadless block will be called and the fake prop will be instantiated.

And finally, I looked at this blog post https://www.fullstacklabs.co/blog/tracking-push-notification-status-react-native and saw that to trigger some code when app is in killed state on iOS, we must use the Notification app extension. Are we sure it's possible to run some code without it on iOS? 🤔

Thanks a lot for your time 🙏

@mikehardy
Copy link
Collaborator

@louisencontre this may be the reason Invertase has invested so much time and effort in creating....a notification package with a notification service extension https://notifee.app/react-native/docs/ios/remote-notification-support :-)

@louisencontre
Copy link

Ah yes thank you @mikehardy 🙏 I got to check out Notifee, it looks really nice 👌
I want to make sure I understand what this thread is saying though. It feels like this thread is telling us that we can, indeed, run some code when the iOS app is killed and receiving a notification. And this without the need to implement the Notification app extension. Is this correct?
Sorry if I am not understanding something basic here...

@mikehardy
Copy link
Collaborator

I speak here only in reference to Fcm json contents, running on ios.

Data block? Background handler might run. Unreliable by design

Mixed data and notification blocks? No handlers called until users interaction on displayed notification, unless you have an extension

Notification block only? Same as above.

@louisencontre
Copy link

Ok thank you very much @mikehardy very clear 👌

@rohail111
Copy link

rohail111 commented Apr 7, 2022

I tried every solution but still i cant trigger the messaging().setBackgroundMessageHandler event all tough the notification is landing on ios device. message().onMessage event is working fine.

Main issue is in the background state.

@mikehardy
Copy link
Collaborator

But you do not specify versions of anything, you do not include the JSON you send via the FCM REST API to do your testing, you do not include the sources of documentation you've consulted already, what you've tried, what your device is for testing, app state and how you achieved it, and you include no evidence from the device indicating what happened, so no help may be offered. https://stackoverflow.com/help/how-to-ask

@rohail111
Copy link

I am using fcm testing tool online not rest api.

@mikehardy
Copy link
Collaborator

@rohail111 then you are not fully in control of your tests since you cannot know the FCM JSON contents. I suggest you rethink your test strategy and use FCM REST API, I wouldn't say that as such a strong suggestion except it's actually really easy to use. Then you have full JSON control. Worth it in my opinion.

@rohail111
Copy link

rohail111 commented Apr 13, 2022

@mikehardy if possible, can you share proper JSON data object by which we can trigger the backgroundMessageHandler event in case of FCM REST api.

@mikehardy
Copy link
Collaborator

@rohail111 there are quite a few examples in the repo

https://github.com/invertase/react-native-firebase/issues?q=is%3Aissue+googleapis+fcm+bash+sort%3Aupdated-desc+mikehardy

Here's one #2997 (comment)

@lucasdallabeneta
Copy link

I was wasting tons of time trying to fix my app, and the solution was so dumb I thought had to share it here: I forgot to enable background app refresh in the iOS settings screen. setBackgroundMessageHandler will not work without this option enabled

@roti-c137
Copy link
Contributor

roti-c137 commented May 18, 2022

@lucasmartinsd same happened to me too. Enabled background app refresh and it works.

On another issue, seems like iOS background app refresh OFF app background, the PN is being routed to onMessage() instead whenever the app is opened.

  1. When server sends a PN & iOS app background,
  2. Device will prompt a PN
  3. On tap app icon (not tapping on notification) Background -> Foreground, onMessage() gets triggered

@roti-c137
Copy link
Contributor

roti-c137 commented May 18, 2022

@mikehardy I didn't find any mention of iOS background app refresh OFF in the document. Would you like me to submit a PR to add that in?

Also just wondering is that scenario being covered anywhere? I found one issue (see my comment above) and wondering is that flow covered / raised by anyone previously.

Edit
My bad, I updated the packages to latest 14.5.0 -> 14.9.4 and it appears to be fine

@mikehardy
Copy link
Collaborator

@roti-c137
Copy link
Contributor

roti-c137 commented May 19, 2022

@roti-c137 I think it is documented here? https://rnfirebase.io/messaging/usage/ios-setup#enable-background-modes

That is enabling background mode in Xcode.

For iOS users, they have the ability to toggle background app refresh in their device’s setting as below. Also it is disabled by default if user is on low power mode.
C590DF44-1972-48B1-BF8C-9447825F4F92

The implication is end user / during actual device testing, user has the ability to switch background mode off. I myself was scratching my head for a few days due to this 😅

@mikehardy
Copy link
Collaborator

Oh yes of course sorry. If you could make a PR to the docs that might help, there's an edit link on each page. I think I use device info to check status in code

@helenaford
Copy link
Member

just faced this, needed the content-available flag, even if background app refresh is on

@rohail111
Copy link

rohail111 commented Jun 9, 2022 via email

@joshke
Copy link

joshke commented Jul 5, 2022

@waqas-syed Thanks for sharing this, I have try to get this working for a full day, after using your code it worked 👍

For anyone else looking for the solution, make sure to modify AppDelegate.m .
Here is my firebase test function, which can run in the emulator

exports.testNotification = functions.https.onRequest(async (data, context) => {
  const message = {
    notification: {
        title: "title",
        body: "body"
    },
    data: { campaignId: "campaignId", title: "title", body: "body" },
    //token: "fcmtoken",
    android: {
        priority: "high",
        notification: {
            title: "title",
            body: "body",
            sound: "default"
        }
    },
    apns: {
        payload: {
            aps: {
                sound: "default",
                "content-available": 1
            }
        },
        headers: {
            "apns-priority": "5"
        }
    }
}

return admin.messaging().send(message)
    .then((res:any) => {
        console.log(res);
    })
    .catch((err:any) => {
        console.log(err);
    });

});

@345ml
Copy link

345ml commented Sep 8, 2022

Thanks a lot!!

SplashScreen.hide();

I don't know why, but for me this was necessary.

Without it, the app would launch silently, but the setBackgroundMessageHandler would not be triggered.

@345ml
Copy link

345ml commented Sep 8, 2022

And I am experiencing another problem in my environment.

Start the app → Close the app.
This is a quit state.
I have confirmed that it works in this state.

But if a few minutes pass after closing the app, it stops working.

Specifically, I am using react-native-callkeep in setBackgroundMessageHandler.

Does anyone else have a similar problem?

@sajumani
Copy link

And I am experiencing another problem in my environment.

Start the app → Close the app. This is a quit state. I have confirmed that it works in this state.

But if a few minutes pass after closing the app, it stops working.

Specifically, I am using react-native-callkeep in setBackgroundMessageHandler.

Does anyone else have a similar problem?

Yes Am also facing issue Specially in IOS

@mikehardy
Copy link
Collaborator

It is expected behavior on iOS that you will not receive data-only FCM in the "killed" state (that is: you have quit the app, and waited some time, so the operating system kills the app - that is what iOS does after "quit")

If you want reliable delivery of FCM on iOS you must include a notification block in your FCM.

@brianomchugh
Copy link

This solution just saved my day (for Android surprisingly enough).

@nhhung4199
Copy link

@lucasmartinsd điều tương tự cũng xảy ra với tôi. Đã bật làm mới ứng dụng nền và nó hoạt động.

Về một vấn đề khác, có vẻ như iOS background app refresh OFF app backgroundPN đang được chuyển đến onMessage()bất cứ khi nào ứng dụng được mở.

  1. Khi máy chủ gửi nền ứng dụng PN & iOS,
  2. Thiết bị sẽ nhắc PN
  3. Khi nhấn vào biểu tượng ứng dụng (không nhấn vào thông báo) Nền -> Tiền cảnh, onMessage() được kích hoạt

I am having the same problem, can you give me the solution?

@Malhar62
Copy link

Malhar62 commented Sep 27, 2023

{ "to": FCM_TOKEN, "notification": { "sound": "default", "content_available": true, "priority": "high" }, "data": { "caller": "demonKing" } }

Try this payload ,worked for me.
this triggers setMessageBackgroundHandler in ios.

@taongocson
Copy link

taongocson commented Nov 16, 2023

Documentation Feedback

This is a solution for invoking messaging().setBackgroundMessageHandler() when the app is in quit state on iOS.

In the past couple of days, I have gone through a ton of issues on the same topic: #3530, #5057, #4104, #3367 just to name a few.

I was able to receive notifications in the foreground, background and also when the app was in quit state. Handlers worked in every scenario too, except for one.

My only problem was: setBackgroundMessageHandler not being invoked when the app was in quit state, even though I was receiving the notification. I also tried to send data-only messages but the result was the same. Even the 8 second delay wasn't working for me. I was even able to invoke the app in a headless state using content-available: 1 in the message's apns section.

Then I realised that the setBackgroundMessageHandler was invoked if I mount the App component instead of returning null when the app was invoked in the headless state. So I started digging what exactly was required in the App component and to my surprise, it only required a component to be mounted, even when the component contained nothing and rendered nothing.

So to sum up what I did:

Index.js

messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('HEADLESS BACKGROUND: ' + JSON.stringify(remoteMessage));
});

function HeadlessCheck({ isHeadless }) {
  if (isHeadless) {
    console.log("Headless");
    return <AppFake/>;  {/* Notice this component, it is not the App Component but a different one*/}
  }

  return <App />;
}

AppRegistry.registerComponent(appName, () => HeadlessCheck);

The AppFake component simply contains this:

import {useEffect} from "react";
import SplashScreen from "react-native-splash-screen";

const AppFake = () => {
    useEffect(() => {
        SplashScreen.hide();
    }, []);

    return null;
}

export default AppFake;

It's doing nothing, apart from hiding the SplashScreen that I am using, I found that this was essential in my case.

This is the message payload:

const message = {
        notification: {
            title: title,
            body: body
        },
        data: { campaignId: campaignId, title: title, body: body },
        token: registrationId,
        android: {
            priority: "high",
            notification: {
                title: title,
                body: body,
                sound: "default"
            }
        },
        apns: {
            payload: {
                aps: {
                    sound: "default",
                    "content-available": 1
                }
            },
            headers: {
                "apns-priority": "5"
            }
        }
    }

let response = admin.messaging().send(message)
        .then((res) => {
            console.log(res);
        })
        .catch((err) => {
            console.log(err);
        });

This worked in my case, although I am not sure whether this will work for everyone. Just sharing it so anyone else can test and see if it gets their background handler working as well.

I have tested this on version 12.7.3, but the scenario was the same on version 10.8.1, so it should probably work on that too.

I followed what you said above but Platform IOS isHeadless is always equal to undefined
System:
OS: macOS 13.0
CPU: (8) x64 Apple M1
Memory: 36.63 MB / 8.00 GB
Shell: 5.8.1 - /bin/zsh
Binaries:
Node: 14.18.1 - /usr/local/bin/node
Yarn: 1.22.19 - /usr/local/bin/yarn
npm: 6.14.15 - /usr/local/bin/npm
Watchman: Not Found
Managers:
CocoaPods: 1.12.1 - /usr/local/bin/pod
SDKs:
iOS SDK:
Platforms: DriverKit 22.4, iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4
Android SDK: Not Found
IDEs:
Android Studio: 2021.3 AI-213.7172.25.2113.9123335
Xcode: 14.3.1/14E300c - /usr/bin/xcodebuild
Languages:
Java: 17.0.9 - /usr/bin/javac
npmPackages:
@react-native-community/cli: Not Found
react: 17.0.2 => 17.0.2
react-native: 0.66.3 => 0.66.3
react-native-macos: Not Found
npmGlobalPackages:
react-native: Not Found

@santaclose666
Copy link

Documentation Feedback

This is a solution for invoking messaging().setBackgroundMessageHandler() when the app is in quit state on iOS.
In the past couple of days, I have gone through a ton of issues on the same topic: #3530, #5057, #4104, #3367 just to name a few.
I was able to receive notifications in the foreground, background and also when the app was in quit state. Handlers worked in every scenario too, except for one.
My only problem was: setBackgroundMessageHandler not being invoked when the app was in quit state, even though I was receiving the notification. I also tried to send data-only messages but the result was the same. Even the 8 second delay wasn't working for me. I was even able to invoke the app in a headless state using content-available: 1 in the message's apns section.
Then I realised that the setBackgroundMessageHandler was invoked if I mount the App component instead of returning null when the app was invoked in the headless state. So I started digging what exactly was required in the App component and to my surprise, it only required a component to be mounted, even when the component contained nothing and rendered nothing.
So to sum up what I did:
Index.js

messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('HEADLESS BACKGROUND: ' + JSON.stringify(remoteMessage));
});

function HeadlessCheck({ isHeadless }) {
  if (isHeadless) {
    console.log("Headless");
    return <AppFake/>;  {/* Notice this component, it is not the App Component but a different one*/}
  }

  return <App />;
}

AppRegistry.registerComponent(appName, () => HeadlessCheck);

The AppFake component simply contains this:

import {useEffect} from "react";
import SplashScreen from "react-native-splash-screen";

const AppFake = () => {
    useEffect(() => {
        SplashScreen.hide();
    }, []);

    return null;
}

export default AppFake;

It's doing nothing, apart from hiding the SplashScreen that I am using, I found that this was essential in my case.
This is the message payload:

const message = {
        notification: {
            title: title,
            body: body
        },
        data: { campaignId: campaignId, title: title, body: body },
        token: registrationId,
        android: {
            priority: "high",
            notification: {
                title: title,
                body: body,
                sound: "default"
            }
        },
        apns: {
            payload: {
                aps: {
                    sound: "default",
                    "content-available": 1
                }
            },
            headers: {
                "apns-priority": "5"
            }
        }
    }

let response = admin.messaging().send(message)
        .then((res) => {
            console.log(res);
        })
        .catch((err) => {
            console.log(err);
        });

This worked in my case, although I am not sure whether this will work for everyone. Just sharing it so anyone else can test and see if it gets their background handler working as well.
I have tested this on version 12.7.3, but the scenario was the same on version 10.8.1, so it should probably work on that too.

I followed what you said above but Platform IOS isHeadless is always equal to undefined System: OS: macOS 13.0 CPU: (8) x64 Apple M1 Memory: 36.63 MB / 8.00 GB Shell: 5.8.1 - /bin/zsh Binaries: Node: 14.18.1 - /usr/local/bin/node Yarn: 1.22.19 - /usr/local/bin/yarn npm: 6.14.15 - /usr/local/bin/npm Watchman: Not Found Managers: CocoaPods: 1.12.1 - /usr/local/bin/pod SDKs: iOS SDK: Platforms: DriverKit 22.4, iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4 Android SDK: Not Found IDEs: Android Studio: 2021.3 AI-213.7172.25.2113.9123335 Xcode: 14.3.1/14E300c - /usr/bin/xcodebuild Languages: Java: 17.0.9 - /usr/bin/javac npmPackages: @react-native-community/cli: Not Found react: 17.0.2 => 17.0.2 react-native: 0.66.3 => 0.66.3 react-native-macos: Not Found npmGlobalPackages: react-native: Not Found

u got setBackgroundMessageHandler error also?

@taongocson
Copy link

taongocson commented Nov 16, 2023

@santaclose666
doc: https://rnfirebase.io/messaging/usage#background-application-state
note: available with react-native >= 0.71.1, older versions need a more difficult style, upgrading is recommended

@santaclose666
Copy link

@santaclose666 doc: https://rnfirebase.io/messaging/usage#background-application-state

i'm already follow all fcm doc and using more notifee/react-native. notification in background and quit mode is still popup but console.log() in setBackgroundMessageHandler or any method handler background or quit mode is not trigger

current version react-native: 0.72.3

my code here:
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import notifee, {EventType} from '@notifee/react-native';
import messaging from '@react-native-firebase/messaging';
import {incrementBagde} from './src/utils/firebaseNotifee';

messaging().setBackgroundMessageHandler(async remoteMessage => {
await notifee.displayNotification(remoteMessage);
incrementBagde();
console.log(remoteMessage);
});

notifee.onBackgroundEvent(async ({type, detail}) => {
const {notification, pressAction} = detail;

console.log('background app', notification);

if (type === EventType.ACTION_PRESS && pressAction.id === 'mark-as-read') {
await notifee.decrementBadgeCount();
await notifee.cancelNotification(notification.id);
}
});

function HeadlessCheck({isHeadless}) {
if (isHeadless) {
return null;
}

return ;
}

AppRegistry.registerComponent(appName, () => HeadlessCheck);

@kiwavi
Copy link

kiwavi commented Dec 3, 2023

"content_available": true, "priority": "high"

This worked for me too! Background refresh was ON so I was wondering what else could be wrong. Also worth noting is that I had added the properties before, but not inside the notification object.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Help: Good First Issue Issues that are non-critical issues & well defined for first time contributors. Type: Docs
Projects
None yet
Development

Successfully merging a pull request may close this issue.