Draft
Conversation
Centralized theme tokens (colors, spacing, typography, radii) plus a shared StyleSheet and a small palette of interaction components — ActionButton, ProfileTextField, SectionHeader, ToggleButtons, Collapsible — used by the rest of the example app. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
Introduce PermissionHelper (location + push permission flows wrapping react-native-permissions and @react-native-firebase/messaging) plus four domain hooks — useAnalytics, useForms, useLocation, usePush — each owning the state and SDK calls for one Klaviyo feature area. Hooks provide handlers the UI layer can wire to buttons without touching the SDK directly. - Location permission flow requires separate taps for WhenInUse → Always on iOS (iOS won't prompt twice in one interaction) - usePush re-fetches APNs token on onTokenRefresh so Firebase's FCM token doesn't stomp the APNs token on iOS - Firebase availability is memoized at module scope Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
…rage Replace the legacy button-wall demo with an interactive, sectioned example that covers the full Klaviyo SDK public API surface and initializes the SDK from JavaScript instead of native code. - App.tsx: SectionList layout with Profile & Events / Forms / Geofencing / Push sections. App is a pure shell — it owns no hook state, so sibling sections don't re-render when one section's state changes. - Each section component colocates the domain hook it consumes (useAnalytics, useForms, useLocation, usePush). This sidesteps the memoization trap where lifting hooks into App would re-run every hook on any state change and invalidate any React.memo on children. No memo wrappers or useCallback/useMemo gymnastics are needed under this layout. - Profile + Events share a single useAnalytics instance by living in one merged AnalyticsSection (Option C from the review discussion): a typed email in the Profile fields is the same profile events attribute to, and we avoid Context/Provider boilerplate in a demo app. The section renders the profile fields and an inline "Events" sub-header with the event buttons below. - Profile fields: External ID / Email / Phone inputs with individual set buttons, plus a collapsible "Additional Attributes" accordion for first/last name, title, organization and a "Location" accordion for city/country/zip/lat/long, aggregate Set Profile button, Reset Profile - Events: test event + Viewed Product, both with value + uniqueId + custom properties - Push: Firebase-backed permission request, Set Push Token (label reads "APNs Push Token" on iOS and "Firebase Push Token" on Android), Set Badge Count (iOS-only, with number input) - Forms: explicit Register / Unregister - Geofencing: explicit Register / Unregister, Get Current Geofences modal - Deep link handling via Klaviyo.handleUniversalTrackingLink in a Linking useEffect with a proper cleanup - Env loading migrated to react-native-dotenv (.env + .env.example) with a typed @env declaration — no more try/require gymnastics - index.js registers a Firebase background message handler before AppRegistry as required by @react-native-firebase/messaging - Bumps @react-native-firebase/app and /messaging to ^24.0.0 for RN 0.81 bridgeless compatibility; adds react-native-dotenv dev dep - Drops legacy AppViewInterface, KlaviyoReactWrapper, RandomGenerators Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
2369d18 to
f27228c
Compare
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]>
Subscribe to Klaviyo.registerFormLifecycleHandler on mount in useForms so every shown/dismissed/CTA-click event is console.logged. Retain the event stream in a ring-buffer (capped at 100 entries, FIFO) in state and expose a FormLifecycleEventsModal (mirroring the GeofencesModal pattern) that renders a chronological log with per-event timestamps, formId/formName, and buttonLabel/deepLinkUrl for CTA-click events. Detail fields rendered via JSON.stringify so the modal doubles as a protocol inspector — documented valid empty strings show as "" rather than collapsing into a placeholder. Event keys are a monotonic id assigned at insertion so FlatList reconciler reuse is stable across prepends. FormsSection gets an ActionButton showing the running event count that opens the modal. Demonstrates the 2.4.0 registerFormLifecycleHandler API; subscribing unconditionally is safe — the SDK only emits events while forms are registered. Part of MAGE-464 Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
#345) # Description Part 3 of 4 in the RN example-app overhaul chain for [MAGE-464](https://linear.app/klaviyo/issue/MAGE-464). Wires up native-level Firebase push integration on both iOS and Android using the JS-first init pattern. Both platforms compile and launch cleanly without Firebase configured (push features are disabled in that mode); full push flow lights up end-to-end when a Firebase config file is present. ## Due Diligence - [x] I have tested this on a simulator/emulator or a physical device, on iOS and Android (if applicable). - [ ] I have added sufficient unit/integration tests of my changes. - [ ] I have adjusted or added new test cases to team test docs, if applicable. - [x] I am confident these changes are implemented with feature parity across iOS and Android (if applicable). ## Release/Versioning Considerations - [x] `Patch` Contains internal changes or backwards-compatible bug fixes. - [ ] `Minor` Contains changes to the public API. - [ ] `Major` Contains **breaking** changes. - [ ] Contains readme or migration guide changes. - [ ] This is planned work for an upcoming release. ## Changelog / Code Overview ### iOS | File | Change | |------|--------| | `AppDelegate.mm` | Call `[FIRApp configure]` on launch (unconditional — stub plist is the zero-config default, documented in #346); preserve UN delegate, deep-link, universal-link, and silent-push handlers; retain `getLaunchOptionsWithURL` helper for RN #32350 cold-start workaround; keep native-init reference block commented | | `Podfile` | `$RNFirebaseAsStaticFramework = true` so RNFirebase links cleanly under `use_frameworks!` | | `Info.plist` | `UIBackgroundModes = [location, remote-notification]`; location usage strings; URL scheme registration | | `*.entitlements` | `aps-environment = development`, wired via `CODE_SIGN_ENTITLEMENTS` | | `project.pbxproj` | Bundle id normalized to `com.klaviyoreactnativesdkexample` (matches Firebase app id and Android `applicationId`); `GoogleService-Info.plist` file reference added so it's bundled when integrators drop it in | | `.github/workflows/ios-build.yml` | Stub `GoogleService-Info.plist` step so CI builds succeed without a real Firebase project (format-valid values so `FirebaseApp.configure()` succeeds at launch; benign backend-registration warning at runtime) | ### Android | File | Change | |------|--------| | `gradle.properties`, `local.properties.template`, `app/build.gradle` | Remove dead `initializeKlaviyoFromNative` / `publicApiKey` / `useNativeFirebase` gradle→BuildConfig plumbing (nothing reads it) | | `app/build.gradle` | Conditionally apply `com.google.gms.google-services` plugin on the presence of `app/google-services.json` — clean build without push configured | | `MainApplication.kt` | Commented reference block for native init; primary path stays in JS | ## Test Plan - [x] iOS: build with stub `GoogleService-Info.plist` (values documented in #346) — app launches, push section shows "Firebase not configured" UI - [x] iOS: build with real plist — `[FIRApp configure]` runs, permission request works (see known sim caveat below) - [x] Android: clean build without `google-services.json` — gms plugin not applied, app launches, push section shows "Firebase not configured" UI - [x] Android: clean build with real `google-services.json` — gms plugin applies, Firebase init succeeds, token populates ### Known iOS simulator caveat Apple has a documented regression on iOS 26.0 / 26.2 simulators where `didRegisterForRemoteNotificationsWithDeviceToken:` never fires (FB19400926, FB19404213; matches [rnfirebase/#8937](invertase/react-native-firebase#8937)). APNs token fetching works correctly on physical devices and iOS 18.x simulators. Setup is correct — Apple's sim is broken. ## Related Issues/Tickets Part of [MAGE-464](https://linear.app/klaviyo/issue/MAGE-464) **Chained PR series:** 1. theme + components (merged in #342) 2. JS layer: permission helpers, hooks, app shell (merged in #343) 3. **This PR** — native platform setup (iOS + Android Firebase push) 4. docs — #346 Also stacked alongside: #347 (CI Play Store publish workflow, branched off this PR). Follow-up: [MAGE-534](https://linear.app/klaviyo/issue/MAGE-534) — convert `AppDelegate.mm` to pure Swift. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 19dcb50. Configure here.
…y CI wasn't running on the earlier PR
cc66f31 to
97a2656
Compare
) # Description Part 4 of 4 in the RN example-app overhaul chain for [MAGE-464](https://linear.app/klaviyo/issue/MAGE-464). Adds a live event log for `Klaviyo.registerFormLifecycleHandler` — the new API introduced in RN SDK 2.4.0 — so integrators have a working reference implementation and a built-in protocol inspector for their QA sessions. **Stacked on #345** (`ecm/example-app/4-native`). This PR should be rebased onto `feat/example-app` (or master, whichever is the eventual landing target) once #345 merges; the diff shown here is only the delta on top of that base. ### What changed - **`useForms`** — subscribes to `registerFormLifecycleHandler` on mount (no toggle needed; SDK only emits while forms are registered). Stores events in a ring-buffer (FIFO, capped at 100) so a long QA session doesn't pin an ever-growing array. - **`FormLifecycleEventsModal`** (new) — mirrors the `GeofencesModal` structure. Renders a `FlatList` with per-event timestamps, `formId`/`formName`, event type badge, and `buttonLabel`/`deepLinkUrl` for CTA-click events. Keys are a monotonic id assigned at insertion for stable FlatList reconciler reuse across prepends. Detail fields use `JSON.stringify` so the modal functions as a protocol inspector — `buttonLabel: ""` renders as `""` rather than collapsing (the SDK documents empty-string as a valid value). - **`FormsSection`** — new `ActionButton` showing a live count of captured events that opens the modal. ## Due Diligence - [ ] I have tested this on a simulator/emulator or a physical device, on iOS and Android (if applicable). - [ ] I have added sufficient unit/integration tests of my changes. - [ ] I have adjusted or added new test cases to team test docs, if applicable. - [x] I am confident these changes are implemented with feature parity across iOS and Android (if applicable). _(JS-only change; no platform split.)_ ## Release/Versioning Considerations - [x] `Patch` Contains internal changes or backwards-compatible bug fixes. - [ ] `Minor` Contains changes to the public API. - [ ] `Major` Contains **breaking** changes. - [ ] Contains readme or migration guide changes. - [ ] This is planned work for an upcoming release. > Example app only — no public API changes, no version bump needed. ## Changelog / Code Overview | File | Change | |------|--------| | `example/src/hooks/useForms.ts` | Subscribe to lifecycle handler on mount; maintain ring-buffer event state; return event array + clear fn | | `example/src/components/FormLifecycleEventsModal.tsx` | New modal component — chronological event log with type badge, timestamps, and detail inspector | | `example/src/sections/FormsSection.tsx` | New ActionButton wiring event count → modal | ## Test Plan Here's how it looks <img width="320" height="213" alt="Screenshot 2026-04-21 at 5 06 57 PM" src="https://github.com/user-attachments/assets/102d6396-4fe3-4b68-9499-3432bd6aa926" /> <img width="354" height="765" alt="Screenshot 2026-04-21 at 5 06 46 PM" src="https://github.com/user-attachments/assets/c043f6df-b027-48e5-91c9-8c4b9bb6f2f3" /> - [ ] iOS: trigger a form show event — confirm it appears in the modal with correct `formId`, `formName`, and timestamp - [ ] iOS: trigger a form dismiss — confirm `dismissed` event type badge appears - [ ] iOS: trigger a form CTA click — confirm `buttonLabel` and `deepLinkUrl` are rendered (not collapsed), including when `buttonLabel` is an empty string - [ ] iOS: generate >100 events — confirm older entries are evicted (ring-buffer, not unbounded growth) - [ ] Android: repeat the above — same behavior expected (JS-only change) - [ ] Confirm Clear button resets the list and the count badge on `FormsSection` goes to 0 - [ ] Confirm app cold-starts cleanly with no forms registered — no crash, event count shows 0 ## Related Issues/Tickets Part of [MAGE-464](https://linear.app/klaviyo/issue/MAGE-464) **Chained PR series:** 1. theme + components (merged in #342) 2. JS layer: permission helpers, hooks, app shell (merged in #343) 3. native platform setup — #345 4. **This PR** — lifecycle event subscription + debug modal 5. docs — #346 🤖 Generated with [Claude Code](https://claude.com/claude-code)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Description
Due Diligence
Release/Versioning Considerations
PatchContains internal changes or backwards-compatible bug fixes.MinorContains changes to the public API.MajorContains breaking changes.Changelog / Code Overview
Test Plan
Related Issues/Tickets