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