Skip to content

[🐛] FirebaseMessaging(iOS): completionHandler called before originalDelegate in willPresentNotification #8754

@WontakKim

Description

@WontakKim

Issue

When implementing custom foreground notification presentation logic in AppDelegate, the completionHandler is already consumed by RNFBMessagingUNUserNotificationCenter before the original delegate can handle it. This prevents custom logic from controlling notification display behavior.

In RNFBMessaging+UNUserNotificationCenter.m, when a notification with gcm.message_id is received in the foreground, the code calls completionHandler before forwarding to the original delegate:

if (notification.request.content.userInfo[@"gcm.message_id"]) {
    // ... event handling
    completionHandler(presentationOptions);  // Called first
}

if (_originalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
    [_originalDelegate userNotificationCenter:center
                      willPresentNotification:notification
                        withCompletionHandler:completionHandler];  // Already consumed
}

Since completionHandler can only be called once, any custom presentation logic in the original delegate is ignored.

The original delegate should be called first to allow custom notification presentation logic, then fallback to the library's default behavior if no delegate is set.

Example

// AppDelegate.m
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
    // Custom logic to determine whether to show notification
    if (shouldShowNotification) {
        if (@available(iOS 14.0, *)) {
            completionHandler(UNNotificationPresentationOptionList | 
                            UNNotificationPresentationOptionBanner | 
                            UNNotificationPresentationOptionBadge);
        } else {
            completionHandler(UNNotificationPresentationOptionAlert);
        }
    } else {
        completionHandler(UNNotificationPresentationOptionNone);
    }
}

This code has no effect because RNFBMessaging already called completionHandler.

Proposed Solution (SUDO)

Reverse the order: call the original delegate first, and only use the default behavior if no delegate is set:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
  
  // Calculate default presentation options
  NSArray *presentationOptionsConfig = [[RNFBJSON shared] getArrayValue:@"messaging_ios_foreground_presentation_options" defaultValue:@[]];
  UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionNone;
  // ... existing presentation options logic
  
  // Send messaging event if FCM message
  if (notification.request.content.userInfo[@"gcm.message_id"]) {
    NSDictionary *notificationDict = [RNFBMessagingSerializer notificationToDict:notification];
    if (!notificationDict[@"contentAvailable"]) {
      [[RNFBRCTEventEmitter shared] sendEventWithName:@"messaging_message_received"
                                                 body:notificationDict];
    }
  }
  
  // Let original delegate handle presentation first
  if (_originalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
    [_originalDelegate userNotificationCenter:center
                      willPresentNotification:notification
                        withCompletionHandler:completionHandler];
  } else {
    // Use default behavior if no delegate
    completionHandler(presentationOptions);
  }
}

This allows developers to implement custom foreground notification logic while maintaining backward compatibility.


Project Files

Javascript

Click To Expand

package.json:

# N/A

firebase.json for react-native-firebase v6:

# N/A

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
# N/A

AppDelegate.m:

// N/A


Android

Click To Expand

Have you converted to AndroidX?

  • my application is an AndroidX application?
  • I am using android/gradle.settings jetifier=true for Android compatibility?
  • I am using the NPM package jetifier for react-native compatibility?

android/build.gradle:

// N/A

android/app/build.gradle:

// N/A

android/settings.gradle:

// N/A

MainApplication.java:

// N/A

AndroidManifest.xml:

<!-- N/A -->


Environment

Click To Expand

react-native info output:

 OUTPUT GOES HERE
  • Platform that you're experiencing the issue on:
    • iOS
    • Android
    • iOS but have not tested behavior on Android
    • Android but have not tested behavior on iOS
    • Both
  • react-native-firebase version you're using that has this issue:
    • e.g. 5.4.3
  • Firebase module(s) you're using that has the issue:
    • e.g. Instance ID
  • Are you using TypeScript?
    • Y/N & VERSION


Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions