diff --git a/README.md b/README.md index 8226929..d158f55 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,38 @@ [![Android](https://github.com/klaviyo/klaviyo-react-native-sdk/actions/workflows/android-ci.yml/badge.svg?branch=master&event=push)](https://github.com/klaviyo/klaviyo-react-native-sdk/actions/workflows/android-ci.yml) [![iOS](https://github.com/klaviyo/klaviyo-react-native-sdk/actions/workflows/ios-ci.yml/badge.svg?branch=master&event=push)](https://github.com/klaviyo/klaviyo-react-native-sdk/actions/workflows/ios-ci.yml) -⚠️ This repository is in beta development ⚠️ +> ⚠️ This repository is in beta development ⚠️ + +## Contents + +- [klaviyo-react-native-sdk](#klaviyo-react-native-sdk) + - [Introduction](#introduction) + - [Requirements](#requirements) + - [React Native](#react-native) + - [Android](#android) + - [iOS](#ios) + - [Installation](#installation) + - [Android](#android-1) + - [iOS](#ios-1) + - [Initialization](#initialization) + - [Identifying a Profile](#identifying-a-profile) + - [Reset Profile](#reset-profile) + - [Anonymous Tracking](#anonymous-tracking) + - [Event Tracking](#event-tracking) + - [Push Notifications](#push-notifications) + - [Prerequisites](#prerequisites) + - [Setup](#setup) + - [Collecting Push Tokens](#collecting-push-tokens) + - [Receiving Push Notifications](#receiving-push-notifications) + - [Rich Push](#rich-push) + - [Tracking Open Events](#tracking-open-events) + - [Deep Linking](#deep-linking) + - [Troubleshooting](#troubleshooting) + - [Contributing](#contributing) + - [License](#license) + - [Code Documentation](#code-documentation) + +## Introduction The Klaviyo React Native SDK allows developers to incorporate Klaviyo analytics and push notification functionality in their React Native applications for Android and iOS. It is a Typescript wrapper (native module bridge) around the native @@ -19,21 +50,26 @@ Once integrated, your marketing team will be able to better understand your app push notifications via FCM (Firebase Cloud Messaging) and APNs (Apple Push Notification Service). ## Requirements + For initial beta release, the SDK was developed and tested against the latest minor release of React Native (0.73). We are actively testing and expanding support to the latest patch releases of recent minor versions of React Native. ### React Native + - `0.68.7+` - We have successfully compiled this SDK on a bare React Native template app down to `0.68.7`. Testing is ongoing to verify on older versions. ### Android + - `minSdkVersion` of `23+` - `compileSdkVersion` of `34+` ### iOS + - Minimum Deployment Target `13.0+` ## Installation + The Klaviyo React Native SDK is available via [NPM](http://npmjs.com). To add it to your project, run the following from your project's root directory: @@ -58,74 +94,52 @@ delegate methods on iOS. To actually run the example app: metro instructions from here, i.e. press `i` to run on iOS or `a` to run on Android. ### Android + Android installation requirements may vary depending upon your project configuration and other dependencies. The Klaviyo React Native SDK's `build.gradle` file exposes transitive dependencies upon the Klaviyo Android SDK, so you can import Android Klaviyo SDK references from your Kotlin/Java files without modifying your gradle configuration. #### React Native 0.73.x + There are no additional installation requirements. Android support is fully tested and verified, including `minSdkVersion=23`. #### React Native 0.68.x - 0.72.x + We have successfully compiled the Klaviyo React Native SDK in a bare React Native template app for these versions with the following modifications to the `android/build.gradle` file: + - Set `compileSdkVersion=34` - Set `minSdkVersion=23` #### React Native <= 0.67.x -We are actively working to verify compatibility with these versions. If you encounter issues, please file an issue. -#### Android Troubleshooting -- We have seen projects, particularly on react-native versions `0.72.x` and `0.71.x`, that required a `minSdkVersion` - of `24`, despite the Klaviyo Android SDK supporting API 23+. If you encounter this, please file an issue in our - repository and provide version numbers of your react-native dependencies. +We are actively working to verify compatibility with these versions. If you encounter issues, please file an issue. ### iOS + After installing the npm package, run the following command in the `ios` directory of your React Native project. Install [Cocoapods](https://cocoapods.org/) if you have not already. + ```sh pod install ``` -> ℹ️ if the above command is outputting version mismatch errors for `KlaviyoSwift`, please run `pod update KlaviyoSwift` as indicated in the error message to update your local pods spec repo. - -#### iOS Troubleshooting -If you are seeing issues related to `minimum deployment target` when installing pods, you may need to update your -minimum iOS version to 13.0 in your Podfile with one of the following strategies. -- Specify iOS version directly in the `Podfile`: - ```ruby - MIN_IOS_OVERRIDE = '13.0' - if Gem::Version.new(MIN_IOS_OVERRIDE) > Gem::Version.new(min_ios_version_supported) - min_ios_version_supported = MIN_IOS_OVERRIDE - end - # existing code - platform :ios, min_ios_version_supported - ``` -- Set the deployment target to 13.0 in XCode, and then pull `IPHONEOS_DEPLOYMENT_TARGET` from the XCode project: - ```ruby - ####### - # Read min iOS version from Xcode project and set as min iOS version for Podfile - require 'xcodeproj' - - project_path = './YOUR_XCODE_PROJECT.xcodeproj' - project = Xcodeproj::Project.open(project_path) - min_ios_version_supported = project.build_configurations.first.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] - ###### - - platform :ios, min_ios_version_supported - ``` ## Initialization + The SDK must be initialized with the short alphanumeric [public API key](https://help.klaviyo.com/hc/en-us/articles/115005062267#difference-between-public-and-private-api-keys1) for your Klaviyo account, also known as your Site ID. Initialization is done in the native layer, and must occur before any other SDK methods can be invoked. Follow the native SDK instructions for initialization, and refer to the [example app](./example) in this repository for guidance: + - [Android SDK instructions](https://github.com/klaviyo/klaviyo-android-sdk#Initialization), and [example app `MainApplication.kt`](./example/android/app/src/main/java/com/klaviyoreactnativesdkexample/MainApplication.kt#L39) - [iOS SDK instructions](https://github.com/klaviyo/klaviyo-swift-sdk#Initialization), and [example app `AppDelegate.mm`](./example/ios/KlaviyoReactNativeSdkExample/AppDelegate.mm#L14) ## Identifying a Profile + The SDK provides methods to identify profiles via the [Create Client Profile API](https://developers.klaviyo.com/en/reference/create_client_profile). A profile can be identified by any combination of the following: @@ -172,6 +186,7 @@ Klaviyo.setProfileAttribute(ProfilePropertyKey.FIRST_NAME, 'Kermit'); Either way, the native SDKs will group and batch API calls to improve performance. ### Reset Profile + To start a _new_ profile altogether (e.g. if a user logs out), either call `Klaviyo.resetProfile()` to clear the currently tracked profile identifiers (e.g. on logout), or use `Klaviyo.setProfile(profile)` to overwrite it with a new profile object. @@ -183,11 +198,13 @@ Klaviyo.resetProfile(); ``` ### Anonymous Tracking + Klaviyo will track unidentified users with an autogenerated ID whenever a push token is set or an event is created. That way, you can collect push tokens and track events prior to collecting profile identifiers such as email or phone number. When an identifier is provided, Klaviyo will merge the anonymous user with an identified user. ## Event Tracking + The SDK also provides tools for tracking analytics events via the [Create Client Event API](https://developers.klaviyo.com/en/reference/create_client_event). A list of common Klaviyo-defined event metrics is provided in [`MetricName`](https://github.com/klaviyo/klaviyo-react-native-sdk/blob/master/src/Event.ts), @@ -220,63 +237,78 @@ Klaviyo.createEvent({ ## Push Notifications ### Prerequisites + Integrating push notifications is highly platform-specific. Begin by thoroughly reviewing the setup instructions for Push Notifications in the README from each native Klaviyo SDK: + - [Android](https://github.com/klaviyo/klaviyo-android-sdk#Push-Notifications) - [iOS](https://github.com/klaviyo/klaviyo-swift-sdk#Push-Notifications) ### Setup + Refer to the following README sections on push setup: + - [Android](https://github.com/klaviyo/klaviyo-android-sdk#Setup) - [iOS](https://github.com/klaviyo/klaviyo-swift-sdk#Setup) ### Collecting Push Tokens + Push tokens must be collected in the native layer. Follow the platform-specific instructions below: + - [Android](https://github.com/klaviyo/klaviyo-android-sdk#Collecting-Push-Tokens) - [iOS](https://github.com/klaviyo/klaviyo-swift-sdk#Collecting-Push-Tokens) #### Notification Permission + Requesting user permission to display notifications can be managed in the native layer as instructed in our native SDK documentation, or with a third party library that provides cross-platform permissions APIs. If you opt for a cross-platform permission solution, you will still need to provide the Klaviyo SDK with the push token from the native layer after a permission change. ### Receiving Push Notifications + You can send test notifications to a specific token using the [push notification preview](https://help.klaviyo.com/hc/en-us/articles/18011985278875) feature in order to test your integration. #### Rich Push + [Rich Push](https://help.klaviyo.com/hc/en-us/articles/16917302437275) is the ability to add images to push notification messages. On iOS, you will need to implement an extension service to attach images to notifications. No additional setup is needed to support rich push on Android. + - [Android](https://github.com/klaviyo/klaviyo-android-sdk#Rich-Push) - [iOS](https://github.com/klaviyo/klaviyo-swift-sdk#Rich-Push) #### Tracking Open Events + Klaviyo tracks push opens events with a specially formatted event `Opened Push` that includes message tracking parameters in the event properties. To track push opens, you will need to follow platform-specific instructions: + - [Android](https://github.com/klaviyo/klaviyo-android-sdk#Tracking-Open-Events) - [iOS](https://github.com/klaviyo/klaviyo-swift-sdk#Tracking-Open-Events) #### Deep Linking + [Deep Links](https://help.klaviyo.com/hc/en-us/articles/14750403974043) allow you to navigate to a particular page within your app in response to the user opening a notification. Familiarize yourself with the [React Native Guide](https://reactnative.dev/docs/linking) to deep linking, then read through the platform-specific instructions below. + - [Android](https://github.com/klaviyo/klaviyo-android-sdk#Deep-Linking) instructions for handling intent filters - [iOS](https://github.com/klaviyo/klaviyo-swift-sdk#Deep-Linking) As shown in the native SDK documentation, you can follow option 1 or 2. - With option 1, when you get the callback, you can handle it as follows: + + With option 1, when you handle the open url (in [`application(_:open:options)`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application)), + you call the linking code block above similar to what you would do with option 1. + + With option 2, when you get the `deepLinkHandler`, you can handle it as follows: ```objective-c - [RCTLinkingManager application:application openURL:url options:options] + [RCTLinkingManager application:UIApplication.sharedApplication openURL: url options: @{}]; ``` - Since you won't have `options`, you can just pass in an empty dictionary for that parameter. - - With option 2, when you handle the open url (in [`application(_:open:options)`](https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623112-application)), - you call the linking code block above similar to what you would do with option 1. + For application, you can pass in an instance of `UIApplication` and since you won't have `options`, you can just pass in an empty dictionary for that parameter. In your React Native code, you can handle the deep link as follows: @@ -292,13 +324,21 @@ Linking.getInitialURL().then((url) => { }); ``` +## Troubleshooting + +Use the [troubleshooting guide](Troubeshooting.md) to resolve common issues with the Klaviyo React Native SDK. +If the issues you are facing isn't in the troubleshooting guide, and you believe it's a bug in the SDK, please file an issue in our repository. + ## Contributing + Refer to the [contributing guide](.github/CONTRIBUTING.md) to learn how to contribute to the Klaviyo React Native SDK. We welcome your feedback in the [discussion](https://github.com/klaviyo/klaviyo-react-native-sdk/discussions) and [issues](https://github.com/klaviyo/klaviyo-react-native-sdk/issues) sections of our public GitHub repository. ## License + The Klaviyo React Native SDK is available under the terms of the MIT license. See [LICENSE](./LICENSE) for more info. ## Code Documentation + Browse complete autogenerated code documentation [here](https://klaviyo.github.io/klaviyo-react-native-sdk/). diff --git a/Troubeshooting.md b/Troubeshooting.md new file mode 100644 index 0000000..76cb3e9 --- /dev/null +++ b/Troubeshooting.md @@ -0,0 +1,41 @@ +# Troubleshooting Guide + +## Android Troubleshooting + +1. We have seen projects, particularly on react-native versions `0.72.x` and `0.71.x`, that required a `minSdkVersion` + of `24`, despite the Klaviyo Android SDK supporting API 23+. If you encounter this, please file an issue in our + repository and provide version numbers of your react-native dependencies. + +## iOS Troubleshooting + +### CocoaPods Installation Issues + +1. If you are seeing issues related to `minimum deployment target` when installing pods, you may need to update your + minimum iOS version to 13.0 in your Podfile with one of the following strategies. + + - Specify iOS version directly in the `Podfile`: + ```ruby + MIN_IOS_OVERRIDE = '13.0' + if Gem::Version.new(MIN_IOS_OVERRIDE) > Gem::Version.new(min_ios_version_supported) + min_ios_version_supported = MIN_IOS_OVERRIDE + end + # existing code + platform :ios, min_ios_version_supported + ``` + - Set the deployment target to 13.0 in XCode, and then pull `IPHONEOS_DEPLOYMENT_TARGET` from the XCode project: + + ```ruby + ####### + # Read min iOS version from Xcode project and set as min iOS version for Podfile + require 'xcodeproj' + + project_path = './YOUR_XCODE_PROJECT.xcodeproj' + project = Xcodeproj::Project.open(project_path) + min_ios_version_supported = project.build_configurations.first.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] + ###### + + platform :ios, min_ios_version_supported + ``` + +2. If the command `pod install` is outputting version mismatch errors for `KlaviyoSwift`, please run `pod update KlaviyoSwift` + as indicated in the error message to update your local pods spec repo. diff --git a/example/ios/KlaviyoReactNativeSdkExample/AppDelegate.mm b/example/ios/KlaviyoReactNativeSdkExample/AppDelegate.mm index 60c5e90..7b66561 100644 --- a/example/ios/KlaviyoReactNativeSdkExample/AppDelegate.mm +++ b/example/ios/KlaviyoReactNativeSdkExample/AppDelegate.mm @@ -1,6 +1,8 @@ #import "AppDelegate.h" #import +#import + @implementation AppDelegate @@ -31,9 +33,8 @@ - (void)application:(UIApplication *)application didRegisterForRemoteNotificatio [PushNotificationsHelper setPushTokenWithToken:deviceToken]; if (isDebug) { - NSString *token = [[deviceToken description] stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"<>"]]; - token = [token stringByReplacingOccurrencesOfString:@" " withString:@""]; - NSLog(@"Device Token: %@", token); + NSString *token = [self stringFromDeviceToken:deviceToken]; + NSLog(@"Device Token: %@", token); } } @@ -50,7 +51,11 @@ - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotif - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler { // 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. - [PushNotificationsHelper handleReceivingPushWithResponse:response completionHandler:completionHandler]; + // furthur, 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: @{}]; + }]; if (isDebug) { UIAlertController *alert = [UIAlertController @@ -90,12 +95,11 @@ - (void)userNotificationCenter:(UNUserNotificationCenter *)center // 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 -// additionally routing to the right screen in the app based on the url is also something that should be handled +// Calling `RCTLinkingManager` is required for your react native listeners to be called - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary *)options { - return [PushNotificationsHelper handleDeepLinksWithUrl:url]; + return [RCTLinkingManager application:app openURL:url options:options]; } - - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { return [self getBundleURL]; @@ -110,4 +114,13 @@ - (NSURL *)getBundleURL #endif } +- (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; +} + @end diff --git a/example/ios/KlaviyoReactNativeSdkExample/PushNotificationsHelper.swift b/example/ios/KlaviyoReactNativeSdkExample/PushNotificationsHelper.swift index 81b5c04..b808f89 100644 --- a/example/ios/KlaviyoReactNativeSdkExample/PushNotificationsHelper.swift +++ b/example/ios/KlaviyoReactNativeSdkExample/PushNotificationsHelper.swift @@ -33,21 +33,18 @@ class PushNotificationsHelper: NSObject { } @objc - static func handleReceivingPush(response: UNNotificationResponse, completionHandler: @escaping () -> Void ) { - let handled = KlaviyoSDK().handle(notificationResponse: response, withCompletionHandler: completionHandler) + static func handleReceivingPush( + response: UNNotificationResponse, + completionHandler: @escaping () -> Void, + deepLinkHandler: ((URL) -> Void)? = nil + ) { + let handled = KlaviyoSDK().handle( + notificationResponse: response, + withCompletionHandler: completionHandler, + deepLinkHandler: deepLinkHandler + ) if !handled { completionHandler() } } - - @objc - static func handleDeepLinks(url: URL) -> Bool { - guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true) - else { - print("Invalid deep linking URL") - return false - } - print("components: \(components.debugDescription)") - return true - } } diff --git a/example/src/App.tsx b/example/src/App.tsx index 7c586ff..e5a9d49 100644 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -1,10 +1,22 @@ import * as React from 'react'; +import { useEffect } from 'react'; + import { Button, View } from 'react-native'; import { type AppViewInterface, appViews } from './AppViewInterface'; import { styles } from './Styles'; +import { Linking } from 'react-native'; export default function App() { + useEffect(() => { + Linking.addEventListener('url', ({ url }) => { + console.log('Event Listener: url', url); + }); + Linking.getInitialURL().then((url) => { + console.log('Initial Url: url', url); + }); + }, []); + return ( <> diff --git a/yarn.lock b/yarn.lock index 6c1b4ca..9a54029 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7403,9 +7403,9 @@ __metadata: linkType: hard "ip@npm:^1.1.5, ip@npm:^1.1.8": - version: 1.1.8 - resolution: "ip@npm:1.1.8" - checksum: a2ade53eb339fb0cbe9e69a44caab10d6e3784662285eb5d2677117ee4facc33a64679051c35e0dfdb1a3983a51ce2f5d2cb36446d52e10d01881789b76e28fb + version: 1.1.9 + resolution: "ip@npm:1.1.9" + checksum: b6d91fd45a856e3bd6d4f601ea0619d90f3517638f6918ebd079f959a8a6308568d8db5ef4fdf037e0d9cfdcf264f46833dfeea81ca31309cf0a7eb4b1307b84 languageName: node linkType: hard