inject headless task in Appdelegate.mm
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.settingsjetifier=truefor Android compatibility? - [ ] I am using the NPM package
jetifierfor 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-firebaseversion you're using that has this issue:e.g. 5.4.3
Firebasemodule(s) you're using that has the issue:e.g. Instance ID
- Are you using
TypeScript?Y/N&VERSION
- 👉 Check out
React Native FirebaseandInvertaseon Twitter for updates on the library.
I'm sorry, I don't understand the problem. What exactly is the problem?
@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?
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 - 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 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 - 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
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.