urbanairship / airship-flutter

Flutter integration
Other
17 stars 18 forks source link

onPushReceived does run in background or terminated state in Iphone #200

Closed ManuelAccenture closed 4 months ago

ManuelAccenture commented 4 months 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?

airship_flutter: ^7.3.1

What are the versions of any relevant development tools you are using?

Ios

Report

What unexpected behavior are you seeing?

onPushReceived is not invoked when receive a notification when mobile is in background or terminated state.

What is the expected behavior?

onPushReceived should be invoked.

What are the steps to reproduce the unexpected behavior?

Setup onPushReceived and send a notification.

Do you have logging for the issue?

Log doesn't show any error or logs.

jyaganeh commented 4 months ago

Hi @ManuelAccenture,

Our push received listeners on Android work a little differently than on iOS, due to some platform restrictions in Flutter and Android. Background messages come through a separate callback, which can be wired up in your main.dart file like:

Future<void> backgroundMessageHandler(PushReceivedEvent event) async {
    // Handle the message
    log("--- backgroundMessageHandler: $event");
}

void main() {
    WidgetsFlutterBinding.ensureInitialized();

    // ... Airship takeOff and other Airship initialization ...

    Airship.push.android
        .setBackgroundPushReceivedHandler(backgroundMessageHandler);
}

On Android, incoming background message callbacks are handled by spawning an isolate, which allows your app to receive a callback when background notifications are received when the app is not running, but this comes with a couple of requirements/limitations:

ManuelAccenture commented 4 months ago

Hello, @jyaganeh , first, let me apologize, because a mix two questions.

The first issue I had was that setBackgroundPushReceivedHandler wasn't working well. The problem was the isolates, I could solve that.

And the second problem is that in IOS onPushReceived is not invoked when the app is in terminated or background state.

I was looking for something similar to setBackgroundPushReceivedHandler for IOS, but I found nothing.

How can I handle background message for IOS?

rlepinski commented 4 months ago

@ManuelAccenture When you swipe to close an iOS app it is no longer able to receive background push or wake up in the background until you manually open it again. Its basically like the force kill behavior on Android.

Assuming you let it terminate by restarting the phone or having it in the background until its terminated by the OS, or just in the background past the 30 second window, you should be able to wake up the app by sending a push with content_available: 1 set in the payload. Even then content available pushes are pretty unreliable and are heavily throttled.

The system treats background notifications as low priority: you can use them to refresh your app’s content, but the system doesn’t guarantee their delivery. In addition, the system may throttle the delivery of background notifications if the total number becomes excessive. The number of background notifications allowed by the system depends on current conditions, but don’t try to send more than two or three per hour.

https://developer.apple.com/documentation/usernotifications/pushing-background-updates-to-your-app

ManuelAccenture commented 4 months ago

Thank you so much for the help.

At least, I need to find other way to save notifications, since there was a lot problems with background notifications in release mode.

AnastasiiaSob commented 1 month ago

@ManuelAccenture have you found any solution for the problem?

AnastasiiaSob commented 1 month ago

Hi @rlepinski , We are facing the same problem at the moment. I need to find a way to access the push notifications' payload when our iOS App is in background/terminated. I don't have to process it immediately when it arrives on the device, I just need to ensure it gets processed at some point. I found a branch with a workaround , but as it is 2 years old, I assume it is not compatible with Airship 7.x.x... Is there any other reliable way to solve the problem, or might there be one in the near future? We are paying customers and the functionality is unforunately crucial for our app. I would be very glad if you could help out!

rlepinski commented 1 month ago

We merged that into main to workaround a flutter delegate issue - https://github.com/urbanairship/airship-flutter/blob/main/ios/Classes/SwiftAirshipPlugin.swift#L61, so it should be there.

What version of flutter and the plugin are you on? Can you verify you have remote-notification background mode capabilities enabled, that you are sending a content-available: 1 in the payload, and you are not killing the app through the app switcher when you test it?

I can check the latest flutter today/tomorrow to make sure its still working and report back.

AnastasiiaSob commented 1 month ago

@rlepinski Flutter 3.22.3 airship_flutter: ^7.6.0

✅ Remote notifications in background modes capabilities in Xcode is enabled

As I am sending push notifications from Airship, I set Background processing: Send content-available flag to device to wake app for processing in iOS Options.

I tested the app downloaded from TestFlight. If I don’t kill the app and just send it to the background, onPushReceived works fine. However, when the app is killed onPushReceived doesn’t work on iOS, and this is the problem.

rlepinski commented 1 month ago

However, when the app is killed onPushReceived doesn’t work on iOS, and this is the problem.

If you swipe to kill an iOS app it breaks this, its an OS behavior that we cant control. If the app is killed by the OS or if you restart your phone it should still work.

rlepinski commented 1 month ago

What is the use case?

If you are sending a visible push notification you could use a notification service extension and mark the push as mutable, but that will require native code since it runs in its own sandbox. To share data with the main app you would have to use shared app group

AnastasiiaSob commented 1 month ago

@rlepinski The use case is that we need to access and collect the payload of all push notifications and show them to the user in our frontend. It is a kind of custom message center in the app, but unfortunately, we can't use the Message Center from Airship. Is there another way to collect and/or access the push notifications for this purpose?

Could you please share an example or documentation of implementing a shared app group in the context of push notifications?

rlepinski commented 1 month ago

Here is a quick example on how you might do this.


final class SharedStore {
    static let shared: SharedStore = SharedStore()

    // update this line
    private let defaults = UserDefaults(suiteName: "group..<BUNDLE_ID>")!

    private static let notificationsKey = "notifications"

    var notifications: [[AnyHashable : Any]] {
        guard let notifications = defaults.object(forKey: Self.notificationsKey) as? [[AnyHashable : Any]] else {
            return []
        }
        return notifications
    }

    func addNotification(_ notification: [AnyHashable : Any]) {
        var notifications = self.notifications
        notifications.append(notification)
        defaults.set(notifications, forKey: Self.notificationsKey)
    }

    func clearNotifications() {
        defaults.removeObject(forKey: Self.notificationsKey)
    }

}
class NotificationService: UANotificationServiceExtension {
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        // process the request, store info in the shared app group
        SharedStore.shared.addNotification(request.content.userInfo)
        super.didReceive(request, withContentHandler: contentHandler)
    }
}

Then in your app, you should now be able to access the shared store. You will have to expose this to flutter in your app though.

When you send pushes, make sure this is checked: Screenshot 2024-08-07 at 3 13 16 PM

AnastasiiaSob commented 1 month ago

Thank you @rlepinski ! How exactly is it supposed to let us access the payload from push notifications that arrive when the iOS app is killed? Is it going to collect them and make the entire payload available as soon as the app starts?

rlepinski commented 1 month ago

The storage will collect the raw APNS payloads from the service and can be accessed on the native side with: SharedStore.shared.notifications. You will have to write a custom plugin to expose those to your app through flutter though. This is not something Airship will support natively for now.