Skip to content

Commit 8049209

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 579664f commit 8049209

14 files changed

Lines changed: 554 additions & 469 deletions

File tree

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ GoogleService-Info.plist
4242
local.properties
4343
android.iml
4444
google-services.json
45+
google-services.json.bak*
46+
GoogleService-Info.plist.bak*
4547

4648
# Cocoapods
4749
#

example/android/app/build.gradle

Lines changed: 9 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,13 @@ 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 native Firebase push:
130+
// 1. Copy example/android/app/google-services.json.template → google-services.json
131+
// 2. Replace the placeholder values with your real Firebase project config
132+
// 3. Ensure applicationId above matches the package_name in your google-services.json
133+
// Push in this example is otherwise handled in JS via @react-native-firebase/messaging.
134+
if (file('google-services.json').exists()) {
135+
apply plugin: "com.google.gms.google-services"
146136
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"project_info": {
3+
"project_number": "000000000000",
4+
"project_id": "your-firebase-project-id",
5+
"storage_bucket": "your-firebase-project-id.appspot.com"
6+
},
7+
"client": [
8+
{
9+
"client_info": {
10+
"mobilesdk_app_id": "1:000000000000:android:0000000000000000000000",
11+
"android_client_info": {
12+
"package_name": "com.klaviyoreactnativesdkexample"
13+
}
14+
},
15+
"oauth_client": [],
16+
"api_key": [
17+
{
18+
"current_key": "REPLACE_WITH_FIREBASE_API_KEY"
19+
}
20+
],
21+
"services": {
22+
"appinvite_service": {
23+
"other_platform_oauth_client": []
24+
}
25+
}
26+
}
27+
],
28+
"configuration_version": "1"
29+
}
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: 18 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,14 @@ 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 android.util.Log
18+
// import com.klaviyo.analytics.Klaviyo
19+
// import com.google.firebase.messaging.FirebaseMessaging
1520

1621
class MainApplication :
1722
Application(),
@@ -39,17 +44,15 @@ class MainApplication :
3944
super.onCreate()
4045
loadReactNative(this)
4146

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-
}
47+
// OPTIONAL: Native Klaviyo initialization. The example app initializes
48+
// Klaviyo from JS via .env (see example/src/App.tsx). If you'd rather
49+
// initialize from Kotlin, uncomment the imports at the top of this file
50+
// and the block below. See example/README.md for trade-offs.
51+
//
52+
// Klaviyo.initialize("YOUR_KLAVIYO_PUBLIC_API_KEY", this)
53+
// FirebaseMessaging.getInstance().token.addOnSuccessListener {
54+
// Log.d("KlaviyoSampleApp", "Push token set: $it")
55+
// Klaviyo.setPushToken(it)
56+
// }
5457
}
5558
}

example/android/gradle.properties

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,17 +41,13 @@ hermesEnabled=true
4141
##################################################
4242
### Configuration to run the Android Sample App ###
4343
##################################################
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
44+
# The example app initializes Klaviyo from JavaScript via .env — see
45+
# example/.env.example and example/src/App.tsx.
46+
#
47+
# Native Firebase push is enabled automatically when
48+
# example/android/app/google-services.json exists (copy it from the provided
49+
# .template file). When no google-services.json is present, push is handled
50+
# in JS via @react-native-firebase/messaging.
5551

5652
# Use this property to enable edge-to-edge display support.
5753
# This allows your app to draw behind system bars for an immersive UI.

example/android/local.properties.template

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
## 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".
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
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.example.
8+
#
9+
# Native Firebase push is enabled automatically when
10+
# example/android/app/google-services.json exists (copy it from the provided
11+
# .template file). Make sure the applicationId in app/build.gradle matches the
12+
# package_name in your google-services.json.
1613

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

0 commit comments

Comments
 (0)