3

First of all, just to clarify...

I have done quite some searchings and readings on the existing sources (medium, stackoverflow, apple developer forum, etc.) before asking this "duplicate question" for confirming and concluding my knowledge is correct.

  1. Will iOS launch my app into the background if it was force-quit by the user?
  2. How to get the push notification payload when force-quit / swipe up to kill the iOS app without tapping on the banner/alert?
  3. https://medium.com/fenrir-inc/handling-ios-push-notifications-the-not-so-apparent-side-420891ddf10b
  4. Handling Push Notifications when App is Terminated
  5. https://developer.apple.com/forums/thread/62005#:~:text=In%20most%20cases%2C%20the%20system,force%20quit%20by%20the%20user.

Before we begin, just to put the terms in very precise manner. Here's what I'm referring when I mention

  1. Foreground - App is Active and running, user is basically interacting with the application
  2. Background - User tapped on home button after interacting. App remains in background, user can double tap on home button and find the app from App Switcher.
  3. Quit - App is actually in background, but it was terminated by the System itself.
  4. Kill - App is no longer in background, user double tap on home button and SWIPE the app away from App Switcher.

Use case i'm trying to tackle

App receive push notification in BACKGROUND, QUIT & KILL states then perform certain background actions (updating Application Badge + storing the Notification in device)

  1. For background - Yes I've managed to achieve this by having content-available = 1 sent together in APNS payload. Notification banner appear, background action executed!
  2. For QUIT - Yes I've managed to achieve this by having content-available = 1 sent together in APNS payload. Notification banner appear, background action executed!
  3. For KILL - Notification banner appear, BUT background action is not trigger.

My questions

  1. Whenever app is kill, it's not possible to awake the app to perform any background action when notification is receive?
  2. How does it works for messaging app like Whatsapp?
  3. How should I handle my cases when app is force quit?
  • ONLY if user tapped notification banner, my app gets to run those background actions (Increase badge count + storing the data).

  • Otherwise, if user choose to tap on App Icon to open my app. The pushed notification won't exist in my app at all, including badge count is not increase.

Below are what I've tested with Whatsapp

Background State

  1. Open Whatsapp, tap Home button (keep app in background)
  2. Send a text message in device, banner notification appear
  3. Open app by tapping app icon, message is there in app.

FORCE QUIT State

  1. Open Whatsapp, double tap Home button, swipe app away
  2. Send a text message in device, banner notification appear.
  3. Open app by tapping app icon, message is there in app.

FORCE QUIT State + WiFi & Cellular data turned OFF

  1. Open Whatsapp, double tap Home button, swipe app away
  2. Send a text message in device, banner notification appear
  3. Turn off WiFi & Cellular data is off. (Confirm and tried to access website via Safari)
  4. Open app by tapping app icon, message is there in app.

The tests with Whatsapp, basically concludes that it's possible to have your app awake to perform background actions (Especially with the case of FORCE QUIT State + WiFi & Cellular data turned OFF)

The only "explanation" I'm able to explain myself is, they are using PushKit notifications Framework instead of User Notification Framework.

Update 3 Nov - iOS 13 wakes app even in KILL state

Apparently on iOS 13, like what @hubsi has mentioned down at the comment as well as some comments from Apple forum. iOS 13 does wakes my app even the app was manually killed by the user.

Tommy Leong
  • 2,509
  • 6
  • 30
  • 54

2 Answers2

2

Update swift 5.x Plus iOS 14.x

    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            self.deplinkNotification(userInfo: response.notification.request.content.userInfo )
               }
}

Update: In addition to what's mentioned below, it seems that as of iOS 13 apps get actually launched when a push notification is received in killed state. I found that in order to have the didReceiveRemoteNotification:fetchCompletionHandler delegate to trigger, the app needs to launch the registerForRemoteNotifications (UIApplication) method asap when launched (e.g. in didFinishLaunchingWithOptions). In case of React Native, opposed to what's stated below, you can actually run JS code. Though you have to wait for RN to have launched before running code, e.g. like this:

    public class func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            // some RN bridged method/event
        }
    }

Old Answer:

There are two ways to have code run when a push notifications is received on iOS with the app being killed:

  1. by using the PushKit framework
    note: Apple allows the PushKit framework only to be used for VoIP and watchOS applications, so using it without actually having that requirement your app will probably not make it through Apple's review
  2. by using a Notification Service Extension
    Using this extension enables you to run (headless) code in background, including "enriching" the notification, e.g. with an image attachment

As for WhatsApp I'm pretty sure they're using PushKit (they require it anyway for incoming calls). Though I think what you described in your last test would also be possible using a Notification Service Extension:
In the extension you can process the pushed notification including its payload and write data (e.g. the chat message) to your local app storage (e.g. shared with your main app through App Groups). When the app gets opened it reads that data from storage (chat message appears without data network).

Since the question is tagged with "react-native": Please note that to my knowledge it's not possible to invoke RN bridged JavaScript code in a Notification Service Extension. You'd have to write native (Swift/Obj-C) code.

Mashhadi
  • 3,004
  • 3
  • 46
  • 80
hubsi
  • 69
  • 6
  • Thanks for responding with suggestions. I'm not entirely sure whether Notification Service Extension + App Groups will be helpful here, though I came across another person who told me the same. To my knowledge from what I've read so far, almost all of the contents are saying the app don't get awake again after MANUALLY KILLED by user. Appreciate if you have any links to share me for read up? – Tommy Leong Oct 29 '20 at 14:17
  • If by "waking the app" you mean that your killed main app will be launched and put into background mode, that's right, you cannot do this (_the system does not automatically launch your app if the user has force-quit it._ [link](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623013-application)). You can only execute some extra code (you can use the extension as a mini-headless-app) for max. 30 seconds and that's afaik even only possible with a non-silent push notification. Though things might have [changed again](https://developer.apple.com/forums/thread/132191)... – hubsi Oct 29 '20 at 19:43
  • Yup, that's exactly what I meant. User killed the app, but we can't "wake the app" simply that's how the OS is designed. With this design, it seems we should make use of "badge" key return from APNS payload. For app that does badge count on application side will not be able to update badge count upon receiving notification when app was MANUALLY KILLED. Anyway, do you mean having the "extension", we are able to execute some extra code even the app was MANUALLY KILLED? – Tommy Leong Oct 30 '20 at 05:57
  • yes, the extension is always executed and you can set the badge count from it. Though I think you cannot get the current value from within the extension (to increment). But you can workaround that by storing the current count in userdefaults (shared via app group) whenever you set the badge – hubsi Oct 30 '20 at 10:39
  • @TommyLeong you added that iOS 13 would wake your app. I tested myself by running code in didReceiveRemoteNotification:fetchCompletionHandler, which was not executed. Can you please share details in how far it woke your app (e.g. what was executed and did it appear in "app switcher" as background app afterwards) and the payload you sent to APNs? Thanks! – hubsi Nov 05 '20 at 08:46
  • First of all, I'm on React Native development. I have my project integrated with RNFirebase. Within the Firebase lib, there's a function `messaging().setBackgroundMessageHandler( remoteMessage => `. This is the method that's getting call while app is quit and notification comes in. Your APNS should also include `content-available = 1` – Tommy Leong Nov 05 '20 at 11:14
  • I however can't verify if the delegate method `didReceiveRemoteNotification:fetchCompletionHandler` was being called when app is killed and banner is received.. because we lost the access to Xcode for debugging since our app is killed. – Tommy Leong Nov 05 '20 at 11:17
  • Hi @TommyLeong, you might need to log the process in a file then trigger the push notification and download the log (send to email or somewhere else) to checkout what methods have been called and what's not. – Anthonius Dec 16 '20 at 08:44
  • btw do you have any idea @hubsi how to execute some code when we get silent push notif? I've checked the link you gave but I'm in iOS 13.3 but my app is not triggered when it's force-quit which means the push notif is good as gone when app is not in active / background :( – Anthonius Dec 16 '20 at 08:45
  • @Anthonius Actually, I think I [found a reason](https://github.com/react-native-push-notification-ios/push-notification-ios/issues/235) why the app gets triggered for some and for some doesn't. If you're using react native, you also need to wait for a few seconds for the RN app to have started before emitting an event. – hubsi Dec 16 '20 at 12:16
  • sorry @hubsi, I didn't see the tag for react-native. I'm using swiftUI 100% :D And silent push notif can't wake app if app is already killed by user :( – Anthonius Dec 17 '20 at 16:38
  • @Anthonius Most statements here refer to native code only. There are just some additional hints on how to get it working for React Native. – hubsi Dec 17 '20 at 17:15
  • Noted @hubsi, if sometime in the future there's any hint/solution regarding this issue please ping me. I still can't find the workaround in SwiftUI – Anthonius Dec 18 '20 at 08:28
0

This is working for us for both iOS and Android even when the app is explicitly killed by the user.

Using-

  1. https://github.com/zo0r/react-native-push-notification
  2. https://github.com/react-native-push-notification-ios/push-notification-ios (do the complete setup required for these two packages)

APN Request

$url = "https://api.sandbox.push.apple.com/3/device/<device_token>";
$headers = array(
    "apns-push-type: voip",
    "apns-expiration: 10",
    "apns-topic: com.example.app.voip", // .voip as suffix to bundleID
    "apns-collapse-id: lcall", 
    "Content-Type: application/x-www-form-urlencoded",
);
$certificate_file = config('pushnotification.apn.certificate');
$payloadArray['aps'] = [
    'alert' => [
        'title' => "Calling title",
        'body' => "Calling body",
    ],
    'badge' => 1,
    "content-available" => 1
];
$data = json_encode($payloadArray);
$client = new Client();
$response = $client->post($url, [
    'headers' => $headers,
    'cert' => $certificate_file,
    'curl' => [
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_2_0,
    ],
    'body'=> $data,
]);

Read official for apns-push-type- https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/sending_notification_requests_to_apns

FCM Request

$url = "https://fcm.googleapis.com/fcm/send";
$headers = array(
    'Authorization' => 'key=<fcm_server_key',
    'Content-Type' => 'application/json'
);
$payloadArray = [
    "to" => "<device_token>",
    "data" => [
        "title"=> "Calling Title",
        "body" => "Calling Body",
    ]
];
$data = json_encode($payloadArray);
$client = new Client();
$response = $client->post($url, [
    'headers' => $headers,
    'curl' => [
        CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
    ],
    'body'=> $data,
]);
Deepak Panwar
  • 160
  • 10