I have been working on adding custom sounds to push notifications for a react-native app using firebase-admin
version ^9.2.0
and react-native-push-notification
version ^5.1.0
.
The reason why I haven't upgraded to the latest version of react-native-push-notification
is because custom sounds do not seem to work even with proper channel configuration. We are also using expo, which appears to be causing a startup error when using version 7+.
I have two mp3 files called regular.mp3
and mass.mp3
. The firebase functions that send the push notifications send the message using the common data object, but also platform-specific fields for push notification sounds:
admin.messaging().send({
data: {
title,
body,
lat: data.Latitude.toString(),
lng: data.Longitude.toString(),
notificationType: data.NotificationType.toString(),
},
notification:{title,body},
apns:{
payload:{
aps:{
alert:{
title,
body,
},
sound: data.NotificationType === 1 ? "mass.mp3" : "regular.mp3",
},
},
},
android: {
priority: "high",
data: {
sound: data.NotificationType === 1 ? "mass" : "regular",
},
notification: {
sound: data.NotificationType === 1 ? "mass" : "regular",
},
},
topic: topic,
})
From my understanding, the data
field under android
does contain the payload that will be added to the root-level data
object when the app is killed and receive the notification. The plugin's source also seems to be using that exact data
field to set the notification sound (in RNReceivedMessageHandler.java):
JSONObject data = getPushData(notificationData.get("data"));
if (data != null) {
if (!bundle.containsKey("message")) {
bundle.putString("message", data.optString("alert", null));
}
if (!bundle.containsKey("title")) {
bundle.putString("title", data.optString("title", null));
}
if (!bundle.containsKey("sound")) {
bundle.putString("soundName", data.optString("sound", null));
}
if (!bundle.containsKey("color")) {
bundle.putString("color", data.optString("color", null));
}
Here is what I got so far:
Here is the code currently in place to manage the notifications:
In index.ts:
PushNotification.configure({
// (optional) Called when Token is generated (iOS and Android)
onRegister: function(token) {
console.log("TOKEN:", token);
},
// (required) Called when a remote is received or opened, or local notification is opened
onNotification: function(notification) {
console.log("NOTIFICATION:", notification);
// process the notification
// (required) Called when a remote is received or opened, or local notification is opened
//notification.finish(PushNotificationIOS.FetchResult.NoData);
},
// (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)
onAction: function(notification) {
console.log("ACTION:", notification.action);
console.log("NOTIFICATION:", notification);
// process the action
},
// (optional) Called when the user fails to register for remote notifications. Typically occurs when APNS is having issues, or the device is a simulator. (iOS)
onRegistrationError: function(err) {
console.error(err.message, err);
},
// IOS ONLY (optional): default: all - Permissions to register.
permissions: {
alert: true,
badge: true,
sound: true,
},
popInitialNotification: true,
requestPermissions: true,
});
In App.js
CreateIncidentPushNotification=(remoteMessage)=>{
const {data} = remoteMessage;
PushNotification.localNotification({
title: data.title,
message: data.body,
playSound: true,
soundName: data.notificationType === "1" ? "mass" : "regular",
});
}
I was wondering if anyone else had an idea about what could be going on. The notification still manages to get to my device even when the app is killed, which is great. The only missing part is the sound.
Okay so I finally got it working. I had to add the following to my manifest file and comment a receiver:
<meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_name"
android:value="my-channel"/>
<meta-data android:name="com.dieam.reactnativepushnotification.notification_channel_description"
android:value="my channel"/>
<!-- Change the value to true to enable pop-up for in foreground (remote-only, for local use ignoreInForeground) -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_foreground"
android:value="false"/>
<!-- Change the value to false if you don't want the creation of the default channel -->
<meta-data android:name="com.dieam.reactnativepushnotification.channel_create_default"
android:value="true "/>
<!-- Change the resource name to your App's accent color - or any other color you want -->
<meta-data android:name="com.dieam.reactnativepushnotification.notification_color"
android:resource="@color/white"/> <!-- or @android:color/{name} to use a standard color -->
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationActions" />
<receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationPublisher" />
<!-- <receiver android:name="com.dieam.reactnativepushnotification.modules.RNPushNotificationBootEventReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver> -->
Then, I had to use channels in order to get the sounds working in foreground, background, and when the app is killed. As you can see, I created a custom channel in my manifest file and activated the default channel as well. I HAD to activate the default channels because I have two notification types that require different sounds. Using a single channel was NOT WORKING.
Once the manifest file has been updated, I modified the firebase functions (using firebase-admin
to do the following:
admin.messaging().send({
data: {
title,
body,
lat: data.Latitude.toString(),
lng: data.Longitude.toString(),
notificationType: data.NotificationType.toString(),
},
notification:{title,body},
apns:{
payload:{
aps:{
alert:{
title,
body,
},
sound: data.NotificationType === 1 ? "mass.mp3" : "regular.mp3",
},
},
},
android: {
priority: "high",
data: {
sound: data.NotificationType === 1 ? "mass" : "regular",
channelId: data.NotificationType === 1 ? "my-channel" : "fcm_fallback_notification_channel",
},
notification: {
sound: data.NotificationType === 1 ? "mass" : "regular",
channelId: data.NotificationType === 1 ? "my-channel" : "fcm_fallback_notification_channel",
},
},
topic: topic,
})
.then((response) => {
console.log('Successfully sent message:', response);
})
.catch((error) => {
console.log('Error sending message:', error);
});
Firebase was now aware of the two channels I was using. I then moved to the application code and handled the local notification like this:
PushNotification.localNotification({
title: data.title,
message: data.body,
playSound: true,
soundName: data.notificationType === "1" ? "mass" : "regular",
channelId: data.notificationType === "1" ? "my-channel" : "fcm_fallback_notification_channel"
});
I also activated both onMessage
and setBackgroundMessageHandler
handlers of the react-native-push-notification
package:
messaging().onMessage(this.sendMessage);
messaging().setBackgroundMessageHandler(this.sendMessage);
Where this.sendMessage
is responsible to call the localNotification
call mentioned above.
By the way, I am stil getting duplicated notifications when the app is in background, so this is purely a fix for the sounds.
UPDATE:
removing the setBackgroundMessageHandler
removed the duplicates!!!! :)
Peace!