I am creating a messaging application, and like any mobile messaging service Push Notifications are needed for when the app isn't connected to the backend.
Let me use an example to out line the scenario I'm experiencing.
There is a conversation between User A & User B
// User B's application is idle (not receiving messages from our backend)
// User A sends User B a message
A --> B
Because User B isn't connected, he/she is sent a push notification beckoning him/her to open the app and sync the message.
User B's phone now has one notification on his/her lock screen, like so
Message from User A
then ...
// User A sends User B another message
A --> B
User B's phone now has two separate notifications on his/her lock screen from User A.
These messages read like this:
Message from User A
Message from User A
BUT, I would like the lockscreen to read something like this
Message from User A (2)
I am unsure how to get the notifications to aggregate once they reach the phone, assuming they have metadata attached to them that articulates who the "sender" of the message is.
Currently, this is the payload I am sending up to Urban Airship
function sendPushNotification (event, user) {
if (event.type == 21 || event.type == 22 || event.type == 24) {
var sender = event.sender.username;
var alert = "from #" + sender;
var reciever = user.username;
var payload = {
"audience": {
"alias" : reciever
},
"device_types": [ "ios", "android" ],
"notification": {
"ios": {
"alert": alert,
"badge": "+1",
"sound": "default",
"extra": { "username": sender }
},
"android": {
"alert": alert,
"collapse_key": "inboxappco",
"extra": { "username": sender }
}
}
};
console.log("Hello 2");
pushNotification(payload);
} else {
// modularize for general purpose notifications
}
}; // end sendPushNotification function
Any advice on how I can leverage the the sender metadata, to aggregate consecutive push notifications from the same person into one line item on the lock screen?
Thanks in advance SOF.
It appears that your app will need to create your own custom push notification object and somehow get access to the NotificationManager
PushManager.shared().setNotificationBuilder(new YourCustomNotificationObject());
I'm not sure how Urban Airship exposes the NotificationManager, but you need to use the setGroup("arbitrarygroupname") accessor in your NotificationBuilder
Unless you are targeting minimum API level 20, then this is accessor is not available, so you have to use the v4+ NotificationCompat.Builder object, and make sure your support libraries are version 20 or higher.
EDIT, as of UrbanAirship 4.0.3 for Android this is not possible, server side you can keep track of a series of push notifications and use the collapse_key parameters with each push, collapse_key will replace push notifications of the same type, so your SERVER will have to send push notifications with different names, with the name being Message from User A (2) instead of letting the Android system handle that client side
Related
I have a custom notification channel that never gets triggered. I read all the documentation for server and client but I'm still missing something.
What I want
I want to send a high-priority push notification via FCM to eventually wake up a foreground service in the app. Therefore I defined a custom notification channel. I want to receive the push, find out that it was sent to the custom channel, and show a notification.
My Problem
I receive all push notifications from firebase as expected but the channelId is always null.
The Setup
We use FCM directly on both sides, the server and the android app.
Our users use Android 8.1 and higher. No legacy push is needed.
The backend sends a JSON that looks like the following:
{
"notification": {
"android_channel_id": "MY_High_Prio_Push_Channel"
},
"data": {
"notificationBody": "BodyText",
"notificationTitle": "Title"
},
"priority": "high"
}
The App has registered the push channel successfully. I can see it in the device settings. The push channel will be re-registered on any app start like this
private fun registerHighPrioPushToRestartTheForegroundService() {
val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(getString(R.string.push_channel_id_high_prio), getString(R.string.push_channgel_sync_with_high_prio), NotificationManager.IMPORTANCE_HIGH)
channel.setShowBadge(true)
notificationManager.createNotificationChannel(channel)
}
At Runtime
If the android device receives a push from FCM onNewMessageReceived() is called. I then check what channelId it was sent to by calling:
if (remoteMessage.notification?.channelId == getString(R.string.push_channel_id_high_prio)) {
// I would love to do my channel specific stuff here
}
As you can see in the screenshot below the body contains the full JSON. All other values are null.
The problem was on the server side. The whole JSON for the push was put in the body tag of the notification. Now that this is fixed we send a JSON like the following and the parsing in the client works as expected. We removed the notification tag so that onMessageReceived() will be called on every single push.
{
"to": "{devicePushToken}",
"android": {
"priority": "high"
},
"priority": 10,
"data": {
"title": "Some Title",
"body": "Some Message",
"android_channel_id": "yourUniquePushId"
}
}
I use Firebase Cloud Messaging (FCM) HTTP Legacy API protocol to send push notifications in JSON to android mobile devices. For the client side I use react-native-fcm library.
The aim is to send the notification to the particular devices when the application is in 3 states:
1) running
2) background running
3) killed
According to the documentation for FCM there are 3 different types of messages which can be sent via FCM service:
1) notification (has predefined fields)
2) data (set whatever fields you want)
3) mixed (notification + data).
The logic of listening the event for incoming message on the client side using react-native-fcm is the next:
this.notificationEmitterSubscription = FCM.on(FCMEvent.Notification, notif => {
if(notif && notif.fcm){
//received from Firebase
if(!notif.local_notification && notif.title){
let badge = parseInt(notif.badge);
FCM.setBadgeNumber(badge);
this.showNotification(notif.title, notif.body, badge);
}
//notification is clicked
if(notif.opened_from_tray){
FCM.setBadgeNumber(0);
this.executeNavigateAction(notif.fcm.action); //this method just navigates user to a particular screen in the application
}
}
});
Show notification method is implemented in this way:
showNotification(title, body, badge) {
FCM.presentLocalNotification({
body: body,
priority: "high",
title: title,
sound: "default",
large_icon: "ic_launcher",// Android only
icon: "ic_launcher",
show_in_foreground :true, /* notification when app is in foreground (local & remote)*/
vibrate: 300, /* Android only default: 300, no vibration if you pass null*/
lights: true, // Android only, LED blinking (default false)
badge: badge,
local: true,
click_action: NAV_SCREEN_NAME
});
}
notif.title, notif.body and notif.badge are the fields which are set in data section of the message when sending it via FCM API. In other word the message is sent in the (3) mixed form:
{
"registration_ids" : ["FCM_device_token_1", "FCM_device_token_2"],
"notification" :
{
"title" : "fcm notification message title",
"body" : "fcm notification message body",
"badge" : 111
},
"data" :
{
"title" : "fcm data message title",
"body" : "fcm data message body",
"badge" : 222
}
}
If the message is sent as (1) notification (without "data" section in the message, in this case some changes in the reading the fields are necessary, to change notif.title -> notif.fcm.title, but this is not the main point in the question) or mixed (3) then the listener for the notification is NOT triggered when application is (2) background running and (3) killed. As a result, the badge number is not set. BUT despite the fact that the method showNotification(title, body, badge) is not called (because the event listener is not triggered) the message IS shown. It seems that react-native-fcm has internal implementation for this situation to show (1) notification and (3) mixed messages automatically when application is not running. In other words, the listener IS called for (1) notification and (3) mixed messages only when the application is (1) running and IS NOT called when the application is in the (2) background or (3) killed and does NOT show the badge number. However, the message itself IS shown for all situations.
Another approach is to send a (2) data message. This type of FCM message triggers the listener (notificationEmitterSubscription) for all states of the application: (1) running and (2) background running and (3) killed. As a result, badge number is set in all these states. However, despite the fact that method showNotification(title, body, badge) is called whenever a data FCM message is received, method FCM.presentLocalNotification does NOT display the message if the application is killed.
Thus, in few words, I have a question.
How to:
EITHER display a badge number when (1) notification or (3) mixed message is received and the application is in (2) background running or (3) killed
OR display a (2) data message when the application is (3) killed?
Thank you!
The solution has been found. The statement is that: there is no code running if the application is killed, so the messages is handled and displayed out of your code. The message has to be set in the next format to be shown for the killed status:
{
"registration_ids" : ["FCM_token_1", "FCM_token_2"],
"data" :
{
"custom_notification" :
{
"title" : "FCM test title",
"body" : "FCM test body"
},
badge : 1
}
}
In your react-native application in the notification handler the notification is received as a json value of notif.custom_notification property. So, the code looks like this:
this.notificationEmitterSubscription = FCM.on(FCMEvent.Notification, notif => {
if(notif && notif.fcm){
//received from Firebase
if(!notif.local_notification && notif.custom_notification){
let message = JSON.parse(notif.custom_notification);
let body = message.body;
let title = message.title;
let badge = parseInt(notif.badge);
FCM.setBadgeNumber(badge);
this.showNotification(title, body, badge);
}
//notification is clicked
if(notif.opened_from_tray){
FCM.setBadgeNumber(0);
this.executeNavigateAction(notif.fcm.action);
}
}
});
The issue can be solved as a resolved one.
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.
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.
We are using firebase push notification in Android and in iOS. The push is sent using FCM REST API call. Push type is notification with extra data node.
Here is a sample payload:
{
"notification" : {
"title": "title text",
"body": "message body text",
"sound": "default"
},
"data": {
"messageType": "xxx"
},
"to": "yyy",
"priority": "high",
"time_to_live": 0
}
This type of push notification does not show a heads up display when the app is in background and phone is on. -- Notification just are added to notifications bar but are not sneak peaked to user at the top of the screen. -- no matter if the current app is full screen app or not.
One solution that I have tried and is working is to shift to pure data message where we will not send any notification node, but just the data node and write the code to show notification ourselves and set notification priority to Max (ie .setPriority(Notification.PRIORITY_MAX)) on notification builder object.
But this seems to have issues on iOS where data only pushes are not received/shown to user if the app killed by user.
So is there any workaround to this? any solution that works on Android, but also does not break iOS.
You should set the NotificationPriority to PRIORITY_HIGH or PRIORITY_MAX for Android to show the notification. See Android Notification and Notification Priority for more details.
You should set these values using the constants, not using the strings. E.g.
{
token,
android: {
notification:
{
title,
body,
// See https://developer.android.com/reference/android/app/Notification#PRIORITY_MAX.
// This is deprecated in Android API level 26, but works for older versions.
notification_priority: 2,
// Always allow Android users to see the message in its entirety.
// See https://developer.android.com/reference/android/app/Notification#VISIBILITY_PUBLIC
visibility: 1
}
}
}