Skip to content

Commit

Permalink
introduce property on RCTPushNotificationManager to hold notification…
Browse files Browse the repository at this point in the history
… that launched the app (#42628)

Summary:
Pull Request resolved: #42628

Changelog: [iOS][Deprecated] Retrieving initial notification requires UNUserNotificationCenterDelegate setup instead of reading UIApplicationLaunchOptionsLocalNotificationKey

# how to migrate:

if you are currently using `getInitialNotification` to check the notification on an app start (warm or cold), you will need to do a migration.

have an object become the delegate of `UNUserNotificationCenterDelegate`. you can use your app's `AppDelegate` to do this.

then, override `userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:`. in the implementation, add these lines:

  if ([response.actionIdentifier isEqualToString:UNNotificationDefaultActionIdentifier]) {
    id module = _reactHost ? [[_reactHost getModuleRegistry] moduleForName:"PushNotificationManager"]
                           : [_bridge moduleForClass:[RCTPushNotificationManager class]];

    if ([module isKindOfClass:[RCTPushNotificationManager class]]) {
      RCTPushNotificationManager *pushNotificationManager = (RCTPushNotificationManager *)module;
      pushNotificationManager.initialLocalNotification = response.notification;
    }
  }

# reasoning:

when you start an app from a push notification, the `UIApplicationLaunchOptionsLocalNotificationKey` in `application:didFinishLaunchingWithOptions:` will contain the notification metadata that started the app. additionally, this was stored on the bridge, which is not compatible with the new architecture. however, `UIApplicationLaunchOptionsLocalNotificationKey` has been deprecated since iOS 10, which this PR aims to address.

apple's supported API to do this is `userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:` which is on `UNUserNotificationCenterDelegate`. this is a significant change from a pull to push model. in order to make this change without having to rearchitect the product layer, we're going to store the notification on the notification native module.

another caveat is that the API follows the delegation pattern, not the listener pattern. that means we can't make our notificaiton native module the delegate here - the app will probably need `UNUserNotificationCenterDelegate` to be a top level object - usually the scope of the app delegate.

in future, i actually think we need to unify this with `handleLocalNotificationReceived:`, but right now there's forked handling between platforms and some product code is only using the pull model, so this is still the minimum change in the product layer.

Reviewed By: ingridwang

Differential Revision: D52897071

fbshipit-source-id: 579578d1b3128c5f7e81249c75cf7655b8e360e2
  • Loading branch information
philIip authored and facebook-github-bot committed Jan 26, 2024
1 parent 8c4979e commit c8ab44c
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ extern NSString *const RCTRemoteNotificationReceived;

@interface RCTPushNotificationManager : RCTEventEmitter

@property (nonatomic, nullable, readwrite) UNNotification *initialNotification;

typedef void (^RCTRemoteNotificationCallback)(UIBackgroundFetchResult result);

+ (void)didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,11 @@ @implementation RCTPushNotificationManager
return [formatter stringFromDate:date];
}

static BOOL IsNotificationRemote(UNNotification *notification)
{
return [notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]];
}

RCT_EXPORT_MODULE()

- (dispatch_queue_t)methodQueue
Expand Down Expand Up @@ -232,7 +237,7 @@ + (void)didFailToRegisterForRemoteNotificationsWithError:(NSError *)error

+ (void)didReceiveNotification:(UNNotification *)notification
{
BOOL const isRemoteNotification = [notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]];
BOOL const isRemoteNotification = IsNotificationRemote(notification);
if (isRemoteNotification) {
NSDictionary *userInfo = @{@"notification" : notification.request.content.userInfo};
[[NSNotificationCenter defaultCenter] postNotificationName:RCTRemoteNotificationReceived
Expand Down Expand Up @@ -524,20 +529,44 @@ - (void)handleRemoteNotificationRegistrationError:(NSNotification *)notification
: (RCTPromiseResolveBlock)resolve reject
: (__unused RCTPromiseRejectBlock)reject)
{
NSMutableDictionary<NSString *, id> *initialNotification =
// The user actioned a local or remote notification to launch the app. Notification is represented by UNNotification.
// Set this property in the implementation of
// userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler.
if (self.initialNotification) {
NSDictionary<NSString *, id> *notificationDict =
RCTFormatUNNotificationContent(self.initialNotification.request.content);
if (IsNotificationRemote(self.initialNotification)) {
NSMutableDictionary<NSString *, id> *notificationDictCopy = [notificationDict mutableCopy];
notificationDictCopy[@"remote"] = @YES;
resolve(notificationDictCopy);
} else {
resolve(notificationDict);
}
return;
}

NSMutableDictionary<NSString *, id> *initialRemoteNotification =
[self.bridge.launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey] mutableCopy];

// The user actioned a remote notification to launch the app. This is a fallback that is deprecated
// in the new architecture.
if (initialRemoteNotification) {
initialRemoteNotification[@"remote"] = @YES;
resolve(initialRemoteNotification);
return;
}

UILocalNotification *initialLocalNotification =
self.bridge.launchOptions[UIApplicationLaunchOptionsLocalNotificationKey];

if (initialNotification) {
initialNotification[@"remote"] = @YES;
resolve(initialNotification);
} else if (initialLocalNotification) {
// The user actioned a local notification to launch the app. Notification is represented by UILocalNotification. This
// is deprecated.
if (initialLocalNotification) {
resolve(RCTFormatLocalNotification(initialLocalNotification));
} else {
resolve((id)kCFNull);
return;
}

resolve((id)kCFNull);
}

RCT_EXPORT_METHOD(getScheduledLocalNotifications : (RCTResponseSenderBlock)callback)
Expand Down

0 comments on commit c8ab44c

Please sign in to comment.