invertase / notifee

⚛️ A feature rich notifications library for React Native.
https://notifee.app
Apache License 2.0
1.84k stars 222 forks source link

Notifications don't display when the app is in background. But it displays, when the app is in killed or foreground. #940

Closed AhmetEkiz closed 3 months ago

AhmetEkiz commented 10 months ago

I am using Notifee with Firebase. Everything works when the app is in the foreground or killed state. But it doesn't work when the app is in the background. I see the notifications come to devices with console.log but the DisplayNotificationData function doesn't work until I open the app.

// ....Home\index.js
messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('Message handled in the background in index.js!', remoteMessage);

  // DisplayNotification(remoteMessage);
  DisplayNotificationData(remoteMessage);
}

Here is the displaying notifications function with Notifee

// android\app\src\utils\push_notification_helper.js
export async function DisplayNotificationData(remoteMessage) {
  // Required for iOS
  // See https://notifee.app/react-native/docs/ios/permissions
  await notifee.requestPermission();

  // Create a channel for android
  const channelId = await notifee.createChannel({
    id: 'important',
    name: 'Important Notifications',
    importance: AndroidImportance.HIGH,
  });

  // Access custom data
  const {title, body} = remoteMessage.data;

  // Display a notification using notifee
  await notifee.displayNotification({
    title: title,
    body: body,
    android: {
      channelId: channelId,
    },
  });

  console.log('DisplayNotificationData is worked!');
}

Here is the checking the restrictions:

// android\app\src\utils\push_notification_helper.js
// the function that check the Battery Optimization Enabled and it pops up turn off message.
export async function requestbatteryOptimizationTurnOff() {
  // 1. checks if battery optimization is enabled
  const batteryOptimizationEnabled =
    await notifee.isBatteryOptimizationEnabled();
  if (batteryOptimizationEnabled) {
    // 2. ask your users to disable the feature
    Alert.alert(
      'Restrictions Detected',
      'To ensure notifications are delivered, please disable battery optimization for the app.',
      [
        // 3. launch intent to navigate the user to the appropriate screen
        {
          text: 'OK, open settings',
          onPress: async () => await notifee.openBatteryOptimizationSettings(),
        },
        {
          text: 'Cancel',
          onPress: () => console.log('Cancel Pressed'),
          style: 'cancel',
        },
      ],
      {cancelable: false},
    );
  } else {
    console.log("Restrictions aren't detected for Battery Optimization");
  }
}

export async function requestPowerManagerSettingsTurnOff() {
  // 1. get info on the device and the Power Manager settings
  const powerManagerInfo = await notifee.getPowerManagerInfo();
  if (powerManagerInfo.activity) {
    // 2. ask your users to adjust their settings
    Alert.alert(
      'Restrictions Detected',
      'To ensure notifications are delivered, please adjust your settings to prevent the app from being killed',
      [
        // 3. launch intent to navigate the user to the appropriate screen
        {
          text: 'OK, open settings',
          onPress: async () => await notifee.openPowerManagerSettings(),
        },
        {
          text: 'Cancel',
          onPress: () => console.log('Cancel Pressed'),
          style: 'cancel',
        },
      ],
      {cancelable: false},
    );
  } else {
    console.log("Restrictions aren't detected for Power Managment.");
  }
}

Thanks for the help

mritul commented 9 months ago

Did you get it resolved yet @AhmetEkiz ? I think I might have the exact same issue as you !

AhmetEkiz commented 9 months ago

@mritul I’m sorry, I don't have a solution at the moment. If I find one, I will let you know.

mritul commented 9 months ago

Likewise ! It's the most important case we can't miss because users mostly have it in the minimized state over killed state 😮

AhmetEkiz commented 9 months ago

Hi again, I think this is not the best solution but if you have a rush, you can send data and notifications at the same time to solve the problem. If you send a notification, it works on the background state but will be complicated. I think you must create a logic that if the notification is already sent in the background with notification, you must cancel the only data notification when the app comes to the foreground.

Firebase: notification-messages-with-optional-data-payload

App behavior when receiving messages that include both notification and data payloads depends on whether the app is in the background or the foreground—essentially, whether or not it is active at the time of receipt.

  • When in the background, apps receive the notification payload in the notification tray, and only handle the data payload when the user taps on the notification.
  • When in the foreground, your app receives a message object with both payloads available.
{
  "message":{
    "token":"bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
    "notification":{
      "title":"Portugal vs. Denmark",
      "body":"great match!"
    },
    "data" : {
      "Nick" : "Mario",
      "Room" : "PortugalVSDenmark"
    }
  }
}
mritul commented 9 months ago

Thanks for the time @AhmetEkiz ! Seems like a workaround, but would it let us show a customized notifee message like say, an ongoing notification with a custom sound, because FCM notifications go as default notifications without any customisations ?

AhmetEkiz commented 9 months ago

@mritul Exactly, it is a workaround! I suppose that like you said, we cannot do any customizations with FCM notification. I said this solution because it might be useful to you. 😊

mritul commented 9 months ago

Get it ! Thanks for helping 😄

ainnotate commented 9 months ago

@AhmetEkiz I'm facing the exact same problem.

I'm using notifee for dataSync and I'm using Foreground Service (asForegroundService: true) for this purpose. Here is my observation on different android versions

Android 12 and older:

Result: All works expected. This is my expected behaviour.

Android 13

Android 14

To me, the foreground service not running when app is in background or minimized is of concern.

I'm not sure if the workaround you suggested is applicable for my usecase (local notification). Any pointer would help.

AhmetEkiz commented 9 months ago

@ainnotate Thank you for providing us with such valuable information. I thought there was a way to recognize the device is in the background, and if possible, we can cancel the notification when the app is opened. And If the device is in the killed or foreground, we won't show the Firebase notifications. So, it will be a very nasty workaround. Just so you know, I don't apply this workaround. It was just a suggestion for an emergency solution.

ainnotate commented 9 months ago

Could someone help in understanding the below behaviours?

In Android 13 and 14,

  1. Why does the foreground service stop when the app goes to background (user clicks home button)?
  2. When user kills the app directly when minimized (killed directly from 1 above), the foreground service never starts (but it is still registered). The only way to recover is to "Force Stop" the app from settings menu. However, when the app is killed directly (without minimizing or putting in background), the foreground service continues without any issue.

Is this something related to RN or is this Android behaviour?

I'm trying to develop (or find) a native app that implements a foreground service, to understand this behaviour better.

@mikehardy could you throw some light on this, please?

sagaryadav97 commented 9 months ago

I am using Notifee with Firebase. Everything works when the app is in the foreground or killed state. But it doesn't work when the app is in the background. I see the notifications come to devices with console.log but the DisplayNotificationData function doesn't work until I open the app.

  • Set setBackgroundMessageHandler in ....Home\index.js
  • I send the only data notifications from Firebase as a solution for the notifications display twice in the killed state.
  • I checked and removed the Battery Optimization and Management restrictions.
// ....Home\index.js
messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('Message handled in the background in index.js!', remoteMessage);

  // DisplayNotification(remoteMessage);
  DisplayNotificationData(remoteMessage);
}

Here is the displaying notifications function with Notifee

// android\app\src\utils\push_notification_helper.js
export async function DisplayNotificationData(remoteMessage) {
  // Required for iOS
  // See https://notifee.app/react-native/docs/ios/permissions
  await notifee.requestPermission();

  // Create a channel for android
  const channelId = await notifee.createChannel({
    id: 'important',
    name: 'Important Notifications',
    importance: AndroidImportance.HIGH,
  });

  // Access custom data
  const {title, body} = remoteMessage.data;

  // Display a notification using notifee
  await notifee.displayNotification({
    title: title,
    body: body,
    android: {
      channelId: channelId,
    },
  });

  console.log('DisplayNotificationData is worked!');
}

Here is the checking the restrictions:

// android\app\src\utils\push_notification_helper.js
// the function that check the Battery Optimization Enabled and it pops up turn off message.
export async function requestbatteryOptimizationTurnOff() {
  // 1. checks if battery optimization is enabled
  const batteryOptimizationEnabled =
    await notifee.isBatteryOptimizationEnabled();
  if (batteryOptimizationEnabled) {
    // 2. ask your users to disable the feature
    Alert.alert(
      'Restrictions Detected',
      'To ensure notifications are delivered, please disable battery optimization for the app.',
      [
        // 3. launch intent to navigate the user to the appropriate screen
        {
          text: 'OK, open settings',
          onPress: async () => await notifee.openBatteryOptimizationSettings(),
        },
        {
          text: 'Cancel',
          onPress: () => console.log('Cancel Pressed'),
          style: 'cancel',
        },
      ],
      {cancelable: false},
    );
  } else {
    console.log("Restrictions aren't detected for Battery Optimization");
  }
}

export async function requestPowerManagerSettingsTurnOff() {
  // 1. get info on the device and the Power Manager settings
  const powerManagerInfo = await notifee.getPowerManagerInfo();
  if (powerManagerInfo.activity) {
    // 2. ask your users to adjust their settings
    Alert.alert(
      'Restrictions Detected',
      'To ensure notifications are delivered, please adjust your settings to prevent the app from being killed',
      [
        // 3. launch intent to navigate the user to the appropriate screen
        {
          text: 'OK, open settings',
          onPress: async () => await notifee.openPowerManagerSettings(),
        },
        {
          text: 'Cancel',
          onPress: () => console.log('Cancel Pressed'),
          style: 'cancel',
        },
      ],
      {cancelable: false},
    );
  } else {
    console.log("Restrictions aren't detected for Power Managment.");
  }
}

Thanks for the help

i am facing same issue. but in mine i am not getting notifications when the app is in background or killed state. when i open app all notifications will start showing. and press action is undefined. works in android 11 and below versions. issue in above 12+ versions.

sagaryadav97 commented 9 months ago

Did you get it resolved yet @AhmetEkiz ? I have the same issue

AhmetEkiz commented 9 months ago

@sagaryadav97 no, I haven't solved it yet.

mritul commented 9 months ago

If there could be any way of adding native code to achieve the proper working state it would be really great

whentao commented 9 months ago

I have the same issue here.

sagaryadav97 commented 8 months ago

switched to one signal. working in all states.

ntxl1210 commented 7 months ago

I have same issue. My rootcause is I call method notifee.requestPermission without platform checking before method notifee.displayNotification.

maxs1m commented 7 months ago

the same problem was solved by excluding "await notifee.requestPermission();" from DisplayNotificationData in your case. Now data only push notifications come in any state of the application

rjuevesano commented 7 months ago

Is there a way to turn-off push notifications if the app is active and turn-on if not active? I want to render an in-app notification if app is active.

AhmetEkiz commented 6 months ago

switched to one signal. working in all states.

Thanks for the update. I can't apply this solution for nowadays. 😊

jafar-jabr commented 5 months ago

Since this issue is still open, I wanted to share how I resolved the same problem in my project. The solution involved modifying the AndroidManifest.xml file to include a specific meta-data tag within the <application> element. Here’s what I added:

<meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/default_notification_channel_id"
            tools:replace="android:value" />

the value of @string/default_notification_channel_id is the id of the channel which you created it using notifee.createChannel, I hope this helps anyone else facing the same issue!

ntxl1210 commented 5 months ago

Since this issue is still open, I wanted to share how I resolved the same problem in my project. The solution involved modifying the AndroidManifest.xml file to include a specific meta-data tag within the <application> element. Here’s what I added:

<meta-data
            android:name="com.google.firebase.messaging.default_notification_channel_id"
            android:value="@string/default_notification_channel_id"
            tools:replace="android:value" />

the value of @string/default_notification_channel_id is the id of the channel which you created it using notifee.createChannel, I hope this helps anyone else facing the same issue!

How can I do if my channel id is dynamic?

machivictor commented 5 months ago

When an incoming message is "data-only" (contains no notification option), both Android & iOS regard it as low priority and will prevent the application from waking (ignoring the message). To allow data-only messages to trigger the background handler, you must set the "priority" to "high" on Android, and enable the content-available flag on iOS. For example, if using the Node.js firebase-admin package to send a message:

admin.messaging().sendMulticast({
  tokens: [ ... ], // device fcm tokens...
  data: { ... },
  android: {
    priority: "high", // Required for background/quit data-only messages on Android
  },
  apns: {
    headers: {
      "apns-priority": "5",
    },
    payload: {
      aps: {
        contentAvailable: true, // Required for background/quit data-only messages on iOS
      },
    },
  },
});

I also suggest that you read the docs about FCM messages

github-actions[bot] commented 4 months ago

Hello 👋, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

shabbirujjainwala commented 2 months ago

iam still facing this issue

Gloifyogesh commented 2 months ago

still facing the same issue, Help me resolve the issue. Is there any way to push notification work in background and kill state ?

sn-devz commented 2 months ago

@AhmetEkiz @Gloifyogesh I encountered a similar issue, and I was able to resolve it with these line of code. Give it a try; it might solve your problem too. -> data: message?.extra ? JSON.parse(message?.extra) : {}, -> importance: AndroidImportance.HIGH,

const onNotifeeMessageReceivedBackground = async (message: any) => { const channelId = await notifee.createChannel({ id: 'default', name: 'Default Channel', });

notifee.displayNotification({
  id: message?.cid.toString(),
  title: message.t,
  **data: message?.extra ? JSON.parse(message?.extra) : {},**
  body: message.a,
  android: {
    channelId: channelId,
    **importance: AndroidImportance.HIGH,**
    pressAction: {
      id: 'default',
    },
  },
});

};