Issue
When #8786 was merged, it broke the semantics of willPresent messaging completionHandler contract.
Before
if (_originalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
[_originalDelegate userNotificationCenter:center
willPresentNotification:notification
withCompletionHandler:completionHandler];
} else {
completionHandler(presentationOptions);
}
Now
if (_originalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
[_originalDelegate userNotificationCenter:center
willPresentNotification:notification
withCompletionHandler:completionHandler];
}
// Don't consume completionHandler before the _originalDelegate has been
// processed
completionHandler(presentationOptions);
The issue is that completionHandler is always called regardless of whether the original delegate calls the method.
This results in completionHandler being called twice in the case where the original app delegate overrides willPresentNotification.
The real issue for me is that this means that my willPresentNotification cannot do async work in the original delegate handler now to resolve notification options without blocking the main UI thread.
Example:
- Original delegate implements willPresentNotification and calls completionHandler to customize the response
- If the original delegate willPresentNotification callback is synchronous, the original delegate completionHandler call wins
- If the original delegate willPresentNotification is async, the default completion handler is called
- In both cases completionHandler is called twice which is incorrect
Proposals:
-
- If the original delegate implements willPresentNotification it must call completionHandler
-
- Wrap completion handler in a block, using
__block BOOL handled = false, e.g./
__block BOOL handled = NO;
void (^completionHandlerProxy)(UNNotificationPresentationOptions options) = ^{
if (handled == YES) {
return;
}
handled = YES;
completionHandler(options);
};
// async timeout to call
// 1. Define the delay in seconds
NSTimeInterval delayInSeconds = 3.0;
__weak typeof(self) weakSelf = self;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
if (_originalDelegate != nil && originalDelegateRespondsTo.willPresentNotification) {
[_originalDelegate userNotificationCenter:center
willPresentNotification:notification
withCompletionHandler:completionHandlerProxy];
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_after(popTime, dispatch_get_main_queue(), ^{
completionHandlerProxy(UNNotificationPresentationOptionsNone);
}
<!-- Please describe your issue here --^ and provide as much detail as you can. -->
<!-- Include code snippets that show your usages of the library in the context of your project. -->
<!-- Snippets that also show how and where the library is imported in JS are useful to debug issues relating to importing or methods not found issues -->
Describe your issue here
---
## Project Files
<!-- Provide the contents of key project files which will help to debug -->
<!-- For Example: -->
<!-- - iOS: `Podfile` contents. -->
<!-- - Android: `android/build.gradle` contents. -->
<!-- - Android: `android/app/build.gradle` contents. -->
<!-- - Android: `AndroidManifest.xml` contents. -->
<!-- ADD THE CONTENTS OF THE FILES IN THE PROVIDED CODE BLOCKS BELOW -->
### Javascript
<details><summary>Click To Expand</summary>
<p>
#### `package.json`:
```json
# N/A
firebase.json for react-native-firebase v6:
iOS
Click To Expand
ios/Podfile:
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")
# CocoaPods 1.16.2 uses xcodeproj 1.27.0, which can read objectVersion 70
# projects but lacks the compatibility label needed when creating Pods.xcodeproj.
unless Xcodeproj::Constants::COMPATIBILITY_VERSION_BY_OBJECT_VERSION.key?(70)
compatibility_versions = Xcodeproj::Constants::COMPATIBILITY_VERSION_BY_OBJECT_VERSION.merge(70 => 'Xcode 16.0')
Xcodeproj::Constants.send(:remove_const, :COMPATIBILITY_VERSION_BY_OBJECT_VERSION)
Xcodeproj::Constants.const_set(:COMPATIBILITY_VERSION_BY_OBJECT_VERSION, compatibility_versions.freeze)
end
require 'json'
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}
ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']
platform :ios, podfile_properties['ios.deploymentTarget'] || '15.1'
install! 'cocoapods',
:deterministic_uuids => false
prepare_react_native_project!
target 'legacyreactnativeexpo' do
use_expo_modules!
if ENV['EXPO_USE_COMMUNITY_AUTOLINKING'] == '1'
config_command = ['node', '-e', "process.argv=['', '', 'config'];require('@react-native-community/cli').run()"];
else
config_command = [
'node',
'--no-warnings',
'--eval',
'require(require.resolve(\'expo-modules-autolinking\', { paths: [require.resolve(\'expo/package.json\')] }))(process.argv.slice(1))',
'react-native-config',
'--json',
'--platform',
'ios'
]
end
config = use_native_modules!(config_command)
# FirebaseCoreInternal is Swift. When Firebase is integrated with static
# linkage, CocoaPods needs a module map for GoogleUtilities.
pod 'GoogleUtilities', :modular_headers => true
# use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
# use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']
use_frameworks! :linkage => :static
$RNFirebaseAsStaticFramework = true
use_react_native!(
:path => config[:reactNativePath],
:hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/..",
:privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
)
# target 'notificationservice' do
# inherit! :search_paths
# # Add only extension-safe pods here, if any.
# # Example:
# # pod 'Firebase/Messaging'
# end
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false,
:ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
)
# RCT-Folly's podspec sets these as compiler flags, but framework module
# builds also need them as target-level preprocessor definitions.
folly_definitions = %w[
FOLLY_NO_CONFIG
FOLLY_MOBILE=1
FOLLY_USE_LIBCPP=1
FOLLY_CFG_NO_COROUTINES=1
FOLLY_HAVE_CLOCK_GETTIME=1
FOLLY_HAVE_PTHREAD=1
]
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
definitions = config.build_settings['GCC_PREPROCESSOR_DEFINITIONS']
definitions = [definitions].compact if definitions.nil? || definitions.is_a?(String)
definitions = ['$(inherited)'] if definitions.empty?
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] =
(definitions + folly_definitions).uniq
end
end
Dir.glob(File.join(installer.sandbox.root.to_s, 'Target Support Files', '*', '*.xcconfig')).each do |xcconfig_path|
lines = File.readlines(xcconfig_path)
definition_index = lines.index { |line| line.start_with?('GCC_PREPROCESSOR_DEFINITIONS =') }
if definition_index
existing_definitions = lines[definition_index].split('=', 2).last.strip.split(/\s+/)
lines[definition_index] =
"GCC_PREPROCESSOR_DEFINITIONS = #{(existing_definitions + folly_definitions).uniq.join(' ')}\n"
else
lines << "GCC_PREPROCESSOR_DEFINITIONS = $(inherited) #{folly_definitions.join(' ')}\n"
end
File.write(xcconfig_path, lines.join)
end
# Xcode 26.4 / Apple Clang rejects fmt 11's consteval path. Keep the
# workaround scoped to fmt; React Native pods still need their C++ standard.
installer.pods_project.targets.each do |target|
next unless target.name == 'fmt'
target.build_configurations.each do |config|
config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
end
end
# This is necessary for Xcode 14, because it signs resource bundles by default
# when building for devices.
installer.target_installation_results.pod_target_installation_results
.each do |pod_name, target_installation_result|
target_installation_result.resource_bundle_targets.each do |resource_bundle_target|
resource_bundle_target.build_configurations.each do |config|
config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
end
end
end
end
end
AppDelegate.m:
#import "AppDelegate.h"
#import "ExpoModulesCore-Swift.h"
#include <Foundation/NSObjCRuntime.h>
#include <UserNotifications/UNUserNotificationCenter.h>
#import <UserNotifications/UserNotifications.h>
#include <objc/objc.h>
#import "legacyreactnativeexpo-Swift.h"
#import <FirebaseCore/FirebaseCore.h>
#import <FirebaseMessaging/FirebaseMessaging.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>
#import <React/RCTLog.h>
#import <Signotif/SignotifObjCBridge.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.moduleName = @"main";
self.pendingPresentationHandlers = [NSMutableDictionary new];
[FIRApp configure];
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
RCTLogInfo(@"[AppDelegate] Setup UNUserNotificationCenter and FIRApp");
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
return [super application:application
didFinishLaunchingWithOptions:launchOptions];
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
return [self bundleURL];
}
- (NSURL *)bundleURL {
#if DEBUG
return [[RCTBundleURLProvider sharedSettings]
jsBundleURLForBundleRoot:@".expo/.virtual-metro-entry"];
#else
return [[NSBundle mainBundle] URLForResource:@"main"
withExtension:@"jsbundle"];
#endif
}
// Linking API
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
options:
(NSDictionary<UIApplicationOpenURLOptionsKey, id> *)options {
BOOL handled;
RCTLogInfo(@"[AppDelegate] openURL %@ - %@", url, options);
handled = [RCTLinkingManager application:application
openURL:url
options:options];
if (!handled) {
return [super application:application openURL:url options:options];
}
return YES;
}
// Universal Links
- (BOOL)application:(UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:
(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> *_Nullable))
restorationHandler {
BOOL result = [RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
return [super application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler] ||
result;
}
// Explicitly define remote notification delegates to ensure compatibility
// with some third-party libraries
- (void)application:(UIApplication *)application
didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
RCTLogInfo(
@"[AppDelegate] didRegisterForRemoteNotificationsWithDeviceToken %@",
deviceToken);
[FIRMessaging messaging].APNSToken = deviceToken;
[super application:application
didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Explicitly define remote notification delegates to ensure compatibility
// with some third-party libraries
- (void)application:(UIApplication *)application
didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
RCTLogInfo(@"[AppDelegate] Failed to register for remote notifications %@",
error);
[super application:application
didFailToRegisterForRemoteNotificationsWithError:error];
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
withCompletionHandler:(void (^)(void))completionHandler {
RCTLogInfo(@"[AppDelegate] Received notification response %@", response);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
willPresentNotification:(UNNotification *)notification
withCompletionHandler:
(void (^)(UNNotificationPresentationOptions options))
completionHandler {
RCTLogInfo(@"[AppDelegate] Received notification willPresent %@",
notification);
// UUID(void (^)(UNNotificationPresentationOptions options))
// [SignotifObjCBridge handleWillPresentNotification:notification
// completionHandler:completionHandler];
}
// Explicitly define remote notification delegates to ensure compatibility
// with some third-party libraries
- (void)application:(UIApplication *)application
didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:
(void (^)(UIBackgroundFetchResult))completionHandler {
RCTLogInfo(@"[AppDelegate] Received remote notification %@", userInfo);
// completionHandler(UIBackgroundFetchResultNoData);
return [super application:application
didReceiveRemoteNotification:userInfo
fetchCompletionHandler:completionHandler];
}
@end
Android
Click To Expand
Have you converted to AndroidX?
android/build.gradle:
android/app/build.gradle:
android/settings.gradle:
MainApplication.java:
AndroidManifest.xml:
Environment
Click To Expand
react-native info output:
System:
OS: macOS 26.3.1
CPU: (11) arm64 Apple M3 Pro
Memory: 105.58 MB / 36.00 GB
Shell:
version: "5.9"
path: /bin/zsh
Binaries:
Node:
version: 20.19.2
path: /private/var/folders/6q/rsr9ppvj54d_7_zlk3tx_7n40000gn/T/xfs-b165d19a/node
Yarn:
version: 4.11.0
path: /private/var/folders/6q/rsr9ppvj54d_7_zlk3tx_7n40000gn/T/xfs-b165d19a/yarn
npm:
version: 10.8.2
path: /Users/jd/.asdf/plugins/nodejs/shims/npm
Watchman: Not Found
Managers:
CocoaPods:
version: 1.16.2
path: /Users/jd/.asdf/shims/pod
SDKs:
iOS SDK:
Platforms:
- DriverKit 25.4
- iOS 26.4
- macOS 26.4
- tvOS 26.4
- visionOS 26.4
- watchOS 26.4
Android SDK:
API Levels:
- "29"
- "31"
- "33"
- "34"
- "35"
Build Tools:
- 30.0.3
- 31.0.0
- 33.0.0
- 34.0.0
- 35.0.0
- 36.1.0
System Images:
- android-33 | Google APIs ARM 64 v8a
- android-34 | Google APIs ARM 64 v8a
- android-35 | Google Play ARM 64 v8a
- android-35 | Pre-Release 16 KB Page Size Google APIs ARM 64 v8a
- android-37.0 | Pre-Release 16 KB Page Size Google Play ARM 64 v8a
- android-VanillaIceCream | Google Play ARM 64 v8a
- android-VanillaIceCream | Pre-Release 16 KB Page Size Google APIs ARM 64
v8a
Android NDK: Not Found
IDEs:
Android Studio: 2025.3 AI-253.30387.90.2532.14935130
Xcode:
version: 26.4/17E192
path: /usr/bin/xcodebuild
Languages:
Java:
version: 17.0.14
path: /opt/homebrew/opt/openjdk@17/bin/javac
Ruby:
version: 3.1.0
path: /Users/jd/.asdf/shims/ruby
npmPackages:
"@react-native-community/cli":
installed: 20.1.3
wanted: ^20.1.3
react:
installed: 18.3.1
wanted: 18.3.1
react-native:
installed: 0.77.3
wanted: 0.77.3
react-native-macos: Not Found
npmGlobalPackages:
"*react-native*": Not Found
Android:
hermesEnabled: true
newArchEnabled: false
iOS:
hermesEnabled: true
newArchEnabled: false
- Platform that you're experiencing the issue on:
react-native-firebase version you're using that has this issue:
Firebase module(s) you're using that has the issue:
- Are you using
TypeScript?
Issue
When #8786 was merged, it broke the semantics of willPresent messaging completionHandler contract.
Before
Now
The issue is that
completionHandleris always called regardless of whether the original delegate calls the method.This results in
completionHandlerbeing called twice in the case where the original app delegate overrideswillPresentNotification.The real issue for me is that this means that my
willPresentNotificationcannot do async work in the original delegate handler now to resolve notification options without blocking the main UI thread.Example:
Proposals:
__block BOOL handled = false, e.g./firebase.jsonfor react-native-firebase v6:# N/AiOS
Click To Expand
ios/Podfile:AppDelegate.m:Android
Click To Expand
Have you converted to AndroidX?
android/gradle.settingsjetifier=truefor Android compatibility?jetifierfor react-native compatibility?android/build.gradle:// N/Aandroid/app/build.gradle:// N/Aandroid/settings.gradle:// N/AMainApplication.java:// N/AAndroidManifest.xml:<!-- N/A -->Environment
Click To Expand
react-native infooutput:react-native-firebaseversion you're using that has this issue:e.g. 5.4.3Firebasemodule(s) you're using that has the issue:e.g. Instance IDTypeScript?Y/N&VERSIONReact Native FirebaseandInvertaseon Twitter for updates on the library.