Firebase FCM messages support tag for Android which causes a new notification to replace the previous one with the older one with the same tag. Is there any way to do the same for ios?
This answer suggest using thread-id in the data payload. But it didn't work for me.
There is no thread-id FCM payload key in Legacy FCM HTTP Server protocol. The workaround is to use iOS Notification Service Extension. Send the grouping identifier as "thread-id" key in FCM data payload and let the service extension read the "thread-id" key and set it to the notification content's threadIdentifier property.
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
let userInfo: [AnyHashable : Any] = (bestAttemptContent?.userInfo)!
if let apsInfo = userInfo["aps"] as? [AnyHashable: Any], let bestAttemptContent = bestAttemptContent {
//add thread-id to group notification.
let patientId = userInfo["thread-id"] as! String
bestAttemptContent.threadIdentifier = patientId
contentHandler(bestAttemptContent)
}
}
}
As I mentioned in my answer that you linked:
However, there is currently no parameter counterpart for thread-id in FCM.
The workaround I mentioned is putting the thread-id in the data payload as a custom key value pair wasn't tested (I haven't dabbled with iOS that much so it was a long shot). But logically, if you're able to get the value of the data payload, you should be able to use the value as needed.
Alas, there still isn't any native way to set the thread-id parameter in the iOS payload. The only supported parameters are what is mentioned in the FCM docs.
Yes, you can set thread-id with FCM, but it serves to group notifications.
What you actually need to replace old notifications by new ones is apns-collapse-id
Use the same thread-id to visually group multiple notifications together.
Multiple notifications with the same thread-id are grouped and appear as a stack. Tapping on the stack expands all notifications with the most recent one being on top.
There's a collapseKey option which can be used to skip sending outdated notifications. If the device didn't receive a bunch of notifications and there's a newer one with the same key - only the latest will be pushed.
It seems this would also work to replace old notifications on Android, but not on iOS.
As per delivery options there's an apple specific key: apns-collapse-id
What's a collapsible message
A collapsible message is a message that may be replaced by a new message if it has yet to be delivered to the device.
Quoted from: Non-collapsible and collapsible messages
Setting apns-collapse-id
apns-collapse-id can be set using the lower level messaging APIs - send, sendAll and sendMulticast, where you can specify an apns key
messaging methods reference
low level payload reference
It's set in the apns.headers
const payload = {
tokens: [/*...*/],
data: {/*...*/},
notification: {
title: 'My Title',
body: 'My Content',
},
apns: {
headers: {
'apns-collapse-id': doc.id,
},
payload: {
aps: {
sound: 'default',
},
},
},
};
admin.messaging().sendMulticast(payload);
Setting thread-id
threadId can be set using the lower level messaging APIs - send, sendAll and sendMulticast, where you can specify an apns key
messaging methods reference
low level payload reference
It's set in the apns.payload.aps:
const payload = {
tokens: [/*...*/],
data: {/*...*/},
notification: {
title: 'My Title',
body: 'My Content',
},
apns: {
payload: {
aps: {
threadId: doc.id,
sound: 'default',
},
},
},
};
admin.messaging().sendMulticast(payload);
threadId behavior while offline
For me there are 2 cases:
Sending a few notifications results in the latest being received when the device regains connectivity
Be offline with existing (non dismissed) notification(s) for a given threadId. Regaining connectivity would add the latest notification to the top of the stack
Setting collapseKey
Higher level messaging APIs like sendToDeivce cannot tweak apns directly but can specify options
One of those options is options.collapseKey
const tokens = [/*...*/];
const payload = {
data: {/*...*/},
notification: {
title: 'My Title',
body: 'My Content',
sound: 'default',
},
};
const options = {
collapseKey: doc.id,
}
const response = await admin.messaging().sendToDevice(
tokens,
payload,
options,
);
FCM's HTTP v1 API should support thread-id. Check out the APNS field in https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages
Related
I am subscribing to a topic and pushing notifications using Firebase console/cloud messaging; On Android, everything works fine. On iOS, I receive notifications in the notification centre while the app is in foreground/background/terminated(profile mode)
PROBLEM : No badge appears on the iOS app icon.
I have been checking different articles and QAs but still no solution. Any clue or idea that can help me to do more investigation might solve it is appreciated.
Have tried > Running on both debug and profile mode, using iPhone 12 pro with iOS 15 & iPhone Xs
I have also tried flutter_app_badger still not getting any badge count.
in my main file;
Future<void> _messageHandler(RemoteMessage message) async {
print('background message ${message.notification!.body}');
}
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
).then((value) {
FirebaseMessaging.onBackgroundMessage(_messageHandler);
});
runApp(MyApp());
}
In the initState of my app's first screen;
#override
void initState() {
super.initState();
PushNotify().init(context);
messaging = FirebaseMessaging.instance;
messaging.subscribeToTopic("messaging");
messaging.setForegroundNotificationPresentationOptions(
alert: true, // Required to display a heads up notification
badge: true,
sound: true,
);
if (Platform.isIOS) {
messaging.requestPermission(
alert: true,
announcement: false,
badge: true,
carPlay: false,
criticalAlert: false,
provisional: false,
sound: true,
);
}
FirebaseMessaging.onMessage.listen((RemoteMessage event) {
// FlutterAppBadger.updateBadgeCount(1);
if (event.notification != null) {
print('Message also contained a notification: ${event.notification}');
}
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
print('Message clicked!');
// FlutterAppBadger.removeBadge();
});
}
I struggled with this problem myself the past month.
I came to a realisation that the only way you're going to be able to show a badge ( on iOS ) when the notification is delivered is if you're sending that message from a custom-server and not the Firebase console.
The key issue here is the payload used to send the notification. You have to explicitly include a badge count in your payload for it to be shown on the iOS app icon.
If you're familiar with Typescript or Javascript, sending the message using this payload will show the given badge on the iOS app icon ( for messages sent when the app is in the background or terminated )
const createTokenPayload = (
token: string,
title: string,
message: string,
id?: string,
badge?: number
): TokenMessage => {
const payload: TokenMessage = {
token: token,
notification: {
title: title,
body: message,
},
apns: {
payload: {
aps: {
contentAvailable: true,
badge: badge, // this one here
},
},
},
android: {
priority: "high",
},
};
return payload;
};
That is for a notification sent to a single token.
I could not find a way to add badges when I wanted to send to all users subscribing to a topic. I thought the reason behind is; sending badges with the same value for all users subscribed to a topic irrespective of their current badge-counts is not good.
So the solution I am suggesting for this problem is sending individual notifications with the same message but different badges depending on the current value of the badge for each user.
I hope this helps.
I'm new to using Apple APN to send push notifications. With FCM (Firebase), there is an 'image' property you can use to send a big picture with your push notification. It doesn't look like there is an option to send a big picture with your push notification using Apple APN based on this documentation:
https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification
This is an example of a post request using the Apple APN:
"aps" : {
"alert" : {
"title" : "Game Request",
"subtitle" : "Five Card Draw",
"body" : "Bob wants to play poker"
},
"category" : "GAME_INVITATION"
},
Is there an property you can use in the aps object or the alert object that allows you to send a big picture with your push notification?
How do you send a big picture with your push notification using Apple APN?
Are you sending your notifications via firebase?
Then actually there should be an option, but you need then to specify the picture for the different platforms like:
const message = {
notification: {
title: 'Sparky says hello!'
},
android: {
notification: {
imageUrl: 'https://foo.bar.pizza-monster.png'
}
},
apns: {
payload: {
aps: {
'mutable-content': 1
}
},
fcm_options: {
image: 'https://foo.bar.pizza-monster.png'
}
},
webpush: {
headers: {
image: 'https://foo.bar.pizza-monster.png'
}
},
topic: topicName,
};
As I understand the documentation and the example here you can only send a link that will be loaded on receiving the picture.
Another idea could be to put the same in a custom payload in a data object within the message. But keep in mind there are some limitations to notifications:
limits for push payload size determined by Google and Apple — 4096 bytes (in iOS 8 and lower, the maximum size allowed for a notification payload is 2048 bytes [1]).
Links to further explore:
[enter link description here][1]
[enter link description here][2]
[1]: https://help.pushwoosh.com/hc/en-us/articles/360000440366-Limit-of-characters-that-can-be-sent-through-a-push-notification#:~:text=Answer%3A,is%202048%20bytes%20%5B1%5D).
[2]: https://firebase.google.com/docs/cloud-messaging/ios/send-image
I am using firebase cloud messaging to send push notifications to our iOS application. When the application is in the foreground everything works fine.
The problem is when i send the app to the background and then send a notification.
I expect the following delegate to be called, but it is not:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult)-> Void) {
I have ticked the "remote notifications" background mode in my apps signing & capabilities tab.
The payload i send to firebase looks like this:
{"to":"--TOKEN--",
"priority":"high",
"content_available":true,
"mutable_content": true,
"notification":
{"body":"Test Notification",
"title":"New Notification",
"sound":"default",
"click_action":"notification"
}
}
This payload is sent to firebase via https://fcm.googleapis.com/fcm/send
When i click an notification it is then processed, and i can see the apns version i receive looks like this:
[AnyHashable("google.c.sender.id"): 8xxxxxxxx, AnyHashable("gcm.message_id"): 1xxxxxxxxxx, AnyHashable("google.c.a.e"): 1, AnyHashable("google.c.fid"): fxxxxxxxxx, AnyHashable("aps"): {
alert = {
body = "Test Notification";
title = "New Notification";
};
category = feedback;
"content-available" = 1;
"mutable-content" = 1;
sound = default;
}]
I am not sure why content-available and mutable-content appear in quotes? I know firebase convers its payload to apns format, is there something wrong here?
I want the delegate to be called so that i can execute some code to maintain various data items, so it is important that i can run some code when my app is in the background and a notification is received.
I am not sure what config i am missing as everything i read seems to say this is all i need to do?
My ios app has stopped playing sound for push notification since quite long. We have azure notification hub as our backend for sending notifications.
As per our discussion with MS Azure team, they informed that in order to enable the sound, we would need to include "Sound" property as per new APIs. But using azure notification hub this API would become bad request as "Sound" property is not supported by them. There is nothing else can be performed from azure notification side and they suggested to reach out to APNS to seek any alternatives, if any.
They are still working to add support for Critical Alerts on APNS.
Is there any work around? Has anyone faced such issue? Any help would be greatly appreciaated.
Below is the code for registering for push notifications:
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound], completionHandler: {(_ granted: Bool, _ error: Error?) -> Void in
if error == nil {
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}
}
})
To play the default system sound, create your sound object using the default method.
By default, a notification contains an alert message that is displayed to the user without playing a sound. If you want to play a sound when a notification arrives, Apple provides one “default” sound that you can specify.
{"aps":{"sound":"default"}}
References:
https://developer.apple.com/documentation/usernotifications/unnotificationsound
There is an option to add sound to your remote notification locally, by this way no need to depend on server for adding sound property,
You can also use a notification service app extension to add a sound file to a notification shortly before delivery. In your extension, create a UNNotificationSound object and add it to your notification content in the same way that you’d for a local notification.
For this need to create a new target, you may need create cer for the notification extension's bundle ID same as main app.
Sample UNNotificationServiceExtension code for your reference,
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
// MARK:- Notification lifecycle
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
// -------------
// To play default sound
let defaultSound = UNNotificationSound.default
bestAttemptContent?.sound = defaultSound
// -----OR------
// To play custom sound
let customSound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "somelocalfile"))
bestAttemptContent?.sound = customSound
// --------------
contentHandler(bestAttemptContent!)
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your “best attempt” at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
// If notification doesnt process in time, Increment badge counter. Default notification will be shown
let customSound = UNNotificationSound(named: UNNotificationSoundName(rawValue: "somelocalfile"))
bestAttemptContent.sound = customSound
contentHandler(bestAttemptContent)
}
}
}
NOTE:
According to this doc sound should be sent as default or any custom sound file path. There is no mentioning on behaviour of ignoring "Sound" in payload, considering this as Sound is mandatory!!.
EDIT:
#shaqir From Apple documentation here,
Include this key when you want the system to play a sound.
If the sound file cannot be found, or if you specify default for the value, the system plays the default alert sound.
Means if no sound key in payload, no sound will be played. It should be default or wrong audio path.
I tried to implement the new Notification Service Extension, but I have a problem.
In my NotificationService.swift file I have this code:
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
// Modify the notification content here...
bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
print(bestAttemptContent.body)
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
When I got a push notification the didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) method never called.
Maybe I misunderstood how this extension is working?
Check your deployment target on Service Extension.
I had deployment target set to 10.2 when testing on device with 10.1 and extension wasn't called until I changed it.
Another issue might be debugging.. In order to make it work You need to attach Service Extension Process. In Xcode menu Debug > Attach To Process > Name of your extension
Run service extension as the Target instead of the app. Then it will ask for which app you have run service extension, then select your app and it will send the notification.
Make sure the deployment target of the service extension is less than your physical device's OS version.
Ensure payload contains 'mutable-content: 1'
{"aps" : {
"alert" : {
"title" : "Introduction To Notification",
"subtitle" : "Session 707",
"body" : "New Notification Look Amazing"
},
"sound" : "default",
"category" : "message",
"badge" : 1,
"mutable-content": 1
},
"attachment-url": "https://api.buienradar.nl/Image/1.0/RadarMapNL"
}
Don't add the content-available flag in aps or if you've added then make sure it's set to 0.
---------------------------------------- if nothing works, Restart your device --------------------------------------
I was getting crazy. Finally I realized that I had the deployment target of Notification Service Extension was 10.3 (my phone too). I changed to 10.2 and it works perfectly
If all else fails, restart your device. I just spent 2 hours on this and simply restarting my phone fixed it.
Your push notification payload should contain the "mutable-content" : 1 key value pair.
The remote notification’s aps dictionary includes the mutable-content key with the value set to 1.
Ex of push notification payload JSON:
{
"aps":{
"alert": {
"body": "My Push Notification",
"title" : "Notification title"},
"mutable-content" : 1,
"badge":0},
}
This works perfectly fine and i get the Push notification as follows:
Also Note That :
You cannot modify silent notifications or those that only play a sound or badge the app’s icon.
You can try Pusher or Houston for testing the Push Notifications.
I have the same issue but my problem was notification extension is "app". It should be appex
From docs on UNNotificationServiceExtension class:
The remote notification is configured to display an alert.
The remote notification’s aps dictionary includes the mutable-content key with the value set to 1.
You cannot modify silent notifications or those that only play a sound or badge the app’s icon.
Basically
Must include:
mutable-content: 1
an alert dictionary.
Must NOT include:
content-available: 1
To summarize Apple is doing its best to not allow apps to mutate silent notifications. It wants to allow that only on user notifications (user facing notifications)
If you trying to send push from Firebase console it's not possible to send "mutable_content": true.
So you should send it from server or postman or any type of terminal.
And your payload should be like this: For firebase
{
"to" : "your device token/ firebase topic",
"notification" : {
"body" : "This is an FCM notification that displays an image.!",
"title" : "FCM Notification"
},
"mutable_content": true
}
If not from FCM then your payload should be:
{"aps" : {
"alert" : {
"title" : "Your title",
"subtitle" : "Your subtitle",
"body" : "Your body"
},
"sound" : "default",
"category" : "message",
"badge" : 1,
"mutable-content": 1
},
"attachment-url": "https://yourattachment-url.com"
}
Note: And make sure the deployment target of the service extension is less that your physical device's OS version. Like if your development target is 10.3, your service extension version should be 10.2.
you need to add "mutable-content": 1 to your payload
This is a sample if you implement with Swift and Firebase cloud functions (Node.js) to send notifications has attachments with Notification Service Extension.
Below i have mentioned some important points only.
Payload sample
const payload = {
notification: {
title: name,
body: messageText,
badge: "1",
mutable_content: "true"
},
data: {
type: "MESSAGE",
fromUserId: name,
attachmentUrl: imageUrl
}};
mutable_content: "true" this part is the important i spend so much of time to find the exact naming most of the documentations are invalid now.
The next main thing is the way we have to run the App
Select the Notification Service Extension with your device
After when you run you will get a popup to Select your main project
This way it will call the NotificationService didReceive function (Which is inside the Notification Service Extension) when you receive a notification.