I'm using:
iOS 14
Firebase
Notifee ("@notifee/react-native": "^0.15.1")
React Native ("react-native": "~0.63.3")
I'm on iOS 14 and I'm testing push notifications via Notifee. It's working just fine, except for the fact that my notification dissapears 1-2 seconds after I receive it. It only happens when I don't have my app opened.
It works just fine when I send notification via Firebase console
index.js
messaging().setBackgroundMessageHandler(onMessageReceived);
async function onMessageReceived(message) {
console.log('message background: ');
console.log(message);
const accessToken = await AsyncStorage.getItem('@passwordAccessToken');
if (null === Firebase.notificationProvider) {
if (Firebase.PUSH_PROVIDER === Firebase.PUSH_PROVIDER_NOTIFEE) {
Firebase.notificationProvider = new NotifeePushProvider();
}
}
//when user logs out he shouldn't receive push, with "onMessage()" method I can unsubscribe
//with setBackgroundMessageHandler() I cannot unsubscribe so I need to make this check
if (null !== accessToken) {
Firebase.notificationProvider.displayNotification(message.data);
}
}
App.js
React.useEffect(() => {
//this is for when app is killed and then opened again, we need to subscribe to push again
const subscribeToPush = async () => {
const accessToken = await AsyncStorage.getItem('@passwordAccessToken');
if (null === FirebaseOnMessagingUnsubscription && null !== accessToken) {
FirebaseOnMessagingUnsubscription = subscribeToPushNotifications();
}
}
subscribeToPush();
}, []);
NotifeePushProvider.js
import notifee, { EventType } from '@notifee/react-native';
import * as RootNavigation from '../RootNavigation.js';
import Trans from '../helpers/Trans.js';
import Request from '../helpers/Request.js';
import { ApiConfig } from '../Configuration.js';
class NotifeePushProvider {
constructor() {
this.setCategories();
this.backgroundEvent();
this.foregroundEvent();
}
backgroundEvent() {
notifee.onBackgroundEvent(async ({ type, detail }) => {
const { notification, pressAction } = detail;
console.log('Log: background push event');
this.handlePressedNotification(type, notification, pressAction, detail);
await notifee.decrementBadgeCount();
});
}
foregroundEvent() {
notifee.onForegroundEvent(({ type, detail }) => {
const { notification, pressAction } = detail;
console.log('Log: foreground push event');
this.handlePressedNotification(type, notification, pressAction, detail);
});
}
handlePressedNotification(eventType, notification, pressAction, detail) {
if (eventType === EventType.PRESS && pressAction.id === 'default') {
RootNavigation.navigate('LatestMessages', { senderId: notification.data.sender_id });
}
if (eventType === EventType.ACTION_PRESS && pressAction.id === 'reply') {
let input = detail.input;
let senderId = notification.data.sender_id;
this.replyBack(input, senderId);
}
}
replyBack(textMessage, senderId) {
const request = new Request();
const response = request.sendRequest(
ApiConfig.routes.messages.create,
'POST',
{
body: textMessage,
sender_id: senderId
}
);
}
displayNotification(data) {
notifee.createChannel({
id: 'custom-sound',
name: 'Channel with custom sound',
sound: this.getSound(data.sound, 'android'),
});
let notifeeData = {
title: data.title,
body: data.body,
data: {
sender_id: ''+data.sender_id
},
android: {
channelId: 'default',
sound: this.getSound(data.sound, 'android'),
pressAction: {
id: 'default',
launchActivity: 'default',
},
actions: [
{
title: 'Reply',
//icon: ('../../assets/icons/reply.png'),
pressAction: {
id: 'reply',
},
//input: true //not working :(
},
],
},
ios: {
foregroundPresentationOptions: {
alert: true,
badge: true,
sound: true,
},
categoryId: 'message',
sound: this.getSound(data.sound, 'ios'),
attachments: []
}
};
if ('' !== data.image) {
notifeeData['android']['largeIcon'] = data.image;
notifeeData['ios']['attachments'] = [];
notifeeData.ios.attachments.push({url: data.image});
}
notifee.setBadgeCount(1).then(() => console.log('Badge count set!'));
const notification = JSON.stringify(notifeeData);
notifee.displayNotification(JSON.parse(notification));
}
getSound(sound, OS) {
// console.log(sound)
if ('default' !== sound) {
if (OS === 'ios') {
return `${sound}.wav`;
}
if (OS === 'android') {
return `${sound}.mp3`;
}
}
return sound;
}
async setCategories() {
await notifee.setNotificationCategories([
{
id: 'message',
actions: [
{
id: 'reply',
title: 'Reply',
input: {
placeholderText: Trans.t('push_notification.push_input_placeholder'),
buttonText: Trans.t('push_notification.push_button'),
},
},
],
},
]);
}
}
export default NotifeePushProvider;
Firebase.js
import React from "react";
import messaging from '@react-native-firebase/messaging';
import DeviceInfo from 'react-native-device-info';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { Platform } from 'react-native';
import Request from '../helpers/Request.js';
import { ApiConfig } from '../Configuration.js';
import NotifeePushProvider from './NotifeePushProvider.js';
class Firebase extends React.Component {
static PUSH_PROVIDER_NOTIFEE = 'notifee';
static PUSH_PROVIDER = Firebase.PUSH_PROVIDER_NOTIFEE;
static notificationProvider = null;
}
//https://rnfirebase.io/messaging/server-integration
function getPushToken() {
// Get the device token
messaging()
.getToken()
.then(token => {
return saveTokenToDatabase(token);
});
// If using other push notification providers (ie Amazon SNS, etc)
// you may need to get the APNs token instead for iOS:
// if(Platform.OS == 'ios') { messaging().getAPNSToken().then(token => { return saveTokenToDatabase(token); }); }
// Listen to whether the token changes
return messaging().onTokenRefresh(token => {
saveTokenToDatabase(token);
});
}
async function saveTokenToDatabase(token) {
const passwordAccessToken = await AsyncStorage.getItem("@passwordAccessToken");
if (null === passwordAccessToken) {
return;
}
const request = new Request();
const response = request.sendRequest(
ApiConfig.routes.pushToken.create,
'POST',
{
device_id: DeviceInfo.getUniqueId(),
platform: Platform.OS,
token: token,
token_type: "fcm"
}
);
response.then((res) => {
console.log('Push token response: ' + res);
})
}
async function requestPushPermission() {
if (Platform.OS !== 'ios') {
return;
}
const authStatus = await messaging().requestPermission();
const enabled =
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
if (enabled) {
console.log('iOS Push Authorization status:', authStatus);
}
return enabled;
}
async function onMessageReceived(message) {
console.log('onMessage: ');
console.log(message);
Firebase.notificationProvider.displayNotification(message.data);
}
function subscribeToPushNotifications() {
if (Platform.OS === 'ios') {
const isEnabled = requestPushPermission();
if (false === isEnabled) {
return;
} else {
getPushToken();
}
} else {
getPushToken();
}
if (null === Firebase.notificationProvider) {
if (Firebase.PUSH_PROVIDER === Firebase.PUSH_PROVIDER_NOTIFEE) {
Firebase.notificationProvider = new NotifeePushProvider();
}
}
return messaging().onMessage(onMessageReceived);
}
export default Firebase;
export { requestPushPermission, getPushToken, onMessageReceived, subscribeToPushNotifications };
I can't figure it out, why is that happening?
I figured it out, it seems that problem was in await notifee.decrementBadgeCount(); inside backgroundEvent(). I removed it from there and now it's working
Interesting! Thanks for reporting back. There is no interaction between those two methods documented, this seems like something we should either put in the docs or investigate to see if it is possible to decrement badge without any side effects. Have you tried decrementBadgeCount before displaying it?
I'm going to reopen this just because I think at minimum it could be a documentation issue
Well the first thing I do is display notification, then somehow it enters notifee.onBackgroundEvent and decrementBadgeCount gets executed
Hi, thanks for the details. I've looked into this, and it seems to happen when the badge count reaches 0, and apple clears the notifications in the tray. We should be able to do something to avoid this default behaviour by setting it to -1 instead of 0. In the meantime, you could check the badge count is greater than 1 before calling decrementBadgeCount.
This is fixed, will be out in the next release.