How to collapse multiple notifications into a single one using Firebase? - android

I have function in Firebase Cloud Functions which is used to send notifications to specific users within my app and has as the notificationContent the following code:
const notificationContent = {
notification: {
title: "My Notification Title",
body: "My Notification Body",
icon: "default",
sound : "default"
}
};
I have tried to use collapse_key: "unique_key" but it has no effect. I read the has an effect only when the device is offline. I also have used a tag: "unique" but every time a new notification arrives, it will override the oldest one.
I there any way in which I can achieve this with Firebase? If I receive more then one notification, to be grouped in a single one?
Thanks in advance!

If you want to use more customizable and advanced notification features.
You should only send FCM with data payload, and create notification at android client side.
Remember that if you send FCM with notification payload or notification + data payload, the notification will be created by android core system and BroadcastReceiver's onReceive method won't being called if your app is on background.
If you send FCM with data payload, it will call onReceive all the time, so you can produce custom notification manually at android client side. (most app uses latter method.)
I hope this link would be helpful.

I had this same confusion and realized I misunderstood what collapseKey and tag are for.
collapseKey will limit the number of notifications a client receives while they're offline, tag is what will stack notifications together in the drawer.
So for a typical cloud function, it should look like this:
const notification = {
notification: {
'title': 'Interesting title',
'body': 'Hello, world'
},
'data': {
'whatever': whatever,
},
'android':{
'collapseKey': collapseKey,
'priority': 'high',
'notification': {
'tag': tag,
}
},
'token': fcmToken
};
admin.messaging().send(notification)
Note that the "tag" parameter sits inside of the android notification, not the top-level notification.

The easiest and most flexible solution is to extend the FirebaseMessagingService and handle the notification yourself. But first instead of using notification on your notificationContent in your cloud function, you have to change that to data so that you send a data message instead of a notification message. The difference is that the notification message will have an implicit collapse key (the package name of the app), while the data message won't have one. But the data message needs to be handled on the client or else it won't be displayed.
Here's a sample of what you'll need for your FirebaseMessagingService:
public class MyFCMService extends FirebaseMessagingService {
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
if (remoteMessage.getNotification() != null) {
//this notification was sent from the Firebase console, because in our cloud function, we are using the DATA tag, not the notification tag
//so here we have to handle the notification that was sent from the console
...
} else if (remoteMessage.getData().get(KEY) != null) {
//this data message was sent from our cloud function
//KEY is one of the keys that you are using on the cloud function
//in your example, you are using the keys: title, body, icon, sound
//display the notification to the user
...
notificationManager.notify(TAG, ID, notificationBuilder.build());
//you have to use the same TAG and the same ID on each notification if you want your 2nd notification to simply update the text of the first one, instead of showing as a new notification
}
}
}
PS. When you send a notification from your cloud function (well if you use the data tag, it's actually a data message, not a notification message), then this method will be called, regardless if the app is in the background or in the foreground. HOWEVER, when you send a notification from the firebase console, this method will be called ONLY if the app is in the foreground. If the app is in the background, the Firebase SDK will handle the notification and show it to the user. In some cases, it makes sense to show a notification only when the user is not running the app, for example if you want to advertise some new features of the app. In that case, what you can do is use a unique tag on the notification console (e.g. "display_in_foreground") and check that tag on the client. If you have set that to true, you can show the notification even to users that are currently running the app, or if it's false you can choose not to show the notification. This check will happen only if the app is in the foreground. If it's in the background, this won't be called at all and the SDK will handle to show the notification.

Related

Expo: don't show notification if app is open in foreground

I am developing a react-native messaging app with Expo. Every time a user receives a new message, I send a notification from my server.
Is there any way to not display the notification if the app is currently open?
Right now I am using this as soon as the notification is received:
Notifications.dismissNotificationAsync(notification.notificationId);
But there is a 0.5 second delay where the notification has time to appear in the tray and trigger a sound before it gets dismissed. I would like to not show it at all.
When a notification is received while the app is running, using setNotificationHandler you can set a callback that will decide whether the notification should be shown to the user or not.
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
}),
});
When a notification is received, handleNotification is called with the incoming notification as an argument. The function should respond with a behavior object within 3 seconds, otherwise the notification will be discarded. If the notification is handled successfully, handleSuccess is called with the identifier of the notification, otherwise (or on timeout) handleError will be called.
The default behavior when the handler is not set or does not respond in time is not to show the notification.
If you don't use setNotificaitonHandler, the new notifications will not be displayed while the app is in foreground.
So you can simply set setNotificationHandler to null when your app is initialized.
Notifications.setNotificationHandler(null);
See Documentaition
The answer is yes to your question
Is there any way to not display the notification if the app is
currently open?
The default behavior of Notification in Expo is not to show notification if the App is in foreground. You must have implemented Notifications.setNotificationHandler similar to the following code -
// *** DON'T USE THE FOLLOWING CODE IF YOU DON'T WANT NOTIFICATION TO BE DISPLAYED
// WHILE THE APP IS IN FOREGROUND! ***
// --------------------------------------------------
// Sets the handler function responsible for deciding
// what to do with a notification that is received when the app is in foreground
/*
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
*/
If you don't use setNotificaitonHandler, the new notifications will not be displayed while the app is in foreground.
Use below code snippet. It works on press notification.
_handleNotification = async (notification) => {
const {origin} = notification;
if (origin === ‘selected’) {
this.setState({notification: notification});
}
//OR
if (AppState.currentState !== 'active') {
this.setState({notification: notification});
}
}
I assume you setup a simple FCM - Firebase cloud messaging
And use that to push messages to the client?
The official Expo guide has a section for receiving-push-notifications
This is the actual workflow of FCM (weird can be called as a common issue) that it'll handle the notifications by itself when the application is in the foreground.
The solution which i did for my project was to create a custom notification JSON rather than using their default template which won't be parsed by FCM.
{
"hello":" custom key and value",
"message":{
"SampleKey":"Sample data",
"data":{
"SampleKey" : "Sampledata",
"SampleKey2" : "great match!"},
}}
In console you can add your own custom JSON objects, and when you get the notification parse the notification by using these objects, then you will be able to override that issue.
You can also add a channel for the request to categorize your notifications
this.createNotificationListeners = firebase.notifications()
.onNotification((notification) => {
let{ hello,data,message} = notification;
});

Firebase Cloud Messaging onMessageReceived not working with ShortcutBadger

I'm using Firebase Cloud Messaging to send notifications and ShortcutBadger to update the unread messages count on our app badge. I've extended the FirebaseMessagingService class and implemented the override method onMessageReceived, which seems to be working fine since I get notifications when my app is in the background or killed. However, when I add the ShortcutBadger call inside this method, I no longer receive notifications and the ShortcutBadger call does not work.
ShortcutBadger requires a Context and, since FirebaseMessagingService extends Service, it is a context, which is why I simply pass this. I also have ShortcutBadger working in other parts of my code, so I know it works.
Any suggestions would be appreciated.
public class Notifications extends FirebaseMessagingService {
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
ShortcutBadger.applyCount(getApplicationContext(), 1); // <-- now is working
}
}
EDIT: SERVER SIDE CODE
Here's the server-side code that sends the Firebase message.
Notification notification = new Notification.Builder(null)
.title(msg.getTitle())
.badge(msg.getBadge())
.body(msg.getMessage())
.build();
Message message = new Message.Builder()
.timeToLive(timeToLive)
.delayWhileIdle(true)
.notification(notification)
.addData("text", msg.getMessage())
.addData("title", msg.getTitle())
.addData("line1", msg.getSubtitle())
.addData("badge", String.valueOf(msg.getBadge()))
.addData("qummute_notification", "true")
.build();
Result result = send(message, deviceKey, retries);
It's been suggested that I send the message without the Notification payload. Will try that and see what happens.
Have you made sure that you are not calling ShortcutBadger.removeCount(Context context) while testing?
Try using ShortcutBadger.applyCountOrThrow(Context context, int badgeCount) to see if any exceptions are thrown.
The onMessageReceived(RemoteMessage remoteMessage) is called on a background thread. Perhaps the phone/launcher you are testing on is using a badge that cannot be created and shown from a background thread.
You need to handle this situation from server side.Remove Notification from your JSON and Use data.
Remove Notification Hole object,
"notification": {
"title": "My Title if any",
"body": "location",
"sound": "default"
},
From where you are sending the notification you need to change that code,
For Server side code should be like below then your onMessageReceived method call even if app is in the background or killed.
{
"registration_ids": [
"cBbGxlj5JRI:APA91bFRLp3tDoBo_P5lGYGUZ4F6iJ55yBJq4GInL7RVV2asHR5PovqnMX-ekIdl_xOYyRyJmSe3SkxYMa3mzIvqml3MmLYZUh8JWygixIebhzhb_l4xv0Q-v3werKl_UO069G1uOyvq"
],
"data": {
"title": "alt_ctgry - Vehicle Number - extra detail(if any)",
"body": "location"
},
"priority": "high"
}
if you want to create notification then write your own code for notification.
For Notification data access use below code,
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
super.onMessageReceived(remoteMessage);
Log.e("FCM", "Call onMessageReceived\n" + remoteMessage + "");
String title = remoteMessage.getData().get("title");
String body = remoteMessage.getData().get("body");
createNotification(title, body);
}
which seems to be working fine since I get notifications when my app is in the background or killed
No, onMessageReceived is called when your app in foreground but not background IF you have notification payload.
In your server side code you add notification payload, so previously your onMessageReceived also was not called when app in background but notification was shown by OS from your payload.
If you want to handle your push all the time you need to remove notification payload, but remember that it could break pushes on iOS if you have an iOS app.

FCM Data Message should be non-collapsibl by default

Based on the documentation, it is my understanding that there are two types of messages that Firebase can send: Notification and Data. Also, they can be either collapsible or non-collapsible with non-collapsible being the default for data messages. This would mean that each message is delivered to the client app. See below:
However, when I send data messages to my client they are collapsed. For example, I send one and it appears in the notification bar with no problem but if I don't open it and another message comes in, it is replaced by the new message. Here's some of my code.
Data message:
//create the notification payload
let payload = {
data: {
title: 'Title',
context: context,
parent: parent,
body: user + " commented on your contribution.",
sound: 'default'
}
};
//send the notification
return admin.messaging().sendToDevice(userToken, payload).then(ok =>{
console.log('Notification sent to: ' + submitter);
}).catch(error => {
console.log('Could not send notification.');
});
What am I doing wrong? I want each notification to appear and not be replaced.
You are debugging at the wrong end. The actual problem is on the client side where you are posting the notification.
While posting the notification if the notification id is the same Android OS will replace the existing notification.
Look for the notify() in your code. Refer to this doc for more details : https://developer.android.com/reference/android/app/NotificationManager.html#notify(int, android.app.Notification)

Creating a local notification in response to a push notification (from firebase) in cordova/ionic

I'm building an application using Ionic Framework that implements a chat function similar to good-old facebook messenger, in that i want to notify users of a chat message, but if they view it elsewhere, i want to remove the notification from their home screen.
I'm using firebase as a back-end for push notifications (though that could be changed i suppose).
I know that you can't expire a remote notification, but i've been told you can expire + remove a local notification, so my question is - can i reliably receive a remote notification, create a local one, and display that, and then in response to a notification with a scope of 'expire' or 'remove', delete a local notification so that my users don't see a duplication of information?
Most plugins tend to detect the status of the app and add a remote notification to the homescreen with the info you've pushed by default, is there a way to avoid this?
Thanks guys.
EDIT:
- Local notifications: http://ionicframework.com/docs/native/local-notifications/
- Firebase cloud messaging: https://github.com/fechanique/cordova-plugin-fcm
As far as I can tell there're no plugins which accomplish all what you need. However..
can i reliably receive a remote notification, create a local one, and display that, and then in response to a notification with a scope of 'expire' or 'remove', delete a local notification so that my users don't see a duplication of information?
Most plugins tend to detect the status of the app and add a remote notification to the homescreen with the info you've pushed by default, is there a way to avoid this?
Yes, by using silent notifications and building the local notification by yourself.
For a project I'm working in, I modified the plugin cordova-plugin-fcm to add support for (local on demand) notifications dismiss/display, send multiple notifications to the cordova app, and some PRs that are not included yet. Also I build the notification by myself, to have full control of what is displayed. You can take a look at the code to get some ideas.
In brief it works like this:
Firstly, I send a "silent" push to the app, which is not displayed by Android:
{
"content_available": true, // IMPORTANT: For Apple -> content-available: 1, for firebase -> content_available: true
"priority": "high",
"to": "/topics/all", // or to a fcm token
"data"{
"title": "My title", // this implies that you display the notification by yourself
"body": "My body", // this implies that you display the notification by yourself
"type": "NEW_USER_MESSAGE", // only relevant to this project
"userId": "1", // only relevant to this project
"timestamp", "150000000"
}
}
Note: If the payload have the "notification": {} item, Android will display it on the system tray (if the app is in background).
https://firebase.google.com/docs/cloud-messaging/concept-options#notifications_and_data_messages
Secondly, when the push arrives to the app (in onMessageReceived()), I build the local notification, assigning it a TAG and an ID. This is the way you can use to dismiss it later.
For example, you could create a local notification with the TAG "NEW_USER_MESSAGE" and ID 1 (a constant indicating a state of the message, or the user ID for example). Also, Android will replace notifications with the same TAG and ID, so this is another way to automatically replace notifications (for example if you send a generic message, like "New update available").
public static String TYPE_NEW_USER_MESSAGE = "NEW_USER_MESSAGE";
public static String TYPE_USER_LEFT_ROOM = "USER_LEFT_ROOM";
NotificationManager notificationManager =
(NotificationManager) _ctx.getSystemService(Context.NOTIFICATION_SERVICE);
// based in the type of the message you've received, you can stylize the notification
if (type.equals( TYPE_USER_LEFT_ROOM )){
notificationBuilder.setColor(Color.RED);
notificationBuilder.setLights(Color.RED, 1000, 500);
}
else if (type.equals( TYPE_NEW_USER_MESSAGE )){
notificationBuilder.setColor(Color.BLUE);
notificationBuilder.setLights(Color.BLUE, 1000, 1000);
}
Notification n = notificationBuilder.build();
notificationManager.notify(type, userId, n);
One advantage of doing it in this way, is that you have full control of the notification to be displayed, so you can stylize it like you want.
If you want to discard expired messages, you can check out the elapsed time between the sent timestamp and the current timestamp:
java.util.Date now = new java.util.Date();
java.util.Date sent_timestamp = new java.util.Date( Long.valueOf(timestamp.toString()) );
final Long elapsed_time = ((now.getTime() - sent_timestamp.getTime()) / 1000);
Log.d(TAG, "New message. sent " + elapsed_time + "s ago");
Thirdly, when the user clicks on a notification Android will launch your app, and the plugin will send the payload of the push message to the cordova view (onNotificationReceived()).
Once your app is opened and you have received the push message, you can dismiss it adding a new action to the plugin:
onNotificationReceived(data){
if (data.wasTapped === true){
if (data.type === 'NEW_USER_MESSAGE'){
FCMPlugin.dismissNotification(NEW_USER_MESSAGE, 1);
}
}
}
The Android action:
else if (action.equals( ACTION_DISMISS_NOTIFICATION )) {
cordova.getThreadPool().execute(new Runnable() {
public void run() {
try{
Log.d(TAG, "FCMPlugin dismissNotificaton: " + args.getString(0)); //tag
NotificationManager nManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
nManager.cancel(args.getString(0)/*NEW_USER_MESSAGE*/, args.getInt(1) /*1*/);
Log.d(TAG, "FCMPlugin dismissNotificaton() to remove: " + id); //tag
callbackContext.success();
}catch(Exception e){
callbackContext.error(e.getMessage());
}
}
});
https://github.com/TrustedCircles/cordova-plugin-fcm/blob/master/src/android/FCMPlugin.java#L286
And the method exposed to the cordova app:
// dismisses a notification by tag+id
FCMPlugin.prototype.dismissNotification = function( tag, userId, success, error ){
exec(success, error, "FCMPlugin", 'dismissNotification', [tag, userId]);
}
https://github.com/TrustedCircles/cordova-plugin-fcm/blob/master/www/FCMPlugin.js#L65
The only tricky bit with notifications in cordova/ionic is the JS part receiving the notification and triggering the Android code.
I used https://github.com/phonegap/phonegap-plugin-push library and its pretty straight forward.
There is a callback when notifications are received in JS(Cordova/Ionic), use this to render you notifications locally in Android.
P.S: Basel's answer tells you how to clear your notifications, so I decided to leave that bit out.

Unable to handle event in background push notification

I am using cordova plugin add phonegap-plugin-push plugin for push notification
In forground notification works fine.and i can handle event also.
When my app is in background then i got notification as well but on click of push notification my event is not fire.
I am using below code
$cordovaPushV5.initialize(options).then(function() {
// start listening for new notifications
$cordovaPushV5.onNotification();
// start listening for errors
$cordovaPushV5.onError();
// register to get registrationId
if (PNdeviceToken == null) //becuase registration will be done only the very first
{
$cordovaPushV5.register().then(function(registrationId) {
// save `registrationId` somewhere;
window.localStorage.setItem('PNdeviceToken', registrationId);
$rootScope.fcmToken = registrationId;
console.log(registrationId)
alert("first time registered id -- " + registrationId)
})
} else {
$rootScope.fcmToken = PNdeviceToken;
alert("already saved registered id -- " + $rootScope.fcmToken)
}
});
$rootScope.$on('$cordovaPushV5:notificationReceived', function(event, data) {
console.log(event)
console.log(data)
})
When i tap on background push notiction then $cordovaPushV5:notificationReceived event not fire, How can I solve this problem?
How can i handle background push notification event?
I had the Same issue and got it resolved it after 2 days of research.
Handling the notification events is same whether the app is in foreground or background.
We have to set "content-available" : "1" in the data field while pushing notifications. Else it wont call notificationReceived event if app is in background.
Also note this is not possible as of now through Google Firebase Console.
We have to send our custom payload messages (data or notification or both) seperately using any one of the firebase servers.
Detailed info can be found on the plugin's GitHub Docs Page on background notifications.
Quoting from there -
On Android if you want your on('notification') event handler* to be called when your app is in the background it is relatively simple.
First the JSON you send from GCM will need to include "content-available": "1". This will tell the push plugin to call your on('notification') event handler* no matter what other data is in the push notification.
*on('notification') event handler = $cordovaPushV5:notificationReceived event in your case.
See this answer for sending custom payload messages using PHP and NodeJS

Categories

Resources