Skip to content

[🐛] setBackgroundMessageHandler does not get called for sometime after Killing the app #8654

Open

Description

@rawatmanoj

Issue

setBackgroundMessageHandler is not getting called for sometime after I kill the app background in Android. After some time I get the notification and further notifications are coming fine. But if I kill the app again it will take time again to get notification.

import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';
import messaging from '@react-native-firebase/messaging';

messaging().setBackgroundMessageHandler(async remoteMessage => {
  console.log('Message handled in the background!', remoteMessage);
  // handleBackgroundNotifcation(remoteMessage);
  pushLocalNotification(remoteMessage);
});

if (__DEV__) {
  require('./ReactotronConfig');
}
AppRegistry.registerComponent(appName, () => App);

Project Files

Javascript

Click To Expand

package.json:

    "@react-native-firebase/analytics": "^21.10.1",
    "@react-native-firebase/app": "^21.10.1",
    "@react-native-firebase/app-check": "^21.10.1",
    "@react-native-firebase/crashlytics": "^21.10.1",
    "@react-native-firebase/dynamic-links": "^21.10.1",
    "@react-native-firebase/messaging": "^21.10.1",
    "@react-native-firebase/perf": "^21.10.1",
    "@react-native-firebase/remote-config": "^21.10.1",

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:
require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
# Resolve react_native_pods.rb with node to allow for hoisting
require Pod::Executable.execute_command('node', ['-p',
  'require.resolve(
    "react-native/scripts/react_native_pods.rb",
    {paths: [process.argv[1]]},
  )', __dir__]).strip

platform :ios, min_ios_version_supported
prepare_react_native_project!

linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
  Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
  use_frameworks! :linkage => linkage.to_sym
end



target 'MashreqMobileApp' 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

  # Read APP_ID from ../.app.build
  def read_app_id
    env_file = File.expand_path('../.app.build', __dir__)
    if File.exist?(env_file)
      File.foreach(env_file) do |line|
        if line =~ /^APP_ID=(.*)/
          return $1.strip
        end
      end
    end
    nil
  end

  config = use_native_modules!(config_command)

  pod 'FirebaseCoreInternal', :modular_headers => true
  pod 'Firebase', :modular_headers => true
  pod 'FirebaseCore', :modular_headers => true
  pod 'GoogleUtilities', :modular_headers => true
  pod 'FirebaseCoreExtension', :modular_headers => true
  pod 'FirebaseCrashlytics', :modular_headers => true
  pod 'FirebaseInstallations', :modular_headers => true
  pod 'GoogleDataTransport', :modular_headers => true
  pod 'nanopb', :modular_headers => true
  pod 'Firebase/Crashlytics'
  pod 'react-native-google-maps', :path => '../node_modules/react-native-maps'
  pod 'FirebaseRemoteConfig', :modular_headers => true
  pod 'FirebaseABTesting', :modular_headers => true
  pod 'RNSecureStorage', :path => '../node_modules/rn-secure-storage/ios' # After the upgrade to RN 0.76 this is required to link the library correctly
  pod 'TrustKit', :modular_headers => true

  app_id = read_app_id
  if app_id.nil? || app_id.empty? || app_id == 'MASHREQ_PAK'
    pod 'mashreq-rn-covalent', :path => '../node_modules/@mashreq/mashreq-rn-covalent'
  end

  use_react_native!(
    :path => config[:reactNativePath],
    :hermes_enabled => true,
    :fabric_enabled => false,
    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/.."
  )

  post_install do |installer|
    # https://github.com/facebook/react-native/blob/main/packages/react-native/scripts/react_native_pods.rb#L197-L202
    react_native_post_install(
      installer,
      config[:reactNativePath],
      :mac_catalyst_enabled => false,
      :ccache_enabled => ENV['TF_BUILD'] == 'True'
    )
    installer.pods_project.targets.each do |t|
      if t.name.start_with?("AEP")
        t.build_configurations.each do |bc|
            bc.build_settings['OTHER_SWIFT_FLAGS'] = '$(inherited) -no-verify-emitted-module-interface'
        end
      end
    end

    # TODO: Ask KYC team to remove bitcode from the EFR lib and remove the step below
    bitcode_strip_path = `xcrun --find bitcode_strip`.chop!

    def strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
      framework_path = File.join(Dir.pwd, framework_relative_path)
      command = "#{bitcode_strip_path} #{framework_path} -r -o #{framework_path}"
      puts "Stripping bitcode: #{command}"
      system(command)
    end

    framework_paths = [
      "../node_modules/@mashreq/mashreq-rn-efr/ios/EFRSDK.xcframework/ios-arm64/EFRSDK.framework/EFRSDK",
    ]

    framework_paths.each do |framework_relative_path|
      strip_bitcode_from_framework(bitcode_strip_path, framework_relative_path)
    end
    # END OF TODO: Ask KYC and NEO EGY Onboarding teams to remove bitcode from the libs they are using and remove the step above

  end
end

target 'PushTemplateExtension' do
  pod 'AEPNotificationContent'
end

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:
    • 21.10.1
  • Firebase module(s) you're using that has the issue:
    • e.g. Instance ID
  • Are you using TypeScript?
    • Y & 5.0.4


Activity

  1. MichaelVerdon commented on Aug 15, 2025

    @MichaelVerdon
    Contributor

    When you say 'after some time' how long do you mean?

  2. tomLadder commented on Aug 17, 2025

    @tomLadder

    Having the same issue, that the delivery is delayed when you kill the app for some seconds

  3. rawatmanoj commented on Aug 17, 2025

    @rawatmanoj
    Author

    @tomLadder this got fixed for me. This issue is in development only. try installing UAT build or some other build in your device, it will work everytime.

  4. MichaelVerdon commented on Aug 18, 2025

    @MichaelVerdon
    Contributor

    Hi there, happy to see you found a solution. I will be closing this ticket. Feel free to reopen if the issue persists.

  5. AmsalSugihan commented on Oct 23, 2025

    @AmsalSugihan

    Hello @MichaelVerdon , i'm facing the same issue here on android. The setBackgroundMessageHandler is not triggered when the app has just been closed (killed). The first notification never shows, but the second and the following notifications are delivered correctly.
    Version : 23.4.0
    Android version : 15

  6. dbs-tuan commented on Oct 27, 2025

    @dbs-tuan

    Hello @MichaelVerdon , i'm facing the same issue here on android. The setBackgroundMessageHandler is not triggered when the app has just been closed (killed). The first notification never shows, but the second and the following notifications are delivered correctly.

    Hi @MichaelVerdon
    I'm facing the same issue

    "react-native": "0.80.2",
    "@react-native-firebase/messaging": "^23.1.0",
    

    Android: 13

    The logs from Logcat:

    Unhandled SoftException
    java.lang.RuntimeException: Cannot start headless task, CatalystInstance not available
    at com.facebook.react.jstasks.HeadlessJsTaskContext.startTask(HeadlessJsTaskContext.kt:90)
    at com.facebook.react.jstasks.HeadlessJsTaskContext.startTask(HeadlessJsTaskContext.kt:63)
    at com.facebook.react.HeadlessJsTaskService.invokeStartTask$lambda$0(HeadlessJsTaskService.kt:83)
    at com.facebook.react.HeadlessJsTaskService.$r8$lambda$7kS9Qf9PfS2Skrx_6VkHrCvUXIQ(Unknown Source:0)
    at com.facebook.react.HeadlessJsTaskService$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
    at android.os.Handler.handleCallback(Handler.java:942)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loopOnce(Looper.java:201)
    at android.os.Looper.loop(Looper.java:288)
    at android.app.ActivityThread.main(ActivityThread.java:7918)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:942)
    
  7. 2 remaining items

  8. venux92 commented on Jan 23, 2026

    @venux92

    2 -> That's exactly what happens.

    I'm actually having this error on a release build on my Android device where I check the Logcat.

  9. mikehardy commented on Jan 23, 2026

    @mikehardy
    Collaborator

    Thanks for the extra information @venux92 - without some triage I'm not sure if this is upstream or not, however our test app has a manual test (and related cloud function) that allows us to test background notifications. So triage / reproduction should be possible. This may be New Architecture specific, as a note for reproduction - the logcat certainly indicated it was new arch, and using Expo.

  10. venux92 commented on Jan 23, 2026

    @venux92

    Thanks @mikehardy

    Any idea what I could do in the meantime to prevent this?

    Is there a way to retry the task or run a separate task beforehand—even if it also fails—that would allow the full React Native initialization to complete?

    I've migrated from React Native 0.68.5 (Old Architecture) to Expo 54 (react-native: 0.81.5, New Architecture).
    As soon as I remove setBackgroundMessageHandler, the app works fine.

    I appreciate your input on this. I’m effectively blocked until I find a more stable solution, especially since my app relies heavily on push notifications.

    Have a nice weekend, and thanks!

  11. MichaelVerdon commented on Jan 26, 2026

    @MichaelVerdon
    Contributor

    Thanks @mikehardy

    Any idea what I could do in the meantime to prevent this?

    Is there a way to retry the task or run a separate task beforehand—even if it also fails—that would allow the full React Native initialization to complete?

    I've migrated from React Native 0.68.5 (Old Architecture) to Expo 54 (react-native: 0.81.5, New Architecture). As soon as I remove setBackgroundMessageHandler, the app works fine.

    I appreciate your input on this. I’m effectively blocked until I find a more stable solution, especially since my app relies heavily on push notifications.

    Have a nice weekend, and thanks!

    Hi there
    You could probably try disabling newArch as I believe its on by default on Expo although i am not keen it would work.

    {
      "expo": {
        "newArchEnabled": false
      }
    }
    
  12. mikehardy commented on Jan 26, 2026

    @mikehardy
    Collaborator

    I believe that will work for Expo 54 and lower, so could be good for now

    however the upcoming Expo 55 (currently in beta) will not contain legacy architecture support, so using legacy architecture is possibly viable if all your packages still support it, but will only have a lifespan of half a year or so before Expo 54 will start to seem "stale" and likely start causing transitive problems if not up to date https://expo.dev/changelog/sdk-54#sdk-54-is-the-final-release-to-include-legacy-architecture-support

  13. venux92 commented on Jan 27, 2026

    @venux92

    I tried to disable newArch but I have already libraries that require newArch to work and yes expo 55 will enforce that.

    Currently based on your feedback @mikehardy

    I've created a patch that check if React is ready first otherwise triggers it.
    Not sure if this is the best approach as I have little knowledge in java but this solved the issue.

    Maybe this can help?

    I've tested 50+ times on different devices.
    The log "Waited " + waitedMs + "ms for React to be ready
    ranged from 450ms low/old device to 100ms for recent devices and the SoftException never occurred again.
    I received all my push notifications (I've sent up to 10 at once).

    I've also added a retryPolicy in HeadlessJsTaskConfig as I saw this was possible.
    While the current error is a SoftException and it won't matter but it will catch other errors.

    Not sure why there wasn't any retry policy yet btw, maybe this was a product decision?

    I'm putting the patch below just in case.

    diff --git a/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingHeadlessService.java b/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingHeadlessService.java
    index cfa06542b4b28fd72ee9ce44f0c6535ed5688be7..c9fc74c0908b94a65431ae9ebe5b43a1c120d050 100644
    --- a/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingHeadlessService.java
    +++ b/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingHeadlessService.java
    @@ -2,29 +2,110 @@ package io.invertase.firebase.messaging;
     
     import android.content.Intent;
     import android.os.Bundle;
    +import android.util.Log;
    +
     import com.facebook.react.HeadlessJsTaskService;
    +import com.facebook.react.ReactApplication;
    +import com.facebook.react.ReactHost;
    +import com.facebook.react.bridge.ReactContext;
     import com.facebook.react.jstasks.HeadlessJsTaskConfig;
    +import com.facebook.react.jstasks.HeadlessJsTaskRetryPolicy;
    +import com.facebook.react.jstasks.LinearCountingRetryPolicy;
     import com.google.firebase.messaging.RemoteMessage;
     import io.invertase.firebase.common.ReactNativeFirebaseJSON;
     import javax.annotation.Nullable;
     
     public class ReactNativeFirebaseMessagingHeadlessService extends HeadlessJsTaskService {
    +  private static final String TAG = "RNFBMessagingHeadless";
       private static final long TIMEOUT_DEFAULT = 60000;
       private static final String TIMEOUT_JSON_KEY = "messaging_android_headless_task_timeout";
       private static final String TASK_KEY = "ReactNativeFirebaseMessagingHeadlessTask";
     
    +  // Retry configuration
    +  private static final int MAX_RETRY_ATTEMPTS = 3;
    +  private static final int RETRY_DELAY_MS = 1000;
    +
    +  // React ready wait configuration
    +  private static final int MAX_WAIT_MS = 5000;
    +  private static final int WAIT_INTERVAL_MS = 50;
    +
    +  @Override
    +  public int onStartCommand(Intent intent, int flags, int startId) {
    +    // Trigger React initialization before waiting.
    +    // This help fixing an issue in bridgeless mode where React only
    +    // initializes when super.onStartCommand() is called, but we need React ready
    +    // before that to avoid race conditions.
    +    try {
    +      ReactApplication app = (ReactApplication) getApplication();
    +      ReactHost host = app.getReactHost();
    +      if (host != null && !isReactReady()) {
    +        host.start();
    +      }
    +    } catch (Exception e) {
    +      Log.w(TAG, "Error triggering React start: " + e.getMessage());
    +    }
    +
    +    // Wait for React to be ready
    +    int waitedMs = 0;
    +    while (!isReactReady() && waitedMs < MAX_WAIT_MS) {
    +      try {
    +        Thread.sleep(WAIT_INTERVAL_MS);
    +        waitedMs += WAIT_INTERVAL_MS;
    +      } catch (InterruptedException e) {
    +        Log.w(TAG, "Wait interrupted after " + waitedMs + "ms");
    +        Thread.currentThread().interrupt();
    +        break;
    +      }
    +    }
    +
    +    if (waitedMs > 0) {
    +      Log.d(TAG, "Waited " + waitedMs + "ms for React to be ready. Ready: " + isReactReady());
    +    }
    +
    +    return super.onStartCommand(intent, flags, startId);
    +  }
    +
    +  private boolean isReactReady() {
    +    try {
    +      ReactApplication app = (ReactApplication) getApplication();
    +      ReactHost host = app.getReactHost();
    +      if (host != null) {
    +        ReactContext ctx = host.getCurrentReactContext();
    +        return ctx != null && ctx.hasActiveReactInstance();
    +      }
    +    } catch (Exception e) {
    +      Log.w(TAG, "Error checking React status: " + e.getMessage());
    +    }
    +    return false;
    +  }
    +
       @Override
       protected @Nullable HeadlessJsTaskConfig getTaskConfig(Intent intent) {
         Bundle extras = intent.getExtras();
    -    if (extras == null) return null;
    +    if (extras == null) {
    +      Log.w(TAG, "Intent has no extras, cannot process message");
    +      return null;
    +    }
    +
         RemoteMessage remoteMessage = intent.getParcelableExtra("message");
    +    if (remoteMessage == null) {
    +      Log.w(TAG, "RemoteMessage is null, cannot process");
    +      return null;
    +    }
    +
    +    HeadlessJsTaskRetryPolicy retryPolicy = new LinearCountingRetryPolicy(
    +      MAX_RETRY_ATTEMPTS,
    +      RETRY_DELAY_MS
    +    );
     
         return new HeadlessJsTaskConfig(
    -        TASK_KEY,
    -        ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap(remoteMessage),
    -        ReactNativeFirebaseJSON.getSharedInstance().getLongValue(TIMEOUT_JSON_KEY, TIMEOUT_DEFAULT),
    -        // Prevents race condition where the user opens the app at the same time as a notification
    -        // is delivered, causing a crash.
    -        true);
    +      TASK_KEY,
    +      ReactNativeFirebaseMessagingSerializer.remoteMessageToWritableMap(remoteMessage),
    +      ReactNativeFirebaseJSON.getSharedInstance().getLongValue(TIMEOUT_JSON_KEY, TIMEOUT_DEFAULT),
    +      // Prevents race condition where the user opens the app at the same time as a notification
    +      // is delivered, causing a crash.
    +      true,
    +      retryPolicy
    +    );
       }
     }
    

    Might not be PR ready, just did it to unlock me
    This is also why I'm logging many things, to make sure it works fine.

    Hopefully this help ✌🏽

    Keen to hear your feedback.

  14. Falsin commented on Jan 28, 2026

    @Falsin

    @venux92 You saved my life.Thank you.

  15. mikehardy commented on Jan 28, 2026

    @mikehardy
    Collaborator

    @venux92 very nice:

    • I like the patch, I believe it is doing roughly the right thing
    • the extra logging is great
    • to make it production worthy I/we need to analyze if those Java APIs are available in our oldest supported react-native version or if we can easily support them without it being a breaking change by requiring a react-native release where they are supported. I believe those APIs are somewhat new and may cause compile failures for apps on older react-native if we're not careful. Just needs a check (maybe our minimum required react-native is already new enough! then no problem), then if it's a problem something like a check for new arch being in use, then Java reflection to access the APIs

    If you opened a PR for this, it would be fantastic

    Not sure why there wasn't any retry policy yet btw, maybe this was a product decision?

    Retry Policy is a feature originally added in 2019 / react-native 0.61 which is after this code came to exist. It likely was never considered.

    I like the idea of a retry policy.

    react-native adding the feature react/react-native@ac7ec46

    versus the implementation here in the monorepo, which isn't even the original implementation - that was a couple years prior https://github.com/invertase/react-native-firebase/commits/30df38f6df8750ff753566eb961bb46c10a84fed/packages/messaging/android/src/main/java/io/invertase/firebase/messaging/ReactNativeFirebaseMessagingHeadlessService.java

  16. mikehardy commented on Jan 28, 2026

    @mikehardy
    Collaborator

    This is the code I've used before, for example, when doing similar on Notifee - not sure why it didn't occur to me we would need it here as well but yes we do

    Note that there is extra work done here - lots of extra work - in order for the javascript layer to finalize the headlessJS task which is apparently much more important in new architecture. Also, this shows the reflective access to the ReactHost and Context so it's safe to use for older react-native versions:

    https://github.com/transistorsoft/react-native-background-fetch/blob/master/android/src/main/java/com/transistorsoft/rnbackgroundfetch/HeadlessTask.java

    Notifee's use of the same https://github.com/invertase/notifee/blob/main/packages/react-native/android/src/main/java/io/invertase/notifee/HeadlessTask.java

    Could likely pull the ideas in that code or maybe all of it pretty much verbatim into here and have a trusted solution

  17. venux92 commented on Jan 29, 2026

    @venux92

    @mikehardy thanks for the feedback

    I did not think of looking at notifee code ^^ even if I'm using it as well.

    Notifee's use of the same https://github.com/invertase/notifee/blob/main/packages/react-native/android/src/main/java/io/invertase/notifee/HeadlessTask.java

    Nice and there are even check for new/old arch.

    I could create a PR but as you contribute on both notifee and react-native-firebase I would probably also suggest a trusted solution, that would help if any update need to be made so they can be replicated quickly across both repo. Also I feel in the notifee version, there are extra nice checks with the atomicBoolean to prevent multiple initialisation of the react context.

    Also, this shows the reflective access to the ReactHost and Context so it's safe to use for older react-native versions

    👍

    Let me know

  18. mikehardy commented on Jan 29, 2026

    @mikehardy
    Collaborator

    Okay, for most folks seeing this, I recommend at least temporarily the patch posted by @venux92 #8654 (comment)

    For most people that will work fine for now

    If you are on old architecture, and also in certain corner cases, that patch won't handle everything perfectly.

    As a permanent solution, a PR is needed that effectively takes the react-native-background-fetch solution (which was also adopted by notifee) and drops it in here. It handles old and new arch, it handles all the corner cases we are aware of, and it's well-tested in existing app deployments. There won't be a general solution that can be reused across repos, just copying the file contents in here and bending it to match the task we start is sufficient

    I hope to get to that myself but it may be some time unfortunately, so any PR from affected parties that dropped that more robust solution here would be most welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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