react-native-firebase icon indicating copy to clipboard operation
react-native-firebase copied to clipboard

inject headless task in Appdelegate.mm

Open crzycoder opened this issue 3 years ago • 5 comments

Issue

NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions]; how to use this with latest react native 0.69.3


Project Files

Javascript

Click To Expand

package.json:

"@react-native-firebase/analytics": "^15.2.0",
"@react-native-firebase/app": "^15.2.0",
"@react-native-firebase/crashlytics": "^15.2.0",
"@react-native-firebase/dynamic-links": "^15.2.0",
"@react-native-firebase/messaging": "^15.2.0",
"@react-native-firebase/perf": "^15.2.0",
"react": "^18.0.0",
"react-native": "^0.69.3",

firebase.json for react-native-firebase v6:

# N/A

iOS

Click To Expand

ios/Podfile:

  • [ ] I'm not using Pods
  • [x] I'm using Pods and my Podfile looks like:
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'

platform :ios, '12.4'
install! 'cocoapods', :deterministic_uuids => false
production = ENV["PRODUCTION"] == "1"

target 'Fintelli' do
  config = use_native_modules!
  pod 'React-RCTText', :path => '../node_modules/react-native/Libraries/Text', :modular_headers => true

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

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

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

  target 'FintelliTests' do
    inherit! :complete
    # Pods for testing
  end

   post_install do |installer|
     react_native_post_install(installer)
     __apply_Xcode_12_5_M1_post_install_workaround(installer)
     `sed -i -e  $'s/__IPHONE_10_0/__IPHONE_12_0/' Pods/RCT-Folly/folly/portability/Time.h`
   end
end

AppDelegate.mm:

#import "AppDelegate.h"
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <React/RCTAppSetupUtils.h>
#import <Firebase.h>
#import <React/RCTLinkingManager.h>
#import "RNSplashScreen.h"
#import "RNFBMessagingModule.h"
#import <UserNotifications/UserNotifications.h>
#import <RNCPushNotificationIOS.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>
static NSString *const kRNConcurrentRoot = @"concurrentRoot";
@interface AppDelegate () <RCTCxxBridgeDelegate, RCTTurboModuleManagerDelegate> {
  RCTTurboModuleManager *_turboModuleManager;
  RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
  std::shared_ptr<const facebook::react::ReactNativeConfig> _reactNativeConfig;
  facebook::react::ContextContainer::Shared _contextContainer;
}
@end
#endif
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  RCTAppSetupPrepareApp(application);
  [FIRApp configure];
  NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
  
  //Clear keychain on first run in case of reinstallation
  if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HAS_RUN_BEFORE"] == NO) {
    // Set the appropriate value so we don't clear next time the app is launched
    [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"HAS_RUN_BEFORE"];
    NSArray *secItemClasses = [NSArray arrayWithObjects:
                               (__bridge id)kSecClassGenericPassword,
                               (__bridge id)kSecClassInternetPassword,
                               (__bridge id)kSecClassCertificate,
                               (__bridge id)kSecClassKey,
                               (__bridge id)kSecClassIdentity,
                               nil];
    for (id secItemClass in secItemClasses) {
      NSDictionary *spec = @{(__bridge id)kSecClass: secItemClass};
      SecItemDelete((__bridge CFDictionaryRef)spec);
    }
   }
  
  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
  NSDictionary *initProps = [self prepareInitialProps];
  UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"Fintelli", initProps);
  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];
  [RNSplashScreen show];
  return YES;
}

-(void)applicationWillResignActive:(UIApplication *)application
{
  UIView *colorView = [[UIView alloc]initWithFrame:self.window.frame];
  colorView.backgroundColor = [UIColor whiteColor];
  colorView.tag = 1234;
  colorView.alpha = 0;
  [self.window addSubview:colorView];
  [self.window bringSubviewToFront:colorView];
  [UIView animateWithDuration:0.5 animations:^{
    colorView.alpha = 1;
  }];
}

-(void)applicationDidBecomeActive:(UIApplication *)application
{
  UIView *colorView = [self.window viewWithTag:1234];
  [UIView animateWithDuration:0.5 animations:^{
    colorView.alpha = 0;
  } completion:^(BOOL finished) {
    [colorView removeFromSuperview];
  }];
}
/// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
///
/// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
/// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
/// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`.
- (BOOL)concurrentRootEnabled
{
  // Switch this bool to turn on and off the concurrent root
  return true;
}
- (NSDictionary *)prepareInitialProps
{
  NSMutableDictionary *initProps = [NSMutableDictionary new];
#ifdef RCT_NEW_ARCH_ENABLED
  initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]);
#endif
  return initProps;
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#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

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

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

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
 [RNCPushNotificationIOS didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}
// Required for the notification event. You must call the completion handler after handling the remote notification.
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
  [RNCPushNotificationIOS didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}
// Required for the registrationError event.
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
 [RNCPushNotificationIOS didFailToRegisterForRemoteNotificationsWithError:error];
}
// Required for localNotification event
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
didReceiveNotificationResponse:(UNNotificationResponse *)response
         withCompletionHandler:(void (^)(void))completionHandler
{
  [RNCPushNotificationIOS didReceiveNotificationResponse:response];
}

@end

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:

"react": "^18.0.0",
"react-native": "^0.69.3",
  • Platform that you're experiencing the issue on:
    • [x] 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

crzycoder avatar Aug 04 '22 08:08 crzycoder

I'm sorry, I don't understand the problem. What exactly is the problem?

mikehardy avatar Sep 18 '22 03:09 mikehardy

@mikehardy I can probably help with explaining as I find myself in the same situation.

The instructions here explain how to inject an isHeadless prop for iOS, which we can use when the app is in the background state to prevent mounting the root React component, which has worked well.

With the React Native 0.69.x upgrade, changes are done to the AppDelegate.mm file that conflict with aforementioned "inject a isHeadless prop" implementation instructions.

Could you kindly help to provide updated instructions to inject an isHeadless prop for those running React Native >= 0.69.x, given the changes to AppDelegate.mm?

abandisch avatar Sep 20 '22 22:09 abandisch

Well the general idea is "process launch options in between the start of the main AppDelegate hook, and send your processed options through.

So in 0.69.3 that means adding the processing call

(NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];)

https://github.com/facebook/react-native/blob/38cfe63338af4977817abefd12fb608fa5d2fc83/template/ios/HelloWorld/AppDelegate.mm#L35

Now you have some new launch options., Send those through as the parameter to this method call here:

https://github.com/facebook/react-native/blob/38cfe63338af4977817abefd12fb608fa5d2fc83/template/ios/HelloWorld/AppDelegate.mm#L36

That should work? I apologize - that seems pretty self-evident to me so I'm afraid I may have missed something. Maybe it just doesn't work any more? Has anyone watching this tried that?

mikehardy avatar Sep 20 '22 23:09 mikehardy

@mikehardy - thanks for replying. This really just boils down to my limited understanding of the inner workings of AppDelegate.mm, but I've sorted this with the help of the guys over at SO

abandisch avatar Sep 21 '22 00:09 abandisch

That works but also includes an unnecessary step, it's my understanding that the addCustomPropsToUserProps is already doing what addEntriesFromDictionary is doing, so you're just merging dictionaries twice for no reason.

It really is supposed to be as simple as "call the messaging function with initialProps as an arg, then use the new props object where ever initialProps was used before".

[edit: although, I might be wrong in that, it does not appear to merge them, which is perhaps a bug? It's been operating this way since initial implementation though, so either the feature is not used much and no one has noticed or it does not matter? https://github.com/invertase/react-native-firebase/blob/ab530bc8cb624f6e4ae49fe579f21df711706ee2/packages/messaging/ios/RNFBMessaging/RNFBMessagingModule.m#L48-L58 ]

But, doing unnecessary but idempotent work is not a bug, just a tiny tiny performance hit (really tiny), so no big deal, and if it works it works. Cheers

mikehardy avatar Sep 21 '22 12:09 mikehardy

@mikehardy - thanks for replying. This really just boils down to my limited understanding of the inner workings of AppDelegate.mm, but I've sorted this with the help of the guys over at SO

That's resolved the Issue. Thanks @abandisch

crzycoder avatar Sep 22 '22 09:09 crzycoder

currenty this is my AppDelegate.mm file looks like `- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { self.moduleName = @"mpclogistics_driverapp"; // 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]; }`

Could you help me out.

work-tech-hqthinh avatar Jul 15 '23 12:07 work-tech-hqthinh