Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[馃悰] ios - setMessageBackgroundHandler not called after device reboot #6711

Closed
2 of 7 tasks
CharlieBreval opened this issue Nov 18, 2022 · 9 comments
Closed
2 of 7 tasks
Labels
Help: Needs Triage Issue needs additional investigation/triaging. Impact: Bug New bug report Type: Stale Issue has become stale - automatically added by Stale bot

Comments

@CharlieBreval
Copy link

CharlieBreval commented Nov 18, 2022

Issue

Hello and thanks for your super work making firebase awesome for react native !

On our app we are setting up data-only notifications (no notification key in the payload sent to firebase). The payload sent to firebase through the legacy api is the following one :

{
  "data": {
    "body": "You have received a new reservation request from: Charlie Breval, 2 people, 10/27/2022 8:30 AM\\n",
    "eventData": "{\\"mealDate\\":\\"2022-10-27T06:30:00Z\\",\\"reservationUuid\\":\\"e54a2509-cc32-4d59-ba7e-720dba23dfb5\\",\\"restaurantUuid\\":\\"2b2ed104-a473-4798-a8a1-3e30931745ce\\"}",
    "title": "Info",
    "eventType": "reservation.create"
  },
  "content_available": true,
  "to": "edj00p4-mUZRhjY84Z49ed:APA91bFeMmTWy99f6geoCozhE6FvbMaraSO1MgJghWblcJNMa4_n6dzpDVmX-Qjz9NuvwGSjPAVk394NNEKIWcnXDsyPFqDS_I9DUStUsLfuOOnJ4EE1VyFOE1aH3TrkvWUI5IbFCjEE",
  "priority": "high"
}

Our dependencies are :

    "react-native": "0.68.5",
    ...
    "@react-native-firebase/analytics": "16.4.5",
    "@react-native-firebase/app": "16.4.5",
    "@react-native-firebase/messaging": "16.4.5",
    ...
    "@notifee/react-native": "7.0.4",

In our setBackgroundMessageHandler() method, we display a notification using notifee and it works perfectly on android.
On Ios, we are meeting an issue : When device is powered off and powered on, data-only notification seems to not be handled by application (which has not been started since reboot)

To reproduce on ios:

  • power-off the device
  • power-on the device
  • trigger a push notification (data-only)

Below the js code setBackgroundMessageHandler :


  messaging().setBackgroundMessageHandler(async (remoteMessage) => {
    const intl = getConfiguredIntl();
    const apolloClient = getConfigureApolloClient();

    // On iOS to have quick actions available, we need to initialize the categories
    await defineIosQuickActionCategories(intl);

    notifee.displayNotification({
        title: title || 'Information',
        body: body || 'You have a new notification',
        data,
        ios: {
          sound: 'default',
          ...iosCategory,
        },
        android: {
          channelId: CHANNEL_ID,
          smallIcon: 'ic_stat_name',
          // pressAction is needed if you want the notification to open the app when pressed
          pressAction: {
            id: 'default',
          },
        },
      })
  });

Nothing happen on our app.
Is it only happening on our app or is it a known issue ?

NB: this scenario is well working on android
NB2: We really need to use data-only notification because we are in some cases displaying notification with quick-actions.


Project Files

Javascript

Click To Expand

package.json:

    "react-native": "0.68.5",
    ...
    "@react-native-firebase/analytics": "16.4.5",
    "@react-native-firebase/app": "16.4.5",
    "@react-native-firebase/messaging": "16.4.5",
    ...
    "@notifee/react-native": "7.0.4",

firebase.json for react-native-firebase v6:

{
  "react-native": {
    "messaging_ios_auto_register_for_remote_messages": false,
    "messaging_android_notification_channel_id": "high-priority"
  }
}

iOS

Click To Expand

ios/Podfile:

  • I'm not using Pods
  • I'm using Pods and my Podfile looks like:
# frozen_string_literal: true

require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '11.0'
install! 'cocoapods', deterministic_uuids: false # More information on why it's disabled here: https://medium.com/@hdsenevi/how-to-fix-pod-install-breaking-your-ios-project-57a7c1c64441

pod 'Firebase', modular_headers: true
pod 'FirebaseCore', modular_headers: true
pod 'GoogleUtilities', modular_headers: true
$RNFirebaseAsStaticFramework = true

target 'UFM3-development' do
  config = use_native_modules!

  # Flags change depending on the env values.
  flags = get_default_flags

  use_react_native!(
    path: config[:reactNativePath],
    # to enable hermes on iOS, change `false` to `true` and then install pods
    hermes_enabled: flags[:hermes_enabled],
    fabric_enabled: flags[:fabric_enabled],
    # An absolute path to your application root.
    app_path: "#{Pod::Config.instance.installation_root}/.."
  )

  # Enables Flipper.
  #
  # Note that if you have use_frameworks! enabled, Flipper will not work and
  # you should disable the next line.
  use_flipper!('Flipper' => '0.159.0')
  post_install do |installer|
    react_native_post_install(installer)
    __apply_Xcode_12_5_M1_post_install_workaround(installer)
  end
end

target 'UFM3-staging' do
  config = use_native_modules!

  # Flags change depending on the env values.
  flags = get_default_flags
  use_react_native!(
    path: config['reactNativePath'],
    # to enable hermes on iOS, change `false` to `true` and then install pods
    hermes_enabled: flags[:hermes_enabled],
    fabric_enabled: flags[:fabric_enabled],
    # An absolute path to your application root.
    app_path: "#{Pod::Config.instance.installation_root}/.."
  )
end

target 'UFM3-production' do
  config = use_native_modules!

  # Flags change depending on the env values.
  flags = get_default_flags

  use_react_native!(
    path: config['reactNativePath'],
    # to enable hermes on iOS, change `false` to `true` and then install pods
    hermes_enabled: flags[:hermes_enabled],
    fabric_enabled: flags[:fabric_enabled],
    # An absolute path to your application root.
    app_path: "#{Pod::Config.instance.installation_root}/.."
  )
end

AppDelegate.m:

#import "AppDelegate.h"

#import "RNFBMessagingModule.h"
#import <React/RCTLinkingManager.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>

#import <React/RCTAppSetupUtils.h>

#if RCT_NEW_ARCH_ENABLED
  #import <React/CoreModulesPlugins.h>
  #import <React/RCTCxxBridgeDelegate.h>
  #import <React/RCTFabricSurfaceHostingProxyRootView.h>
  #import <React/RCTSurfacePresenter.h>
  #import <React/RCTSurfacePresenterBridgeAdapter.h>
  #import <ReactCommon/RCTTurboModuleManager.h>

  #import <react/config/ReactNativeConfig.h>

  @interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
    RCTTurboModuleManager *_turboModuleManager;
    RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
    std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
    facebook::react::ContextContainer::Shared _contextContainer;
  }
  @end
#endif

#import <Firebase.h>
#import "RNBootSplash.h"
#import <CodePush/CodePush.h>
#import "ReactNativeConfig.h"
#import "Orientation.h"

#ifdef FB_SONARKIT_ENABLED
  #import <FlipperKit/FlipperClient.h>
  #import <FlipperKitLayoutPlugin/FlipperKitLayoutPlugin.h>
  #import <FlipperKitUserDefaultsPlugin/FKUserDefaultsPlugin.h>
  #import <FlipperKitNetworkPlugin/FlipperKitNetworkPlugin.h>
  #import <SKIOSNetworkPlugin/SKIOSNetworkAdapter.h>
  #import <FlipperKitReactPlugin/FlipperKitReactPlugin.h>

  static void InitializeFlipper(UIApplication *application) {
    FlipperClient *client = [FlipperClient sharedClient];
    SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults];
    [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]];
    [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]];
    [client addPlugin:[FlipperKitReactPlugin new]];
    [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]];
    [client start];
  }
#endif

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
  RCTAppSetupPrepareApp(application);

  [FIRApp configure];
  #ifdef FB_SONARKIT_ENABLED
    InitializeFlipper(application);
  #endif

  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];

  #if RCT_NEW_ARCH_ENABLED
    _contextContainer = std::make_shared<facebook::react::ContextContainer const>();
    _reactNativeConfig = std::make_shared<facebook::react::EmptyReactNativeConfig const>();
    _contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
    _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
    bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
  #endif

  UIView *rootView = RCTAppSetupDefaultRootView(bridge, [ReactNativeConfig envFor:@"APP_NAME"], appProperties);

  if (@available(iOS 13.0, *)) {
    rootView.backgroundColor = [UIColor systemBackgroundColor];
  } else {
    rootView.backgroundColor = [UIColor whiteColor];
  }

  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [UIViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];

  [RNBootSplash initWithStoryboard:@"BootSplash" rootView:rootView];

  return YES;
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [CodePush bundleURL];
#endif
}

#if RCT_NEW_ARCH_ENABLED

#pragma mark - RCTCxxBridgeDelegate

- (std::unique_ptr<facebook::react::JSExecutorFactory>)jsExecutorFactoryForBridge:(RCTBridge *)bridge
{
  _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
                                                             delegate:self
                                                            jsInvoker:bridge.jsCallInvoker];
  return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager);
}

#pragma mark RCTTurboModuleManagerDelegate

- (Class)getModuleClassFromName:(const char *)name
{
  return RCTCoreModulesClassProvider(name);
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                      jsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
{
  return nullptr;
}

- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const std::string &)name
                                                     initParams:
                                                         (const facebook::react::ObjCTurboModule::InitParams &)params
{
  return nullptr;
}

- (id<RCTTurboModule>)getModuleInstanceFromClass:(Class)moduleClass
{
  return RCTAppSetupDefaultModuleFromClass(moduleClass);
}

#endif

// Handle deep linking
- (BOOL)application:(UIApplication *)application
   openURL:(NSURL *)url
   options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options
{
  return [RCTLinkingManager application:application openURL:url options:options];
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(nonnull NSUserActivity *)userActivity
 restorationHandler:(nonnull void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler
{
 return [RCTLinkingManager application:application
                  continueUserActivity:userActivity
                    restorationHandler:restorationHandler];
}
// Handle deep linking

- (UIInterfaceOrientationMask)application:(UIApplication *)application supportedInterfaceOrientationsForWindow:(UIWindow *)window {
  return [Orientation getOrientation];
}

@end

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


@CharlieBreval CharlieBreval added Help: Needs Triage Issue needs additional investigation/triaging. Impact: Bug New bug report labels Nov 18, 2022
@mikehardy
Copy link
Collaborator

Is that js code in index.js, prior to the AppRegistry call to register the app?

If it is, I think this may be another specific case if the general problem that data only notifications are not guaranteed to deliver. In that case, is a notification extension handler not able to add quick actions in an FCM with notification block? I wasn't aware that was a limitation but it might be

If it's in the App code and not index.js, move it to index.js and retry?

@CharlieBreval
Copy link
Author

CharlieBreval commented Nov 18, 2022

Thanks a lot for your answer @mikehardy
Yes we do it before AppRegistry.registerComponent() but the function initMessaging is async, do you think it could be linked ?

index.js :

import React from 'react';
import { AppRegistry } from 'react-native';

import App from '@app/App';
import config from '@app/config';
import { StorybookApp } from '@app/StorybookApp';
import { initMessaging } from '@app/providers/PushNotificationProvider/messaging';

initMessaging();

function HeadlessCheck({ isHeadless }) {
  if (isHeadless) {
    // App has been launched in the background by iOS, ignore
    return null;
  }

  return <App />;
}

AppRegistry.registerComponent(config.appName, () =>
  process.env.STORYBOOK ? StorybookApp : HeadlessCheck,
);

and initMessaging file :

export const initMessaging = async () => {
  /**
   * BACKGROUND REMOTE MESSAGE
   */
  messaging().setBackgroundMessageHandler(async (remoteMessage) => {
    const intl = getConfiguredIntl();
    const apolloClient = getConfigureApolloClient();

    // On iOS to have quick actions available, we need to initialize the categories
    await defineIosQuickActionCategories(intl);

    switch (remoteMessage?.data?.eventType) {
      case REMOTEMESSAGE_EVENTTYPE_RESERVATIONCREATE:
        reservationCreateHandler.handle({ remoteMessage, apolloClient });
        break;

      default:
        break;
    }
  });

The function initMessaging is async due to the async function from notifee called inside defineIosQuickActionCategories:

await notifee.setNotificationCategories(categories);

@mikehardy
Copy link
Collaborator

Perhaps. With no toplevel await I use the old style in my index.js, method().then().catch() in my app. You might experiment

@CharlieBreval
Copy link
Author

CharlieBreval commented Nov 18, 2022

I tested with a very simple handler :

messaging().setBackgroundMessageHandler(() => {
    notifee.displayNotification({
        title: 'hello,
        body: 'its me',
    });
});

Notification does not show up after a device restart, unless the application has been opened once.
I am not 100% sure our appDelegate is correct, could it come from there ?

@mikehardy
Copy link
Collaborator

You could always put a native code log in your AppDelegate to trace program execution through it (stackoverflow has good examples of this, and it seems you know how to get native device logs, but just in case - you can make an obvious tag, then use Console.app to watch device logs and filter for the tag).

This seems like it falls into the "no guaranteed delivery" bucket to me. The app has not run and the system does not seem to want to start it just for a data-only message

@CharlieBreval
Copy link
Author

Hello @mikehardy
Thanks a lot for your time. After a time to investiguate, I finally decided to switch our implementation into :

  • not using data only push notification
  • use firebase-admin sdk on the backend instead of the legacy REST api (we were using the legacy api and that was the reason I couldn't use the ios category id on a notification triggered by the backend)

Everything seems to work fine, even the onBackgroundEvent is well triggered after a device restart without having launched the application once.

I think we could add some documentation about it : "if you want to use apns category, we highly recommend to switch to new firebase api"

@CharlieBreval
Copy link
Author

PS: I forgot to mention that our target is having push notifications with quickActions on iOS. I tested with android, and for now, it doesn' work

@mikehardy
Copy link
Collaborator

As someone who has looked at the documentation far too much to know where new users might first notice things, could you hit the edit button at the top right of whatever page seems best for your suggested documentation update? Github has a web PR flow for docs that makes it pretty painless - just takes a couple minutes 馃檹

As for the quick actions that may be a Notifee issue (probably is, I should say...)

@github-actions
Copy link

Hello 馃憢, to help manage issues we automatically close stale issues.

This issue has been automatically marked as stale because it has not had activity for quite some time.Has this issue been fixed, or does it still require attention?

This issue will be closed in 15 days if no further activity occurs.

Thank you for your contributions.

@github-actions github-actions bot added the Type: Stale Issue has become stale - automatically added by Stale bot label Dec 25, 2022
@github-actions github-actions bot closed this as not planned Won't fix, can't repro, duplicate, stale Jan 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Help: Needs Triage Issue needs additional investigation/triaging. Impact: Bug New bug report Type: Stale Issue has become stale - automatically added by Stale bot
Projects
None yet
Development

No branches or pull requests

2 participants