urbanairship / react-native-airship

Airship React Native module
Other
86 stars 62 forks source link

`setForegroundDisplayPredicate` doesn't show the notification on foreground #580

Closed idrakimuhamad closed 1 month ago

idrakimuhamad commented 1 month ago

❗For how-to inquiries involving Airship functionality or use cases, please contact (support)[https://support.airship.com/].

Preliminary Info

What Airship dependencies are you using?

"@ua/react-native-airship": "^19.1.0"

Report

What unexpected behavior are you seeing?

When setting setForegroundDisplayPredicate predicate as true, it is not showing the incoming notification while the app is on foreground.

What is the expected behavior?

I'm expecting that I would see or get the notification in the notification tray and sound

What are the steps to reproduce the unexpected behavior?

Airship.push.android.setForegroundDisplayPredicate(
    () => Promise.resolve(true)
)

Is this expected or maybe I'm missing anything? Or does the notification send require some specific configuration for this to work?

rlepinski commented 1 month ago

It should default to displaying in the foreground. How are you sending the push? Do you have logs you can share?

idrakimuhamad commented 1 month ago

It should default to displaying in the foreground. How are you sending the push? Do you have logs you can share?

The push are send from our backend. I think the log looked like this.

image

What confuses me is that I can see that the push send from the airship console are showing on foreground but not for the one from our backend.

rlepinski commented 1 month ago

You are sending this on Android com.urbanairship.foreground_display: false, try without that

idrakimuhamad commented 1 month ago

Ahh I see. I think they did this on purpose for the current app since they already have in app toast shown on foreground. I will need to adapt to this then and work with other means.

Anyway, does it mean that iOS will also behave this way?

rlepinski commented 1 month ago

On iOS, we dont look for that key, so its not needed. The default behavior is to not display on foreground. We have a similar method though that you can assign the foreground presentation options per notification to control this.https://docs.airship.com/reference/libraries/react-native/latest/classes/AirshipPushIOS.html#setForegroundPresentationOptionsCallback

idrakimuhamad commented 1 month ago

Ok so for iOS even the payload com.urbanairship.foreground_display: false is there, it will not looked at it. As such, if I use the setForegroundPresentationOptionsCallback to show the notification on foreground, it supposed to present the notification. Correct?

I just wanna confirm this cause since I can't change (yet) what backend send, so I need to use other library to show the notification. If this payload only affecting android, i will only handle this for Android and let iOS uses the SDK's API

rlepinski commented 1 month ago

Ok so for iOS even the payload com.urbanairship.foreground_display: false is there, it will not looked at it. As such, if I use the setForegroundPresentationOptionsCallback to show the notification on foreground, it supposed to present the notification. Correct?

Correct, have full control at receive time

I just wanna confirm this cause since I can't change (yet) what backend send, so I need to use other library to show the notification. If this payload only affecting android, i will only handle this for Android and let iOS uses the SDK's API

The level of effort to do that vs remote the key seems pretty high to me. Not to mention you will break some Airship features doing it that way:

If you really cant just remove that key from your backend, I would recommend just scrubbing the key at receive time before Airship processes it. You can do that with registering your own FCM service:

package com.urbanairship.reactnative;

import android.os.Bundle;

import androidx.annotation.NonNull;

import com.google.firebase.messaging.RemoteMessage;
import com.urbanairship.push.fcm.AirshipFirebaseMessagingService;

public class FirebaseMessagingService extends AirshipFirebaseMessagingService {
    @Override
    public void onMessageReceived(@NonNull RemoteMessage message) {

        if (message.getData().containsKey("com.urbanairship.foreground_display")) {
            Bundle bundle = message.toIntent().getExtras();
            bundle.remove("com.urbanairship.foreground_display");
            RemoteMessage modified = new RemoteMessage(bundle);
            super.onMessageReceived(modified);
        } else {
            super.onMessageReceived(message);
        }
    }
}

Then register it in the manifest in the application entry:

        <service
            android:name=".FirebaseMessagingService"
            android:exported="false">
            <intent-filter android:priority="100">
                <action android:name="com.google.firebase.MESSAGING_EVENT" />
            </intent-filter>
        </service>
idrakimuhamad commented 1 month ago

Yeah, because we currently shared the same backend with two different apps, with same purpose. However, the current app already handle foreground notifications with in app toast, while our new app we intend to just show the native notifications HUD when on foreground. So backend have to explicitly set the foreground to false, to avoid redundancy.

I can try that service workaround, that looks promising.

Also, on side note related to foreground, I've tried the iOS's setForegroundPresentationOptionsCallback, however i'm unable to get/see the notification. The push i sent are through the airship portal, and i'm able to receive it if the app is in background. I've actually filed a support request from the portal yesterday.

When I log within the callback, I can see the notification payload. However, the presentation are not actually showing. Example of my implementation using the callback.

Airship.push.iOS.setForegroundPresentationOptionsCallback((payload) => {
      console.log("ForegroundPresentationOptionsCallback", payload)
      return Promise.resolve([
            iOS.ForegroundPresentationOption.Banner,
            iOS.ForegroundPresentationOption.Banner,
            iOS.ForegroundPresentationOption.Badge,
            iOS.ForegroundPresentationOption.List,
            iOS.ForegroundPresentationOption.Sound,
      ])
})
rlepinski commented 1 month ago

Its most likely being overridden by either your app delegate or some other plugin. Could you look for the usage of this:

-(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler

in your app delegate, or share it? If you are not implementing that, could you provide a list of the plugins you are using?

idrakimuhamad commented 1 month ago

I didn't have such usage in my app delegate, but that reminds me that I am using Notifee to create local notifications. I have looked into its code base, and they have that usage in their code.

I will try to remove it and see if that does the trick. If it does, I will try the workaround you provided for Android. That way, I wouldn't need to use Notifee anymore.

rlepinski commented 1 month ago

I think its Notifee that will cause the callback on iOS to not work - https://github.com/invertase/notifee/blob/main/ios/NotifeeCore/NotifeeCore%2BUNUserNotificationCenter.m#L99

idrakimuhamad commented 1 month ago

I can confirm removing notifee solved it for iOS. I will try for the android.

Update

Okay doesn't seems to get it to work. The message within the service are updated, but the push received listener still contained the key and it doesn't show the notification on foreground still.

For reference, this is how I do it.

package com.mypackage.name

import com.google.firebase.messaging.RemoteMessage
import com.urbanairship.push.fcm.AirshipFirebaseMessagingService

class FirebaseMessagingService : AirshipFirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        println("FirebaseMessagingService\n")
        println("Message: ${message.data}")

        if (message.data.containsKey("com.urbanairship.foreground_display")) {
            val bundle = message.toIntent().extras
            bundle?.remove("com.urbanairship.foreground_display")

            println("Modified bundle: $bundle")

            val modified = RemoteMessage(bundle!!)
            super.onMessageReceived(modified)
        } else {
            super.onMessageReceived(message)
        }
    }
}

The log:

FirebaseMessagingService
Message: {com.urbanairship.push.ALERT=You have a pending transaction to authorise. Approve or reject this transaction now., com.urbanairship.metadata=xxx, com.urbanairship.push.PUSH_ID=xxx, com.urbanairship.foreground_display=false, sometoken=xxx, com.urbanairship.title=Pending Authorisation, com.urbanairship.push.APID=xxx, transactionTime=2024-08-08T21:40:17+08:00, com.urbanairship.push.CANONICAL_PUSH_ID=xxx}
Modified bundle: Bundle[{com.urbanairship.push.ALERT=You have a pending transaction to authorise. Approve or reject this transaction now., google.delivered_priority=normal, com.urbanairship.metadata=xxx, google.sent_time=1723124418268, google.ttl=2419200, google.original_priority=normal, com.urbanairship.push.PUSH_ID=xxx, from=39626265254, sometoken=xxx, google.message_id=0:1723124418278193%459057c6f9fd7ecd, com.urbanairship.title=Pending Authorisation, com.urbanairship.push.APID=xxx, google.c.sender.id=39626265254, transactionTime=2024-08-08T21:40:17+08:00, com.urbanairship.push.CANONICAL_PUSH_ID=xxx}]
'PushReceived:', '{\n  "pushPayload": {\n    "title": "Pending Authorisation",\n    "alert": "You have a pending transaction to authorise. Approve or reject this transaction now.",\n    "extras": {\n      "com.urbanairship.title": "Pending Authorisation",\n      "transactionTime": "2024-08-08T21:40:17+08:00",\n      "com.urbanairship.foreground_display": "false",\n      "com.urbanairship.push.ALERT": "You have a pending transaction to authorise. Approve or reject this transaction now.",\n      "com.urbanairship.push.PUSH_ID": "xxx",\n      "com.urbanairship.push.CANONICAL_PUSH_ID": "xxx",\n      "com.urbanairship.push.APID": "xxx",\n      "sometoken": "xxx",\n      "com.urbanairship.metadata": "xxx",\n      },\n  "isForeground": true\n}'
idrakimuhamad commented 1 month ago

I got it now. I need to remove the call to the superclass method in the beginning, and only call it once the message is updated.

Thanks @rlepinski !

rlepinski commented 1 month ago

Yep, i see I made that mistake in my example, sorry for that! Glad you got it figured out