I am following these instructions to integrate Appslflyer into my React-Native app: https://support.appsflyer.com/hc/en-us/articles/360017822178#testing-ios-uninstalls
WHAT AM I TRYING TO ACHIEVE
I need to track iOS uninstalls. They use silent push notifications and require I copy-paste some code from the link above.
WHAT IS MY PROBLEM?
I've done all the other work to setup p12 certs and install but am getting stuck on merging the code from the instructions with existing code in my app.
WHAT DID I TRY?
I have no clue what I am looking at code-wise but I tried renaming AppDelegate to get past the duplicate interface name error in the .h file and imported it as AppDelegate2 but after that I don't have a clue how to implement the interface.
I also made an effort to paste some of the code into the existing implementation but still it wont build and tbh I don't know if it works even if it builds successfully.
Can someone with expertise in Objective-C assist? Thanks in advance.
I left the full content of AppDelegate in the code snippet in case you can spot where I can rather paste the new code to make life easier.
My AppDelegate.h looks like this BEFORE I follow the instructions:
#import <Foundation/Foundation.h>
#import <EXUpdates/EXUpdatesAppController.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <UMCore/UMAppDelegateWrapper.h>
#import <AppsFlyerLib/AppsFlyerLib.h>
@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>
@end
and AFTER I follow the instructions it looks like:
#import <Foundation/Foundation.h>
#import <EXUpdates/EXUpdatesAppController.h>
#import <React/RCTBridgeDelegate.h>
#import <UIKit/UIKit.h>
#import <UMCore/UMAppDelegateWrapper.h>
#import <AppsFlyerLib/AppsFlyerLib.h>
@interface AppDelegate : UMAppDelegateWrapper <RCTBridgeDelegate, EXUpdatesAppControllerDelegate>
@end
@interface AppDelegate2 : UIResponder <UIApplicationDelegate, AppsFlyerLibDelegate>
@end
My AppDelegate.m looks like this BEFORE I paste new code:
#import <Firebase.h>
#import "RNSailthruMobileBridge.h"
#import "AppDelegate.h"
#import "ReactNativeConfig.h"
#import <React/RCTLinkingManager.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import <EXSplashScreen/EXSplashScreenService.h>
#import <UMCore/UMModuleRegistryProvider.h>
#import <RNAppsFlyer.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>
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <AdSupport/AdSupport.h>
#import <UserNotifications/UserNotifications.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
@interface AppDelegate () <RCTBridgeDelegate>
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
@property (nonatomic, strong) NSDictionary *launchOptions;
@end
@implementation AppDelegate
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Firebase
if ([FIRApp defaultApp] == nil) {
[FIRApp configure];
}
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
self.launchOptions = launchOptions;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
#ifdef DEBUG
[self initializeReactNativeApp];
#else
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
controller.delegate = self;
[controller startAndShowLaunchScreen:self.window];
#endif
[super application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
//NSString * token = [[NSString alloc] initWithData:deviceTokenencoding:NSUTF8StringEncoding];
NSString *str = [NSString stringWithFormat:@"Device Token=%@",deviceToken];
NSLog(@"Device Token:%@",str);
//NSLog(@"Device token is called");
//const void *devTokenBytes = [deviceToken bytes];
//NSLog(@"Device Token");
}
(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSString *str = [NSString stringWithFormat: @"Error: %@", err];
NSLog(@"Error:%@",str);
}
(RCTBridge *)initializeReactNativeApp
{
// RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
// RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
// then read individual keys like:
NSString *SAIL_THRU_SDK_KEY = [ReactNativeConfig envFor:@"SAIL_THRU_SDK_KEY"];
id<RCTBridgeDelegate> moduleInitialiser = [[RNSailthruMobileBridge alloc] initWithJSCodeLocation:[self sourceURLForBridge:nil] appKey:SAIL_THRU_SDK_KEY
moduleRegistryAdapter: _moduleRegistryAdapter]; // Obtain SDK key from your Sailthru Mobile app settings
RCTBridge * bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:self.launchOptions];
RCTRootView * rootView = [[RCTRootView alloc]
initWithBridge:bridge
moduleName:@"main"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return bridge;
}
(NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
return extraModules;
}
(NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#ifdef DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
#endif
}
(void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
appController.bridge = [self initializeReactNativeApp];
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
[splashScreenService showSplashScreenFor:self.window.rootViewController];
}
(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
[[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler];
[RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
return YES;
}
(BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [RCTLinkingManager application:application openURL:url
sourceApplication:sourceApplication annotation:annotation];
}
(BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *) options {
[[AppsFlyerAttribution shared] handleOpenUrl:url options:options];
return YES;
}
@end
... and like this AFTER I paste new code:
#import <Firebase.h>
#import "RNSailthruMobileBridge.h"
#import "AppDelegate.h"
#import "ReactNativeConfig.h"
#import <React/RCTLinkingManager.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTRootView.h>
#import <UMCore/UMModuleRegistry.h>
#import <UMReactNativeAdapter/UMNativeModulesProxy.h>
#import <UMReactNativeAdapter/UMModuleRegistryAdapter.h>
#import <EXSplashScreen/EXSplashScreenService.h>
#import <UMCore/UMModuleRegistryProvider.h>
#import <RNAppsFlyer.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>
#import <AppTrackingTransparency/AppTrackingTransparency.h>
#import <AdSupport/AdSupport.h>
#import <UserNotifications/UserNotifications.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
@interface AppDelegate2 () <UIApplicationDelegate>
@end
@interface AppDelegate () <RCTBridgeDelegate>
@property (nonatomic, strong) UMModuleRegistryAdapter *moduleRegistryAdapter;
@property (nonatomic, strong) NSDictionary *launchOptions;
@end
@implementation AppDelegate
(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
########## below is new code from AppsFlyer instructions #########
// The userNotificationTypes below is just an example and may be changed depending on the app
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
center.delegate = self;
[center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError * _Nullable error) {
}];
[[UIApplication sharedApplication] registerForRemoteNotifications];
// if you do not use push notificaiton in your app, uncomment the following line
//application.applicationIconBadgeNumber = 0;
}
########## above is new code from AppsFlyer instructions #########
// Firebase
if ([FIRApp defaultApp] == nil) {
[FIRApp configure];
}
#ifdef FB_SONARKIT_ENABLED
InitializeFlipper(application);
#endif
self.moduleRegistryAdapter = [[UMModuleRegistryAdapter alloc] initWithModuleRegistryProvider:[[UMModuleRegistryProvider alloc] init]];
self.launchOptions = launchOptions;
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
#ifdef DEBUG
[self initializeReactNativeApp];
#else
EXUpdatesAppController *controller = [EXUpdatesAppController sharedInstance];
controller.delegate = self;
[controller startAndShowLaunchScreen:self.window];
#endif
[super application:application didFinishLaunchingWithOptions:launchOptions];
return YES;
}
(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
//NSString * token = [[NSString alloc] initWithData:deviceTokenencoding:NSUTF8StringEncoding];
NSString *str = [NSString stringWithFormat:@"Device Token=%@",deviceToken];
NSLog(@"Device Token:%@",str);
//NSLog(@"Device token is called");
//const void *devTokenBytes = [deviceToken bytes];
//NSLog(@"Device Token");
}
(void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
NSString *str = [NSString stringWithFormat: @"Error: %@", err];
NSLog(@"Error:%@",str);
}
(RCTBridge *)initializeReactNativeApp
{
// RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:self.launchOptions];
// RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge moduleName:@"main" initialProperties:nil];
// then read individual keys like:
NSString *SAIL_THRU_SDK_KEY = [ReactNativeConfig envFor:@"SAIL_THRU_SDK_KEY"];
id<RCTBridgeDelegate> moduleInitialiser = [[RNSailthruMobileBridge alloc] initWithJSCodeLocation:[self sourceURLForBridge:nil] appKey:SAIL_THRU_SDK_KEY
moduleRegistryAdapter: _moduleRegistryAdapter]; // Obtain SDK key from your Sailthru Mobile app settings
RCTBridge * bridge = [[RCTBridge alloc] initWithDelegate:moduleInitialiser launchOptions:self.launchOptions];
RCTRootView * rootView = [[RCTRootView alloc]
initWithBridge:bridge
moduleName:@"main"
initialProperties:nil];
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return bridge;
}
(NSArray<id<RCTBridgeModule>> *)extraModulesForBridge:(RCTBridge *)bridge
{
NSArray<id<RCTBridgeModule>> *extraModules = [_moduleRegistryAdapter extraModulesForBridge:bridge];
// If you'd like to export some custom RCTBridgeModules that are not Expo modules, add them here!
return extraModules;
}
(NSURL *)sourceURLForBridge:(RCTBridge *)bridge {
#ifdef DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
#else
return [[EXUpdatesAppController sharedInstance] launchAssetUrl];
#endif
}
(void)appController:(EXUpdatesAppController *)appController didStartWithSuccess:(BOOL)success {
appController.bridge = [self initializeReactNativeApp];
EXSplashScreenService *splashScreenService = (EXSplashScreenService *)[UMModuleRegistryProvider getSingletonModuleForClass:[EXSplashScreenService class]];
[splashScreenService showSplashScreenFor:self.window.rootViewController];
}
(BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler {
[[AppsFlyerAttribution shared] continueUserActivity:userActivity restorationHandler:restorationHandler];
[RCTLinkingManager application:application
continueUserActivity:userActivity
restorationHandler:restorationHandler];
return YES;
}
(BOOL)application:(UIApplication *)application openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
return [RCTLinkingManager application:application openURL:url
sourceApplication:sourceApplication annotation:annotation];
}
(BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary *) options {
[[AppsFlyerAttribution shared] handleOpenUrl:url options:options];
return YES;
}
########## below is new code from AppsFlyer instructions #########
(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
[[AppsFlyerLib shared] registerUninstall:deviceToken];
}
@end
I get this error when I build: Screenshot of the error in Xcode: .../AppDelegate.h:13:1: Duplicate interface definition for class 'AppDelegate'