androidfirebasereact-nativepush-notificationreact-native-push-notification

react-native-push-notification custom sound not working when android app is killed


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.


Solution

  • 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!