0

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'

  • `didFinishLaunchingWithOptions` your new code have 2 methods same implementation. remove one and run. – jatin fl Jul 16 '21 at 12:18
  • Thanks, that was a copy-pasting mistake. I've fixed the mistake. How do I implement that AppDelegate2 in .m file, for the interface in the .h file? Would that resolve? – P. Lawrence Jul 16 '21 at 22:48
  • generally `AppDelegate` work as entry point of an app. so i think entry point can't be duplicate. – jatin fl Jul 17 '21 at 04:10
  • why do you want second `AppDelegate`. can you describe. – jatin fl Jul 17 '21 at 04:12
  • My problem is that the instructions from Appsflyer require me to paste code that introduces a new AppDelegate interface meaning I end up with two of them in my AppDelegate.h file. If I can only have 1 interface what should I do with this: `@interface AppDelegate : UIResponder @end` since it has a different signature to the one already in AppDelegate.h? – P. Lawrence Jul 17 '21 at 09:30
  • yeah i got your point i am sure that `AppsFlyerLibDelegate` you will get from any framwork. Right? – jatin fl Jul 17 '21 at 09:48
  • Not sure I understand your question but the appsflyer library requires that I paste this: `@interface AppDelegate : UIResponder @end` into AppDelagate.h, but that file already has this: `@interface AppDelegate : UMAppDelegateWrapper @end` How do I get them to work together? – P. Lawrence Jul 19 '21 at 04:34

0 Answers0