iOS config issue – push notifications (Klaviyo) – ReactNative Expo project

Our project is developed using Expo (ReactNative). Used expo’s prebuild command to generate the android and ios codes. We planned to use the Klaviyo for push notifications.

We are using the SDK : klaviyo-react-native-sdk

When implemented, we faced issues with configuration. So referred the example code here:

  • Android
  • iOS

I was able to get the Android working(and was able to test the push notifications), but the iOS is failing the build when I run yarn run ios.

Have been spending more than 3 weeks just to get this resolved.

AppDelegate.h:

#import <RCTAppDelegate.h>
#import <UIKit/UIKit.h>
#import <Expo/Expo.h>
#import <UserNotifications/UserNotifications.h>

// @interface AppDelegate : EXAppDelegateWrapper

// iOS Installation Step 1: Conform AppDelegate to UNUserNotificationCenterDelegate
// @interface AppDelegate: RCTAppDelegate <UNUserNotificationCenterDelegate>
@interface AppDelegate : EXAppDelegateWrapper <UNUserNotificationCenterDelegate>

@end

AppDelegate.mm:

#import "AppDelegate.h"

#import <React/RCTBundleURLProvider.h>
#import <React/RCTLinkingManager.h>

@implementation AppDelegate

//-- ABC - Change to NO if you don't want debug alerts or logs.
BOOL isDebug = YES;

//-- ABC - Change to NO if you prefer to initialize and handle push tokens in the React Native layer
BOOL useNativeImplementation = YES;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  self.moduleName = @"main";

  // 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 = @{};

  //-- ABC - iOS Installation Step 2: Set the UNUserNotificationCenter delegate to self
  [UNUserNotificationCenter currentNotificationCenter].delegate = self;

  //-- ABC
  if (useNativeImplementation) {
    // iOS Installation Step 3: Initialize the SDK with public key, if initializing from native code
    // Exclude if initializing from react native layer
    [PushNotificationsHelper initializeSDK: @"XXXXXX"];

    // iOS Installation Step 4: Request notification permission from the user
    // Exclude if handling permissions from react native layer
    [PushNotificationsHelper requestNotificationPermission];
  } else {
    // Initialize cross-platform push library, e.g. Firebase
  }

  //-- ABC - refer to installation step 16 below
  NSMutableDictionary * launchOptionsWithURL = [self getLaunchOptionsWithURL:launchOptions];

  // return [super application:application didFinishLaunchingWithOptions:launchOptions];
  return [super application:application didFinishLaunchingWithOptions:launchOptionsWithURL];
}

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
  return [self bundleURL];
}

- (NSURL *)bundleURL
{
#if DEBUG
  return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
  return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}

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

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

//-- ABC - iOS Installation Step 6: Implement this delegate to receive and set the push token
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
  if (useNativeImplementation) {
    // iOS Installation Step 7: set the push token to Klaviyo SDK
    // Exclude if handling push tokens from react native layer
    [PushNotificationsHelper setPushTokenWithToken:deviceToken];
  } else {
    // Provide token to cross-platform push library, e.g. firebase
  }

  if (isDebug) {
      NSString *token = [self stringFromDeviceToken:deviceToken];
      NSLog(@"Device Token: %@", token);
  }

  return [super application:application didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
}

//-- ABC - iOS Installation Step 8: [Optional] Implement this if registering with APNs fails
// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
  if (isDebug) {
    NSLog(@"Failed to register for remote notifications: %@", error);
  }

  return [super application:application didFailToRegisterForRemoteNotificationsWithError:error];
}


//-- ABC - iOS Installation Step 9: Implement the delegate didReceiveNotificationResponse to response to user actions (tapping on push) push notifications
// when the app is in the background
// NOTE: this delegate will NOT be called if iOS Installation Step 2 is not done.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
  // iOS Installation Step 10: call `handleReceivingPushWithResponse` method and pass in the below arguments.
  // Note that handleReceivingPushWithResponse calls our SDK and is something that has to be implemented in your app as well.
  // Further, if you want to intercept urls instead of them being routed to the system and system calling `application:openURL:options:` you can implement the `deepLinkHandler` below
  [PushNotificationsHelper handleReceivingPushWithResponse:response completionHandler:completionHandler deepLinkHandler:^(NSURL * _Nonnull url) {
    NSLog(@"URL is %@", url);
    [RCTLinkingManager application:UIApplication.sharedApplication openURL: url options: @{}];
  }];

  // iOS Installation Step 9a: update the app count to current badge number - 1. You can also set this to 0 if you
  // no longer want the badge to show.
  [PushNotificationsHelper updateBadgeCount: [UIApplication sharedApplication].applicationIconBadgeNumber - 1];

  if (isDebug) {
    UIAlertController *alert = [UIAlertController
                                alertControllerWithTitle:@"Push Notification"
                                message:@"handled background notifications"
                                preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction
                      actionWithTitle:@"OK"
                      style:UIAlertActionStyleDefault
                      handler:nil]];
    [self.window.rootViewController presentViewController:alert animated:YES completion:nil];
  }
}


//-- ABC - iOS Installation Step 11: Implement the delegate willPresentNotification to handle push notifications when the app is in the foreground
// NOTE: this delegate will NOT be called if iOS Installation Step 2 is not done.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
       willPresentNotification:(UNNotification *)notification
         withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
  if (isDebug) {
    NSDictionary *userInfo = notification.request.content.userInfo;
    // Handle the push notification payload as needed
    NSLog(@"Received Push Notification: %@", userInfo);
    // Example: Display an alert with the notification message
    NSString *message = userInfo[@"aps"][@"alert"][@"body"];
    UIAlertController *alert = [UIAlertController
                                alertControllerWithTitle:@"Push Notification"
                                message:message
                                preferredStyle:UIAlertControllerStyleAlert];
    [alert addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]];
    [self.window.rootViewController presentViewController:alert animated:YES completion:nil];
  }

  // iOS Installation Step 12: call the completion handler with presentation options here, such as showing a banner or playing a sound.
  completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound);
}


//-- ABC - iOS Installation Step 13: Implement this method to receive deep link. There are some addition setup steps needed as mentioned in the readme here -
// https://github.com/klaviyo/klaviyo-swift-sdk?tab=readme-ov-file#deep-linking
// Calling `RCTLinkingManager` is required for your react native listeners to be called
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
  return [RCTLinkingManager application:app openURL:url options:options];
}


//-- ABC - iOS Installation Step 14: if you want to reset the app badge count whenever the app becomes active implement this
// delegate method and set the badge count to 0. Note that this may sometimes mean that the user would miss the
// notification.
- (void)applicationDidBecomeActive:(UIApplication *)application {
  [PushNotificationsHelper updateBadgeCount:0];
}


//-- ABC - iOS Installation Step 16: call this method from `application:didFinishLaunchingWithOptions:` before calling the super class method with
// the launch arguments. This is a workaround for a react issue where if the app is terminated the deep link isn't sent to the react native layer
// when it is coming from a remote notification.
// https://github.com/facebook/react-native/issues/32350
- (NSMutableDictionary *)getLaunchOptionsWithURL:(NSDictionary * _Nullable)launchOptions {
  NSMutableDictionary *launchOptionsWithURL = [NSMutableDictionary dictionaryWithDictionary:launchOptions];
  if (launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]) {
    NSDictionary *remoteNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];

    if (remoteNotification[@"url"]) {
      NSString *initialURL = remoteNotification[@"url"];
      if (!launchOptions[UIApplicationLaunchOptionsURLKey]) {
        launchOptionsWithURL[UIApplicationLaunchOptionsURLKey] = [NSURL URLWithString:initialURL];
      }
    }
  }
  return launchOptionsWithURL;
}


//-- ABC
- (NSString *)stringFromDeviceToken:(NSData *)deviceToken {
    const unsigned char *tokenBytes = (const unsigned char *)[deviceToken bytes];
    NSMutableString *token = [NSMutableString stringWithCapacity:([deviceToken length] * 2)];
    for (NSUInteger i = 0; i < [deviceToken length]; i++) {
        [token appendFormat:@"%02x", tokenBytes[i]];
    }
    return token;
}

// Explicitly define remote notification delegates to ensure compatibility with some third-party libraries
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
  return [super application:application didReceiveRemoteNotification:userInfo fetchCompletionHandler:completionHandler];
}

@end

Podfile:


use_modular_headers!

require File.join(File.dirname(`node --print "require.resolve('expo/package.json')"`), "scripts/autolinking")
require File.join(File.dirname(`node --print "require.resolve('react-native/package.json')"`), "scripts/react_native_pods")

require 'json'
podfile_properties = JSON.parse(File.read(File.join(__dir__, 'Podfile.properties.json'))) rescue {}

ENV['RCT_NEW_ARCH_ENABLED'] = podfile_properties['newArchEnabled'] == 'true' ? '1' : '0'
ENV['EX_DEV_CLIENT_NETWORK_INSPECTOR'] = podfile_properties['EX_DEV_CLIENT_NETWORK_INSPECTOR']

use_autolinking_method_symbol = ('use' + '_native' + '_modules!').to_sym
origin_autolinking_method = self.method(use_autolinking_method_symbol)
self.define_singleton_method(use_autolinking_method_symbol) do |*args|
  if ENV['EXPO_UNSTABLE_CORE_AUTOLINKING'] == '1'
    Pod::UI.puts('Using expo-modules-autolinking as core autolinking source'.green)
    config_command = [
      'node',
      '--no-warnings',
      '--eval',
      'require(require.resolve('expo-modules-autolinking', { paths: [require.resolve('expo/package.json')] }))(process.argv.slice(1))',
      'react-native-config',
      '--json',
      '--platform',
      'ios'
    ]
    origin_autolinking_method.call(config_command)
  else
    origin_autolinking_method.call()
  end
end

platform :ios, podfile_properties['ios.deploymentTarget'] || '13.4'
install! 'cocoapods',
  :deterministic_uuids => false

prepare_react_native_project!

target 'Anadey' do
  use_expo_modules!
  config = use_native_modules!

  use_frameworks! :linkage => podfile_properties['ios.useFrameworks'].to_sym if podfile_properties['ios.useFrameworks']
  use_frameworks! :linkage => ENV['USE_FRAMEWORKS'].to_sym if ENV['USE_FRAMEWORKS']

  use_react_native!(
    :path => config[:reactNativePath],
    :hermes_enabled => podfile_properties['expo.jsEngine'] == nil || podfile_properties['expo.jsEngine'] == 'hermes',
    # An absolute path to your application root.
    :app_path => "#{Pod::Config.instance.installation_root}/..",
    :privacy_file_aggregation_enabled => podfile_properties['apple.privacyManifestAggregationEnabled'] != 'false',
  )

  post_install do |installer|
    react_native_post_install(
      installer,
      config[:reactNativePath],
      :mac_catalyst_enabled => false,
      :ccache_enabled => podfile_properties['apple.ccacheEnabled'] == 'true',
    )

    # This is necessary for Xcode 14, because it signs resource bundles by default
    # when building for devices.
    installer.target_installation_results.pod_target_installation_results
      .each do |pod_name, target_installation_result|
      target_installation_result.resource_bundle_targets.each do |resource_bundle_target|
        resource_bundle_target.build_configurations.each do |config|
          config.build_settings['CODE_SIGNING_ALLOWED'] = 'NO'
        end
      end
    end
  end

  post_integrate do |installer|
    begin
      expo_patch_react_imports!(installer)
    rescue => e
      Pod::UI.warn e
    end
  end
end

# target 'AnadeyExtension' do
#   use_frameworks! :linkage => :static
#   pod 'KlaviyoSwiftExtension'
# end

Podfile.lock: codeshare link
( I pasted in CodeShare because the body length (66k) went above Stackoverflow’s limit of 30k characters)

I have cleared the Derived Data folder also.

Here is a list of errors that I found when opened Xcode:

enter image description here

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật