Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: UNNotificationServiceExtension support on iOS? #24

Open
tamimattafi opened this issue Apr 26, 2024 · 36 comments
Open

Question: UNNotificationServiceExtension support on iOS? #24

tamimattafi opened this issue Apr 26, 2024 · 36 comments

Comments

@tamimattafi
Copy link

Hello!
Thank you for this amazing library!

I'm wondering, is there a possibility we could have support for changing push notification content for iOS? In a native app, it's usually done using UNNotificationServiceExtension, is it technically possible we could have access to such functionality directly from the library and on the Kotlin side (iosMain for instance) without having to implement such extension in every project?

@mirzemehdi
Copy link
Owner

mirzemehdi commented Apr 26, 2024

@tamimattafi How it works in ios? You can change content even app is in background, and after you get push notification you can change content?

@mirzemehdi
Copy link
Owner

In foreground when you get notification you can modify it currently, and show it as you wanted. For this when you initialize the library for configuration parameter (showPushNotification) you need to pass false, then on listeners, you can show user any modified notication.

//Android

NotifierManager.initialize(
           configuration = NotificationPlatformConfiguration.Android(
               notificationIconResId = R.drawable.ic_launcher_foreground,
               showPushNotification = false,
           )
       )

//ios

     NotifierManager.shared.initialize(configuration: NotificationPlatformConfigurationIos(showPushNotification: false))

https://github.com/mirzemehdi/KMPNotifier?tab=readme-ov-file#platform-setup

@tamimattafi
Copy link
Author

@mirzemehdi Yes, the extension service is meant to work in the background

@mirzemehdi
Copy link
Owner

However when app is in background, and when receiving notification type of message, notification title and body will be shown as it is to the user as this will be handled by the system, in android it is like that at least.

But you can hack around by sending only data type of message, then based on that you can show to the user whatever you want.

@tamimattafi
Copy link
Author

tamimattafi commented Apr 26, 2024

@mirzemehdi Is there a possibility of showing a notification from the background, using data type messages? I would be very thankful if you point out to an example.
Unfortunately, I have tried something similar, and it works only if the iOS app is in the foreground, while on Android it works on both foreground and background.

@mirzemehdi
Copy link
Owner

@tamimattafi Looks like using data type messages with ios when it is in background is tricky. For my case, I can get notification type of message both in background and foreground, but I also have a problem receiving data type of message when app is in background.

I also checked this one https://stackoverflow.com/questions/38166203/ios-data-notifications-with-fcm, and even setting content_available: true wasn't working for me.

If you can find another solution, you can let us know here as well!

@tamimattafi
Copy link
Author

tamimattafi commented Apr 29, 2024

@mirzemehdi I implemented UNNotificationServiceExtension in my project, it has the following behavior and limitations:

  1. It must be in a separate target (as extension), which means it needs its own bundle ID and if you want to access data stored by the main app, you will need to use groups, also the runtime is limited to 24MB. This is very different from what we used to in android
  2. When the app is in the background, UNNotificationServiceExtension is triggered, it gives you the ability and some time to mutate the original one that came from the server, however, for some cases this might not be enough, for example, without a permission from Apple you can't ignore notifications that you don't want to display. To trigger the service, you need to set mutable-content: true in your aps payload, and you must have an alert
  3. When the app is in the foreground, UNNotificationServiceExtension is ignored, instead didReceiveRemoteNotification is called with userInfo. This is a very weird approach for an android developer, we used to have a single entry point.
  4. In android, or using localNotifier, you can set an id to your notification to be able to control it later, but that's not the case for background iOS notifications, the identifier can be set only using collapse-id, which must be inside your aps payload
  5. You might need to set UNUserNotificationCenter.delegate in your didFinishLaunchingWithOptions, because some push notifications can be ignored and they must be controlled using func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification)
  6. If your app is very large, you won't be able to use shared or composeApp in the notification extension (different target) due to memory limitations (24MB), you might need to break down your logic and create a new shared module with minimal dependencies, otherwise, the system can kill this process very unexpectedly

My experience with Push Notifications on iOS is kind of interesting but frustrating at the same time, not sure why they chose to have different entry points and different targets. Service and process systems on Android seem to me more simple and straight forward.

@mirzemehdi
Copy link
Owner

@tamimattafi Thanks for detailed explanation. Look like very complicated thing :D. I need to research that for understanding better

@koushikvkk
Copy link

Hey Guys,
I am from android background. Currently working on a KMP project and am using KMPNotifier library in it. I don't understand your above conversations about iOS. Currently I am trying to send a data type message to my app. But It is not getting received inside onPayloadData method. Is that the known issue? Do we have any workaround for that?

@mirzemehdi
Copy link
Owner

@koushikvkk yeah this is currently issue, trying to work on that

@koushikvkk
Copy link

Ok. Thanks for the info @mirzemehdi.

@mirzemehdi
Copy link
Owner

@koushikvkk for receiving data type message in ios, I wrote here #54 (comment)

Basically main thing that is working for me to get data type only message in ios, is to add "badge" key to aps when sending notification

@Koushiktrimble
Copy link

Koushiktrimble commented Aug 6, 2024

Hey @mirzemehdi, Thanks for the info.
When I tried this, I could see data notification is getting received inside the onPayloadData callback only when the app is in foreground. When the app is in background, the data is not getting received. Do you also face the same issue?

@mirzemehdi
Copy link
Owner

@Koushiktrimble yes, and no. So here is what happens when app is in background. I think this is limitation from ios side, but not sure if someone can find more information let us know please.

  1. First case: Sending only data type message (app in background):
    In this case when app is in background, no listener is triggered.

  2. Second case: Sending both data and notification message (app in background)
    In this case, no listener is triggered, but notification is shown to the user. Then when user clicks the notification, data type message is received as well, and listener is also triggered.

  3. Third case: Sending only data type message (app in foreground)
    No issue, Listener is triggered, and data type message is received.

  4. Fourth case: Sending both data and notification message (app in foreground)
    No issue, Listeners are triggered, data type message is received, and notification is shown to the user.

@mirzemehdi
Copy link
Owner

so this is called silent notification (sending data type message only), and as I see from documentation this is not guaranteed to be received always in ios. That's why I always send both data and notification type message together.

@tamimattafi
Copy link
Author

@mirzemehdi Hello!
Data notifications are always received even if the app is in the background. The limitations are related to UI, you can't show anything from the background, only fetch data. So, data notifications should be something like silent or background notifications for data fetching or changing something in user cache. Never for UI.

If you want to guarantee that the user sees a notification (Display it on the UI), you should always send alerts with badge and sound properties. If you want to customize their text and style on the client side, you should implement Notification Service Extension, and add mutable-content: 1 to your aps payload.

Clicking on a notification should trigger delegate function didFinishLaunchingWithOptions, with a dictionary of data from the push notification you clicked, so you should solely rely on this when you handle clicks and never rely on the data notification.

@mirzemehdi
Copy link
Owner

@tamimattafi thank you for this info. Even printing something to the logs doesn't work. is this expected then?

@tamimattafi
Copy link
Author

tamimattafi commented Aug 7, 2024

@Koushiktrimble
I encountered a problem with logging data notifications from the background as well, so I assume logs are treated as UI and when the app is on the background, they are not processed or displayed.

@Louisnil-AS
Copy link

@mirzemehdi @tamimattafi
May i ask about iOS, is it possible to execute code like simply NSLog a message by this method (UNNotificationServiceExtension). Even the app is fully closed by user (app is killed). So by using UNNotificationServiceExtension I can finally execute code like storing the push message data into my phone even if the app is fully closed? like closed by user?

@tamimattafi
Copy link
Author

tamimattafi commented Aug 8, 2024

@Louisnil-AS You can write NSLog but it won't appear in your logs, because extensions run on a different process, you will need to switch your xcode logs process to see them.
You can download things like a picture from a URL even if the app is closed, so I assume you can store things to local storage too, but data fetching and storing is recommended using data type notifications and not alerts.

Check these docs: https://developer.apple.com/documentation/usernotifications/unnotificationserviceextension/

@Louisnil-AS
Copy link

@tamimattafi
Yes, because normally we can't run code when the app is killed. Like iOS doesn't allow that because of privacy issues...
Did you mean this Extension can solve the problem?

@tamimattafi
Copy link
Author

@Louisnil-AS Yes, the Extension runs and allows HTTP requests and local storage operations. But the time should not exceed few seconds, and the total memory consumption of this process should not exceed 24MB. So if you can live with these limitations, it's possible to do what you are looking for.

@tmdgh1592
Copy link

@mirzemehdi
hello!
Are you talking about an issue where the onPayloadData() function is not called in the background?
I am wondering if it is possible to have the onPayloadData() function be called when fcm is received in the background.

@mirzemehdi
Copy link
Owner

mirzemehdi commented Aug 18, 2024

@tmdgh1592 onPayloadData functions is called in background if it contains both notification and data type message. But if it contains data type message (silent message), then it is not called. But check @tamimattafi 's comment too: #24 (comment)

@tmdgh1592
Copy link

tmdgh1592 commented Aug 18, 2024

@mirzemehdi Thank you
I tried data + notification as you said.
If I include notification, it is true that push notifications are sent in the background, but the onPayloadData() function is still not called.

I tried it in postman

// url : https://fcm.googleapis.com/v1/projects/{projectId}/messages:send

{
  "message": {
    "token": "eCGO2Cbmf...",
    "data": {
      "customKey1":"...",
      "customKey2":"..."
    },
    "notification": {
        "title":"ddd",
        "body":"asdasa"
    }
  }
}

I checked your mentioned comments(#24) and tried including apns and badge instead of notification type,
In the same way, only push notifications are sent, and onPayloadData() is not called.
Is there perhaps a problem with the way I tried?

@mirzemehdi
Copy link
Owner

Correction to above request @tmdgh1592 would be adding apns -> .... badge also as below


        "message": {
            "token": device_id,
            "notification": {
                "title": "FCM Notification",
                 "body": "Notification from FCM"
            },
            "data": data,
            "apns": {
                "payload": {
                    "aps": {
                        "badge": 0,
                    },
                }
            }
        }
    

@tmdgh1592
Copy link

@mirzemehdi
Does device_id mean fcm token??

I tried this format and the result is still the same
I tried logging, but I dont see anything 🥲

@mirzemehdi
Copy link
Owner

@tmdgh1592 yes device_id is fcm token. in logs you will not see payload data when app is in background. But when you click notification, then after app comes foreground onPayloadData will be triggered and you will get data there

@tmdgh1592
Copy link

@mirzemehdi
I thought this was the case after reading the comments you left, but onPayloadData() is not called even when I click on the notification.
There is no problem at all when in the foreground, but this problem only occurs in the background.

@mirzemehdi
Copy link
Owner

@tmdgh1592 then make sure in ios
you call NotifierManager.onApplicationDidReceiveRemoteNotification(userInfo: userInfo) on application's didReceiveRemoteNotification method.

https://github.com/mirzemehdi/KMPNotifier?tab=readme-ov-file#ios

If you did that step too and all other steps in ReadMe, not sure what can be missing

@tmdgh1592
Copy link

tmdgh1592 commented Aug 18, 2024

@mirzemehdi
yes, I already called onApplicationDidReceiveRemoteNotification() and did steps in ReadMe

스크린샷 2024-08-18 오후 9 42 04

    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {
        NotifierManager.shared.onApplicationDidReceiveRemoteNotification(userInfo: userInfo)
        return UIBackgroundFetchResult.newData
    }
import SwiftUI
import ComposeApp
import FirebaseCore
import FirebaseMessaging

class AppDelegate: NSObject, UIApplicationDelegate {

    func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        NotifierInitializer.shared.onApplicationStart()
      
        return true
    }

    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        Messaging.messaging().apnsToken = deviceToken
    }
    
    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {
        NotifierManager.shared.onApplicationDidReceiveRemoteNotification(userInfo: userInfo)
        return UIBackgroundFetchResult.newData
    }
    
}

@main
struct iOSApp: App {
    
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

As of now, I don't really know...
thank you for your help

@tamimattafi
Copy link
Author

@tmdgh1592 Have you tried adding this flag? 'content-available': 1
Check this thread https://stackoverflow.com/questions/27777632/the-purpose-of-content-available-in-push-notification-json

@tmdgh1592
Copy link

tmdgh1592 commented Aug 19, 2024

@tamimattafi

I implemented the ability to display push notifications in onPayloadData().
If you set content_available:1, FCM is received and the badge is updated, so it seems that FCM is being received in the background.
However, I still don't see any push notifications or logs because onPayloadData() is not called.
And if you apply content-available, the push notification will not be displayed in the front either due to the silent effect.

If it's okay, can you show me the json format you use in your fcm?

I tried like below

{
  "message": {
    "token": "eSMzRHZ7A0XSlR8r-...",
    "data": data,
    "apns": {
      "payload": {
        "aps": {
            "content-available": 1,
            "badge":3,
        }
      }
    }
  }
}

@tamimattafi
Copy link
Author

@tmdgh1592 I don't use onPayloadData to show notifications, I use UNNotificationServiceExtension with 'mutable-content': 1, so my payload is irrelevant for your experiment :(

@tmdgh1592
Copy link

@tamimattafi Still, thank you for your helpful comment, bro.

@MahdiAsoudeh
Copy link

MahdiAsoudeh commented Nov 7, 2024

Hello Mr. @mirzemehdi
thanks a lot for this library, wonderful
but I have an error in iOS yet, please help me, god save you

2

1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants