Skip to content

Commit a3d7d0a

Browse files
evan-masseauclaude
andcommitted
feat(example): native platform setup for Firebase push (iOS + Android)
Wire up native-level Firebase push integration with @react-native-firebase and Klaviyo, using the JS-first init pattern. Both platforms compile and launch without Firebase configured — push features are just disabled in that mode — and light up end-to-end when a Firebase config file is present. iOS: - AppDelegate.mm: guard [FIRApp configure] on GoogleService-Info.plist presence, wire UNUserNotificationCenter delegate, preserve deep-link + universal-link handlers, keep commented native-init reference block - Podfile: $RNFirebaseAsStaticFramework = true, static frameworks linkage - Info.plist: UIBackgroundModes = [fetch, location, remote-notification], location usage descriptions, scheme registration - Entitlements: aps-environment = development, wired via CODE_SIGN_ENTITLEMENTS in the Xcode project - Bundle id normalized to com.klaviyoreactnativesdkexample (matches Firebase app id and Android applicationId) - GoogleService-Info.plist reference added to the Xcode project so the file is bundled when integrators drop it in Android: - Remove dead initializeKlaviyoFromNative / publicApiKey / useNativeFirebase gradle→BuildConfig plumbing (nothing reads them) - Conditionally apply com.google.gms.google-services plugin on the presence of app/google-services.json — lets the project build cleanly without push configured - MainApplication.kt: clean commented reference for native init; primary init path stays in JS - Add google-services.json.template as a placeholder so the gradle plugin has something to resolve until integrators add their own Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
1 parent 85780f7 commit a3d7d0a

11 files changed

Lines changed: 352 additions & 484 deletions

File tree

android/local.properties.template

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
## Used for local SDK development, so that gradle can locate the
77
# correct RN version outside the context of an application
8-
reactNativeAndroidVersion=0.80.0
8+
reactNativeAndroidVersion=0.81.5
99

1010
# For local SDK development: set to true in your local.properties file
1111
# you must set separately for the example app and the library

example/android/app/build.gradle

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,6 @@ def enableProguardInReleaseBuilds = false
7171
* this variant is about 6MiB larger per architecture than default.
7272
*/
7373
def jscFlavor = 'io.github.react-native-community:jsc-android:2026004.+'
74-
def localProperties = new Properties()
75-
if (rootProject.file("local.properties").canRead()) {
76-
localProperties.load(new FileInputStream(rootProject.file("local.properties")))
77-
}
78-
79-
//Local properties that will be declared as build config fields:
80-
def apiKey = localProperties['publicApiKey'] ?: publicApiKey
81-
def initializeKlaviyoFromNative = localProperties['initializeKlaviyoFromNative'] ?: initializeKlaviyoFromNative
82-
def useNativeFirebase = localProperties['useNativeFirebase'] ?: useNativeFirebase
8374

8475
android {
8576
ndkVersion rootProject.ext.ndkVersion
@@ -108,19 +99,13 @@ android {
10899
buildTypes {
109100
debug {
110101
signingConfig signingConfigs.debug
111-
buildConfigField "String", "PUBLIC_API_KEY", "\"${apiKey}\""
112-
buildConfigField "Boolean", "INITIALIZE_KLAVIYO_FROM_NATIVE", "${initializeKlaviyoFromNative}"
113-
buildConfigField "Boolean", "USE_NATIVE_FIREBASE", "${useNativeFirebase}"
114102
}
115103
release {
116104
// Caution! In production, you need to generate your own keystore file.
117105
// see https://reactnative.dev/docs/signed-apk-android.
118106
signingConfig signingConfigs.debug
119107
minifyEnabled enableProguardInReleaseBuilds
120108
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
121-
buildConfigField "String", "PUBLIC_API_KEY", "\"${apiKey}\""
122-
buildConfigField "Boolean", "INITIALIZE_KLAVIYO_FROM_NATIVE", "${initializeKlaviyoFromNative}"
123-
buildConfigField "Boolean", "USE_NATIVE_FIREBASE", "${useNativeFirebase}"
124109
}
125110
}
126111
}
@@ -139,8 +124,11 @@ dependencies {
139124
}
140125
}
141126

142-
if (useNativeFirebase.toBoolean()) {
143-
// Note: this auto-initializes firebase from your google-services.json file.
144-
// You'll need to set applicationId to match your firebase project, see above
145-
apply plugin: "com.google.gms.google-services"
127+
// Apply the google-services plugin only when google-services.json is present.
128+
// This lets the example app build out of the box without a Firebase project.
129+
// To enable push: drop your Firebase project's google-services.json into
130+
// example/android/app/, and make sure its package_name matches applicationId
131+
// above. See example/README.md for full setup details.
132+
if (file('google-services.json').exists()) {
133+
apply plugin: "com.google.gms.google-services"
146134
}
Lines changed: 0 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,13 @@
11
package com.klaviyoreactnativesdkexample
22

3-
import android.Manifest
4-
import android.annotation.SuppressLint
5-
import android.app.AlertDialog
63
import android.content.Intent
74
import android.net.Uri
8-
import android.os.Build
95
import android.os.Bundle
10-
import android.provider.Settings
116
import android.util.Log
12-
import android.widget.Toast
13-
import androidx.activity.result.ActivityResultLauncher
14-
import androidx.activity.result.contract.ActivityResultContracts
15-
import androidx.core.app.ActivityCompat
16-
import androidx.core.app.NotificationManagerCompat
177
import com.facebook.react.ReactActivity
188
import com.facebook.react.ReactActivityDelegate
199
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
2010
import com.facebook.react.defaults.DefaultReactActivityDelegate
21-
import com.google.firebase.messaging.FirebaseMessaging
2211
import com.klaviyo.analytics.Klaviyo
2312

2413
class MainActivity : ReactActivity() {
@@ -34,87 +23,10 @@ class MainActivity : ReactActivity() {
3423
*/
3524
override fun createReactActivityDelegate(): ReactActivityDelegate = DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled)
3625

37-
/**
38-
* Launches a permission request, and receives the result in the callback below
39-
*/
40-
private var requestPermissionLauncher: ActivityResultLauncher<String> =
41-
registerForActivityResult(
42-
ActivityResultContracts.RequestPermission(),
43-
) { isGranted: Boolean ->
44-
// This is called with the result of the permission request
45-
val verb = if (isGranted) "granted" else "denied"
46-
Log.d("KlaviyoSampleApp", "Notification permission $verb")
47-
48-
// Android Installation Step 4c: After permission is granted, call setPushToken to update permission state
49-
if (isGranted) {
50-
if (BuildConfig.USE_NATIVE_FIREBASE) {
51-
FirebaseMessaging.getInstance().token.addOnSuccessListener {
52-
Log.d("KlaviyoSampleApp", "Push token set: $it")
53-
Klaviyo.setPushToken(it)
54-
Toast
55-
.makeText(
56-
this,
57-
"Permission granted! Push token set.",
58-
Toast.LENGTH_SHORT,
59-
).show()
60-
}
61-
} else {
62-
Toast
63-
.makeText(
64-
this,
65-
"Permission granted! Push token not set, because Firebase is not initialized natively.",
66-
Toast.LENGTH_SHORT,
67-
).show()
68-
}
69-
} else {
70-
Toast
71-
.makeText(
72-
this,
73-
"Permission denied",
74-
Toast.LENGTH_SHORT,
75-
).show()
76-
}
77-
}
78-
7926
override fun onCreate(savedInstanceState: Bundle?) {
8027
Log.v("KlaviyoSampleApp", "MainActivity.onCreate()")
8128
super.onCreate(savedInstanceState)
8229

83-
// Android Installation Step 4b: Request notification permission from the user, if handling push tokens natively
84-
if (BuildConfig.INITIALIZE_KLAVIYO_FROM_NATIVE) {
85-
// Note: it is not usually advised to prompt for permissions immediately upon app launch. This is just a sample.
86-
when {
87-
NotificationManagerCompat.from(this).areNotificationsEnabled() -> {
88-
// We have already notification permission
89-
Log.v("KlaviyoSampleApp", "Notification permission is granted")
90-
}
91-
92-
ActivityCompat.shouldShowRequestPermissionRationale(
93-
this,
94-
Manifest.permission.POST_NOTIFICATIONS,
95-
) -> {
96-
// Reachable on API level >= 33
97-
// If a permission prompt was previously denied, display an educational UI and request permission again
98-
Log.v("KlaviyoSampleApp", "Requesting notification permission with rationale")
99-
requestPermissionWithRationale()
100-
}
101-
102-
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU -> {
103-
// Reachable on API Level >= 33
104-
// We can request the permission
105-
Log.v("KlaviyoSampleApp", "Requesting notification permission")
106-
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
107-
}
108-
109-
else -> {
110-
// Reachable on API Level < 33
111-
// DENIED - Notifications were turned off by the user in system settings
112-
Log.v("KlaviyoSampleApp", "Notification permission is denied and won't be requested")
113-
alertPermissionDenied()
114-
}
115-
}
116-
}
117-
11830
// Android Installation Step 5a: Depending on the state of your application when the notification is tapped,
11931
// the intent have started this activity, or it might be received via onNewIntent if the app was already running.
12032
// We recommend passing all intents through Klaviyo.handlePush to make sure you don't miss a use case.
@@ -137,39 +49,4 @@ class MainActivity : ReactActivity() {
13749
val action: String? = intent?.action // e.g. ACTION_VIEW
13850
val deepLink: Uri? = intent?.data // e.g. klaviyoreactnativesdkexample://link
13951
}
140-
141-
@SuppressLint("InlinedApi") // It is safe to use Manifest.permission.POST_NOTIFICATIONS, ActivityCompat handles API level differences
142-
private fun requestPermissionWithRationale() =
143-
AlertDialog
144-
.Builder(this)
145-
.setTitle("Notifications Permission")
146-
.setMessage("Permission must be granted in order to receive push notifications in the system tray.")
147-
.setCancelable(true)
148-
.setPositiveButton("Grant") { _, _ ->
149-
// You can directly ask for the permission.
150-
// The registered ActivityResultCallback gets the result of this request.
151-
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
152-
}.setNegativeButton("Cancel") { _, _ -> }
153-
.show()
154-
155-
private fun alertPermissionDenied(): AlertDialog =
156-
AlertDialog
157-
.Builder(this)
158-
.setTitle("Notifications Disabled")
159-
.setMessage("Permission is denied and can only be changed from notification settings.")
160-
.setCancelable(true)
161-
.setPositiveButton("Settings...") { _, _ -> openSettings() }
162-
.setNegativeButton("Cancel") { _, _ -> }
163-
.show()
164-
165-
private fun openSettings() {
166-
val intent =
167-
Intent(
168-
Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
169-
Uri.fromParts("package", packageName, null),
170-
)
171-
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
172-
173-
startActivity(intent)
174-
}
17552
}

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

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.klaviyoreactnativesdkexample
22

33
import android.app.Application
4-
import android.util.Log
54
import com.facebook.react.PackageList
65
import com.facebook.react.ReactApplication
76
import com.facebook.react.ReactHost
@@ -10,8 +9,13 @@ import com.facebook.react.ReactNativeHost
109
import com.facebook.react.ReactPackage
1110
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
1211
import com.facebook.react.defaults.DefaultReactNativeHost
13-
import com.google.firebase.messaging.FirebaseMessaging
14-
import com.klaviyo.analytics.Klaviyo
12+
13+
// OPTIONAL: Imports required for the native Klaviyo initialization pattern
14+
// shown in the commented reference block inside onCreate() below. Uncomment
15+
// these alongside the code in onCreate() if you want to initialize the
16+
// Klaviyo SDK and collect the FCM push token from Kotlin instead of JS.
17+
// import com.klaviyo.analytics.Klaviyo
18+
// import com.google.firebase.messaging.FirebaseMessaging
1519

1620
class MainApplication :
1721
Application(),
@@ -39,17 +43,14 @@ class MainApplication :
3943
super.onCreate()
4044
loadReactNative(this)
4145

42-
if (BuildConfig.INITIALIZE_KLAVIYO_FROM_NATIVE) {
43-
// Android Installation Step 3: Initialize the SDK with public key and context, if initializing from native code
44-
Klaviyo.initialize(BuildConfig.PUBLIC_API_KEY, this)
45-
46-
if (BuildConfig.USE_NATIVE_FIREBASE) {
47-
// Android Installation Step 4a: Collect push token and pass it to Klaviyo, if handling push tokens natively
48-
FirebaseMessaging.getInstance().token.addOnSuccessListener {
49-
Log.d("KlaviyoSampleApp", "Push token set: $it")
50-
Klaviyo.setPushToken(it)
51-
}
52-
}
53-
}
46+
// OPTIONAL: Native Klaviyo initialization. The example app initializes
47+
// Klaviyo from JS via .env (see example/src/App.tsx). If you'd rather
48+
// initialize from Kotlin, uncomment the imports at the top of this file
49+
// and the block below. See example/README.md for trade-offs.
50+
//
51+
// Klaviyo.initialize("YOUR_KLAVIYO_PUBLIC_API_KEY", this)
52+
// FirebaseMessaging.getInstance().token.addOnSuccessListener {
53+
// Klaviyo.setPushToken(it)
54+
// }
5455
}
5556
}

example/android/gradle.properties

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,21 +38,6 @@ newArchEnabled=true
3838
# If set to false, you will be using JSC instead.
3939
hermesEnabled=true
4040

41-
##################################################
42-
### Configuration to run the Android Sample App ###
43-
##################################################
44-
# Set your own configuration here, or make an untracked local.properties file by copying local.properties.template
45-
46-
# Configures whether Klaviyo.initialize() is called from native java/kotlin layer, or javascript/typescript layer
47-
initializeKlaviyoFromNative=true
48-
49-
# Set your public Klaviyo API key
50-
publicApiKey=YOUR_KLAVIYO_PUBLIC_API_KEY
51-
52-
# Enable Firebase in the example app. You must add your google-services.json file to the project,
53-
# and update the applicationId in build.gradle, else the app will crash on launch
54-
useNativeFirebase=false
55-
5641
# Use this property to enable edge-to-edge display support.
5742
# This allows your app to draw behind system bars for an immersive UI.
5843
# Note: Only works with ReactActivity and should not be used with custom Activity.

example/android/local.properties.template

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,11 @@
1-
## Changes in this file must *NOT* be tracked by Version Control Systems,
2-
# as it contains information specific to your local configuration,
3-
# such as your Klaviyo company ID aka "Public API Key".
1+
## local.properties must *NOT* be tracked by Version Control Systems,
2+
# as it contains information specific to your local configuration.
43
# Only the base template should be checked in to VCS.
5-
# Copy this file to `local.properties` to set your local overrides
6-
7-
# Configures whether Klaviyo.initialize() is called from native java/kotlin layer, or javascript/typescript layer
8-
initializeKlaviyoFromNative=true
9-
10-
# Set your public Klaviyo API key
11-
publicApiKey=YOUR_KLAVIYO_PUBLIC_API_KEY
12-
13-
# Enable Firebase in the example app. You must add your google-services.json file to the project,
14-
# and update the applicationId in build.gradle, else the app will crash on launch
15-
useNativeFirebase=false
16-
4+
# Copy this file to `local.properties` to set your local overrides.
5+
#
6+
# Note: the Klaviyo public API key is configured in JavaScript via
7+
# example/.env — see example/.env.template.
8+
#
179
# For local SDK development: set to true in your local.properties file
1810
# you must set separately for the example app and the library
1911
localCompositeBuild=false

example/ios/KlaviyoReactNativeSdkExample.xcodeproj/project.pbxproj

Lines changed: 24 additions & 20 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)