react-native-push-notification / ios

React Native Push Notification API for iOS.
MIT License
741 stars 285 forks source link

Possible fix for receiving silent notification when app is killed (>=iOS13?) #235

Open vanso-hubsi opened 3 years ago

vanso-hubsi commented 3 years ago

Hi,

As it seems starting from iOS 13 (didn't test older versions) it's possible to receive a silent push notification when the app is killed by the user (swiped away). See e.g. here and here.

When I tested my React Native app with the general react-native-push-notification module using this module for iOS, I couldn't make it work. When I tested with RNFirebase, though, my app was woken up and run. So, I started digging ...

While I found that for both app variants my app was launched in background somehow, I observed that for my Firebase app the didReceiveRemoteNotification delegate method was run, while it wasn't triggered at all for the push-notification-ios app. With a lot of digging I then found that the issue seems to be in the call of the iOS method

registerForRemoteNotifications (UIApplication)

As it seems this method must be run before iOS starts processing the (while-app-was-killled-)received notification. With this module here that method is called long after the application started, i.e. when the app requests for push permissions. This is probably long after the received push notification is processed. iOS then probably thinks the app is not push enabled and thus doesn't trigger the delegate method.

I learned that the firebase module has this method called on every UIApplicationDidFinishLaunchingNotification event (see rnfirebase source code). So, as a test I added the line

[[UIApplication sharedApplication] registerForRemoteNotifications];

to the didFinishLaunchingWithOptions in my AppDelegate.m and Hooray! the delegate method was triggered when my killed app received a silent push notification! It didn't run the event handlers up to my Javascript code yet, but I'm sure this can also be fixed somehow.

Note: From my research it seems all this is only possible with iOS 13 and up, unfortunately I don't have an iOS <13 device here to test with and confirm.

Note2: Since I'm actually not a Native (Objective-C/Swift) programmer, I didn't create a pull request or similar. I thought it's better to have the experts here verify my statements and come up with the best approach.

krozett commented 3 years ago

@vanso-hubsi Could you please paste your entire AppDelegate.m file? I'm not sure where to add this line, but I tried right above [[FBSDKApplicationDelegate sharedInstance] application:application didFinishLaunchingWithOptions:launchOptions]; and it's still not receiving any notifications while killed. Also want to make sure my other functions are in sync with yours.

vanso-hubsi commented 3 years ago

Hi @krozett, Unfortunately, I don't have a clean and tested AppDelegate.m anymore, but I tried to collect what's required to get this working.

First, you have to add this to the didFinishLaunchingWithOptions function in your AppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // ...
  // add the following line
  [[UIApplication sharedApplication] registerForRemoteNotifications];
  // ...
  return YES;
}

With this present, the method didReceiveRemoteNotification in that AppDelegate.m will be triggered when the app is woken from killed state by a push notification. To actually receive the event in your application, you'll have to wait until your RN app is launched. I found this documented snippet, though it's written in Swift (it's from a module I outsourced this all to), you'd have to convert it to Objective-C yourself:

    @objc public class func didReceiveRemoteNotification(_ userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {

        var delay = 0.0;
        if UIApplication.shared.applicationState == .background {
            // delay execution to give app time to start up when killed
            // this enables us to receive and process silent push notification in JS when the app has been killed
            delay = 5;
            // TODO: currently ".background" is true for both, when the app was killed or in background. maybe find a way to detect if it was killed
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
            RNCPushNotificationIOS.didReceiveRemoteNotification(userInfo, fetchCompletionHandler:completionHandler)
        }
    }

If I remember correctly, the Google Firebase code waits 8 seconds, while I found 5 to be sufficient for my app.

I hope that helps, it's just off the top of my head.

krozett commented 3 years ago

Thanks @vanso-hubsi ! Actually I tried removing react-native-splash-screen and that solved the issue for me. (https://github.com/react-native-push-notification-ios/push-notification-ios/issues/252#issuecomment-789619802)

Although something still isn't working 100% correctly, since some testers on my team are able to receive them but not others. 😕

krozett commented 3 years ago

Hi @vanso-hubsi , I'm going back to your solution to try to figure out why some of my testers can't get background/killed silent pushes.

However, there's something wrong with this line: [[UIApplication sharedApplication] registerForRemoteNotifications];

It causes my app to not receive a token at all.

So unless you have some other ideas, this library is a no-go for me. I'm doing a minimal testing app, on the latest RN, so maybe I'll file a bug report. But I'm already looking at switching to Firebase for this. It's just too unreliable.

gillbeits commented 2 years ago

Hi! Any new solutions?