Skip to content

Commit a44a582

Browse files
authored
Bridge initialize and setPushToken to RN layer (#98)
* Add an initialize method to react native layer * Add set push token to RN layer * Updated to latest versions of native SDKs * Elaboration on example app initialize/setPushToken, use a consistent value for example API key * SCREAMING_SNAKE_CASE --------- Co-authored-by: Evan Masseau <>
1 parent ed0836a commit a44a582

20 files changed

+165
-34
lines changed

Diff for: .github/actions/setup/action.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@ runs:
2222
${{ runner.os }}-yarn-
2323
2424
- name: Install dependencies
25-
if: steps.yarn-cache.outputs.cache-hit != 'true'
25+
if: steps.yarn-cache.outputs.cache-hit != 'true' || ${{ github.run_attempt > 1 }}
2626
run: yarn install --immutable
2727
shell: bash

Diff for: .github/workflows/android-ci.yml

+3-3
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,19 @@ jobs:
4040
fi
4141
4242
- name: Install JDK
43-
if: env.turbo_cache_hit != 1
43+
if: env.turbo_cache_hit != 1 || ${{ github.run_attempt > 1 }}
4444
uses: actions/setup-java@v4
4545
with:
4646
distribution: 'zulu'
4747
java-version: '17'
4848

4949
- name: Finalize Android SDK
50-
if: env.turbo_cache_hit != 1
50+
if: env.turbo_cache_hit != 1 || ${{ github.run_attempt > 1 }}
5151
run: |
5252
/bin/bash -c "yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses > /dev/null"
5353
5454
- name: Cache Gradle
55-
if: env.turbo_cache_hit != 1
55+
if: env.turbo_cache_hit != 1 || ${{ github.run_attempt > 1 }}
5656
uses: actions/cache@v4
5757
with:
5858
path: |

Diff for: android/build.gradle

+3-2
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ repositories {
102102
google()
103103
}
104104

105+
def klaviyoAndroidSdkVersion = getExtOrDefault("klaviyoAndroidSdkVersion")
105106
def kotlin_version = getExtOrDefault("kotlinVersion")
106107
def localProperties = new Properties()
107108
if (rootProject.file("local.properties").canRead()) {
@@ -126,8 +127,8 @@ dependencies {
126127
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
127128

128129
// Klaviyo Android SDK
129-
api "com.github.klaviyo.klaviyo-android-sdk:analytics:2.0.0"
130-
api "com.github.klaviyo.klaviyo-android-sdk:push-fcm:2.0.0"
130+
api "com.github.klaviyo.klaviyo-android-sdk:analytics:$klaviyoAndroidSdkVersion"
131+
api "com.github.klaviyo.klaviyo-android-sdk:push-fcm:$klaviyoAndroidSdkVersion"
131132

132133
// We used reflection to enumerate keywords in the Klaviyo Android SDK dynamically
133134
implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.21"

Diff for: android/gradle.properties

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryErro
1111
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
1212
# org.gradle.parallel=true
1313
#Tue Dec 19 15:08:27 EST 2023
14+
KlaviyoReactNativeSdk_klaviyoAndroidSdkVersion=2.1.0
1415
KlaviyoReactNativeSdk_compileSdkVersion=31
1516
KlaviyoReactNativeSdk_kotlinVersion=1.8.0
1617
KlaviyoReactNativeSdk_minSdkVersion=23

Diff for: android/src/main/java/com/klaviyoreactnativesdk/KlaviyoReactNativeSdkModule.kt

+16
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ class KlaviyoReactNativeSdkModule internal constructor(private val context: Reac
4545
}
4646
}
4747

48+
@ReactMethod
49+
override fun initialize(apiKey: String) {
50+
Klaviyo.initialize(apiKey, context)
51+
}
52+
4853
@ReactMethod
4954
override fun setProfile(profile: ReadableMap) {
5055
val parsedProfile = Profile()
@@ -57,6 +62,7 @@ class KlaviyoReactNativeSdkModule internal constructor(private val context: Reac
5762
parsedProfile[key] = value
5863
}
5964
}
65+
6066
else ->
6167
if (value is Serializable) {
6268
parsedProfile[key] = value
@@ -110,6 +116,16 @@ class KlaviyoReactNativeSdkModule internal constructor(private val context: Reac
110116
Klaviyo.resetProfile()
111117
}
112118

119+
@ReactMethod
120+
override fun setPushToken(token: String) {
121+
Klaviyo.setPushToken(token)
122+
}
123+
124+
@ReactMethod
125+
override fun getPushToken(callback: Callback) {
126+
callback.invoke(Klaviyo.getPushToken())
127+
}
128+
113129
@ReactMethod
114130
override fun createEvent(event: ReadableMap) {
115131
val klaviyoEvent =

Diff for: android/src/oldarch/KlaviyoReactNativeSdkSpec.kt

+6
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import com.facebook.react.bridge.ReadableMap
77

88
abstract class KlaviyoReactNativeSdkSpec internal constructor(context: ReactApplicationContext) :
99
ReactContextBaseJavaModule(context) {
10+
abstract fun initialize(apiKey: String)
11+
1012
abstract fun setProfile(profile: ReadableMap)
1113

1214
abstract fun setExternalId(externalId: String)
@@ -28,5 +30,9 @@ abstract class KlaviyoReactNativeSdkSpec internal constructor(context: ReactAppl
2830

2931
abstract fun resetProfile()
3032

33+
abstract fun setPushToken(token: String)
34+
35+
abstract fun getPushToken(callback: Callback)
36+
3137
abstract fun createEvent(event: ReadableMap)
3238
}

Diff for: example/android/app/src/main/java/com/klaviyoreactnativesdkexample/MainApplication.kt

+9-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ import com.facebook.soloader.SoLoader
1414
import com.klaviyo.analytics.Klaviyo
1515

1616
class MainApplication : Application(), ReactApplication {
17+
companion object {
18+
const val USE_NATIVE_IMPLEMENTATION = true
19+
}
20+
1721
override val reactNativeHost: ReactNativeHost =
1822
object : DefaultReactNativeHost(this) {
1923
override fun getPackages(): List<ReactPackage> {
@@ -36,8 +40,11 @@ class MainApplication : Application(), ReactApplication {
3640
override fun onCreate() {
3741
super.onCreate()
3842
SoLoader.init(this, false)
39-
Klaviyo.initialize(BuildConfig.PUBLIC_API_KEY, this)
40-
registerActivityLifecycleCallbacks(Klaviyo.lifecycleCallbacks)
43+
44+
if (USE_NATIVE_IMPLEMENTATION) {
45+
// If initializing from the native layer:
46+
Klaviyo.initialize(BuildConfig.PUBLIC_API_KEY, this)
47+
}
4148

4249
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
4350
// If you opted-in for the New Architecture, we load the native entry point for this app.

Diff for: example/android/gradle.properties

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,4 @@ hermesEnabled=true
4242

4343
# Public API Key for your Company
4444
# or override this from an untracked local.properties file
45-
publicApiKey=YOUR_PUBLIC_API_KEY
45+
publicApiKey=YOUR_PUBLIC_KLAVIYO_API_KEY

Diff for: example/android/local.properties.template

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
# Only the base template should be checked in to VCS.
55
# Copy this file to `local.properties` to set your local overrides
66

7-
publicApiKey=YOUR_PUBLIC_API_KEY
7+
publicApiKey=YOUR_PUBLIC_KLAVIYO_API_KEY

Diff for: example/ios/KlaviyoReactNativeSdkExample/AppDelegate.mm

+27-14
Original file line numberDiff line numberDiff line change
@@ -9,29 +9,42 @@ @implementation AppDelegate
99
// Change to NO if you don't want debug alerts or logs.
1010
BOOL isDebug = YES;
1111

12+
// Change to NO if you prefer to initialize and handle push tokens in the React Native layer
13+
BOOL useNativeImplementation = YES;
1214

1315
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
1416
{
1517
self.moduleName = @"KlaviyoReactNativeSdkExample";
1618
self.initialProps = @{};
17-
19+
1820
// Installation Step 2: Set the UNUserNotificationCenter delegate to self
1921
[UNUserNotificationCenter currentNotificationCenter].delegate = self;
2022

21-
// Installation Step 3: Initilize the SDK with public key.
22-
[PushNotificationsHelper initializeSDK: @"YOUR_COMPANY_API_KEY_HERE"];
23-
24-
// Installation Step 4: Request push permission from the user
25-
[PushNotificationsHelper requestPushPermission];
23+
if (useNativeImplementation) {
24+
// Installation Step 3: Initialize the SDK with public key.
25+
// Exclude if initializing from react native layer
26+
[PushNotificationsHelper initializeSDK: @"YOUR_PUBLIC_KLAVIYO_API_KEY"];
27+
28+
// Installation Step 4: Request push permission from the user
29+
// Exclude if handling permissions from react native layer
30+
[PushNotificationsHelper requestPushPermission];
31+
} else {
32+
// Initialize cross-platform push library, e.g. Firebase
33+
}
2634

2735
return [super application:application didFinishLaunchingWithOptions:launchOptions];
2836
}
2937

3038
// Installation Step 6: Implement this delegate to receive and set the push token
3139
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
32-
// Installation Step 7: set the push token to Klaviyo SDK
33-
[PushNotificationsHelper setPushTokenWithToken:deviceToken];
34-
40+
if (useNativeImplementation) {
41+
// Installation Step 7: set the push token to Klaviyo SDK
42+
// Exclude if handling push tokens from react native layer
43+
[PushNotificationsHelper setPushTokenWithToken:deviceToken];
44+
} else {
45+
// Provide token to cross-platform push library, e.g. firebase
46+
}
47+
3548
if (isDebug) {
3649
NSString *token = [self stringFromDeviceToken:deviceToken];
3750
NSLog(@"Device Token: %@", token);
@@ -49,20 +62,20 @@ - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotif
4962
// when the app is in the background
5063
// NOTE: this delegate will NOT be called if Installation Step 2 is not done.
5164
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler {
52-
// Installation Step 10: call `handleReceivingPushWithResponse` method and pass in the below arguments. Note that handleReceivingPushWithResponse calls our SDK and is
53-
// something that has to be implemented in your app as well.
54-
// 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
65+
// Installation Step 10: call `handleReceivingPushWithResponse` method and pass in the below arguments.
66+
// Note that handleReceivingPushWithResponse calls our SDK and is something that has to be implemented in your app as well.
67+
// 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
5568
[PushNotificationsHelper handleReceivingPushWithResponse:response completionHandler:completionHandler deepLinkHandler:^(NSURL * _Nonnull url) {
5669
NSLog(@"URL is %@", url);
5770
[RCTLinkingManager application:UIApplication.sharedApplication openURL: url options: @{}];
5871
}];
59-
72+
6073
if (isDebug) {
6174
UIAlertController *alert = [UIAlertController
6275
alertControllerWithTitle:@"Push Notification"
6376
message:@"handled background notifications"
6477
preferredStyle:UIAlertControllerStyleAlert];
65-
[alert addAction:[UIAlertAction
78+
[alert addAction:[UIAlertAction
6679
actionWithTitle:@"OK"
6780
style:UIAlertActionStyleDefault
6881
handler:nil]];

Diff for: example/ios/KlaviyoReactNativeSdkExample/PushNotificationsHelper.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ class PushNotificationsHelper: NSObject {
1919
if let error = error {
2020
print("AuthError", "error while trying to authorize push", error)
2121
} else {
22-
// Installation Step 5: register for remote notifications after requesting permission on the main thread
22+
// Installation Step 5: register for remote notifications after requesting permission on the main thread
2323
DispatchQueue.main.async {
2424
UIApplication.shared.registerForRemoteNotifications()
2525
}

Diff for: example/ios/Podfile.lock

+5-5
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ PODS:
7373
- hermes-engine/Pre-built (= 0.73.1)
7474
- hermes-engine/Pre-built (0.73.1)
7575
- klaviyo-react-native-sdk (0.1.2):
76-
- KlaviyoSwift (= 3.0.2)
76+
- KlaviyoSwift (= 3.0.3)
7777
- React-Core
78-
- KlaviyoSwift (3.0.2):
78+
- KlaviyoSwift (3.0.3):
7979
- AnyCodable-FlightSchool
8080
- libevent (2.1.12)
8181
- OpenSSL-Universal (1.1.1100)
@@ -1333,8 +1333,8 @@ SPEC CHECKSUMS:
13331333
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
13341334
glog: c5d68082e772fa1c511173d6b30a9de2c05a69a2
13351335
hermes-engine: 34df9d5034e90bd9bf1505e1ca198760373935af
1336-
klaviyo-react-native-sdk: bb23fe38c4794e14613f0e1013acb8ffcf1834ba
1337-
KlaviyoSwift: b3454b6c177b67b72b0afa1b77f6bf6281e9c754
1336+
klaviyo-react-native-sdk: 2f9cd36e7fed062a0d76484a383f4a5dc3472183
1337+
KlaviyoSwift: 9318efaa92f5435d63aa093e3e9d49af947b7685
13381338
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
13391339
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
13401340
RCT-Folly: 7169b2b1c44399c76a47b5deaaba715eeeb476c0
@@ -1383,4 +1383,4 @@ SPEC CHECKSUMS:
13831383

13841384
PODFILE CHECKSUM: 4b9faf5e512fab6e8d87beb30570ec4d3bd884b9
13851385

1386-
COCOAPODS: 1.14.3
1386+
COCOAPODS: 1.15.2

Diff for: example/src/AppViewInterface.ts

+12
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {
2+
initialize,
23
getEmail,
34
getExternalId,
45
getPhoneNumber,
56
resetProfile,
7+
setPushToken,
68
sendRandomEvent,
79
setEmail,
810
setExternalId,
@@ -18,6 +20,11 @@ export interface AppViewInterface {
1820
}
1921

2022
export const appViews: AppViewInterface[] = [
23+
{
24+
title: 'Click to initialize',
25+
color: '#841584',
26+
onPress: initialize,
27+
},
2128
{
2229
title: 'Click to set the full profile',
2330
color: '#841584',
@@ -48,6 +55,11 @@ export const appViews: AppViewInterface[] = [
4855
color: '#ffcccb',
4956
onPress: resetProfile,
5057
},
58+
{
59+
title: 'Click to set a FAKE push token',
60+
color: '#ffcccb',
61+
onPress: setPushToken,
62+
},
5163
{
5264
title: 'Click to get current email',
5365
color: '#841584',

Diff for: example/src/KlaviyoReactWrapper.ts

+19
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,15 @@ import {
1414
getRandomMetric,
1515
} from './RandomGenerators';
1616

17+
export const initialize = async () => {
18+
try {
19+
// If initializing from RN Layer: (replace with your public key)
20+
Klaviyo.initialize('YOUR_PUBLIC_KLAVIYO_API_KEY');
21+
} catch (e: any) {
22+
console.log(e.message, e.code);
23+
}
24+
};
25+
1726
export const setEmail = async () => {
1827
try {
1928
Klaviyo.setEmail(generateRandomEmails());
@@ -76,6 +85,16 @@ export const resetProfile = async () => {
7685
}
7786
};
7887

88+
export const setPushToken = async () => {
89+
try {
90+
// If handling push tokens from the react native layer
91+
// You would need a cross-platform push library to fetch the device token, e.g. firebase
92+
Klaviyo.setPushToken('FAKE_PUSH_TOKEN');
93+
} catch (e: any) {
94+
console.log(e.message, e.code);
95+
}
96+
};
97+
7998
export const setProfileAttribute = async () => {
8099
try {
81100
Klaviyo.setProfileAttribute('CUSTOM', generateRandomName(12));

Diff for: ios/KlaviyoBridge.swift

+10
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ public class KlaviyoBridge: NSObject {
4242
ProfileProperty.allCases.getDictionaryFromEnum()
4343
}
4444

45+
@objc
46+
public static func initialize(_ apiKey: String) {
47+
KlaviyoSDK().initialize(with: apiKey)
48+
}
49+
4550
@objc
4651
public static func setProfile(
4752
_ profileDict: [String: AnyObject]
@@ -107,6 +112,11 @@ public class KlaviyoBridge: NSObject {
107112
return KlaviyoSDK().phoneNumber ?? ""
108113
}
109114

115+
@objc
116+
public static func setPushToken(_ value: String) {
117+
KlaviyoSDK().set(pushToken: value)
118+
}
119+
110120
@objc
111121
public static func resetProfile() {
112122
KlaviyoSDK().resetProfile()

Diff for: ios/KlaviyoReactNativeSdk.mm

+9-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,10 @@ - (NSDictionary *)constantsToExport {
1717
};
1818
}
1919

20-
//MARK: Setters
20+
RCT_EXPORT_METHOD(initialize: (NSString *)apiKey)
21+
{
22+
[KlaviyoBridge initialize: apiKey];
23+
}
2124

2225
RCT_EXPORT_METHOD(setProfile: (NSDictionary *)profileDict)
2326
{
@@ -54,6 +57,11 @@ - (NSDictionary *)constantsToExport {
5457
callback(@[phoneNumber]);
5558
}
5659

60+
RCT_EXPORT_METHOD(setPushToken: (NSString *)pushToken)
61+
{
62+
[KlaviyoBridge setPushToken: pushToken];
63+
}
64+
5765
RCT_EXPORT_METHOD(resetProfile)
5866
{
5967
[KlaviyoBridge resetProfile];

Diff for: klaviyo-react-native-sdk.podspec

+2-2
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ require "json"
33
package = JSON.parse(File.read(File.join(__dir__, "package.json")))
44

55
Pod::Spec.new do |s|
6-
s.name = package["name"]
6+
s.name = package["name"]
77
s.version = package["version"]
88
s.summary = package["description"]
99
s.homepage = package["homepage"]
@@ -17,5 +17,5 @@ Pod::Spec.new do |s|
1717
s.pod_target_xcconfig = { "DEFINES_MODULE" => "YES" }
1818

1919
s.dependency "React-Core"
20-
s.dependency "KlaviyoSwift", "3.0.2"
20+
s.dependency "KlaviyoSwift", "3.0.3"
2121
end

0 commit comments

Comments
 (0)