PushEngage — React Native Spoke
You're working in a React Native app. This skill walks the customer through integrating PushEngage from zero to verified test push, or debugs a broken integration.
Trigger gating. This spoke fires only when
package.jsonhasreact-native(orexpo) as a dependency. The presence ofios/Podfileorandroid/build.gradlealone (withoutpackage.json) does NOT route here — that's the native iOS or Android spoke. The presence ofpubspec.yamlroutes to Flutter, not here. The hub's detection logic puts both Flutter and React Native AHEAD of native iOS/Android checks because RN/Flutter projects also containios/andandroid/folders.
Routing
| Customer intent | Read this file next |
|---|---|
| "Integrate PushEngage" / "Set up PushEngage" (greenfield or partial) | This file's "Integration flow" section below. |
| "PushEngage isn't working" / specific symptom | Run static audit (audit-checks.md) first, then escalate to ../pushengage-debug/SKILL.md for Phase 2/3 (skill: pushengage-debug). |
| "Audit my PushEngage setup" | Read audit-checks.md and run the checks. |
| Concept / terminology question | Read ../pushengage/concepts.md. |
| Best practices / rule lookup | Read best-practices.md (RN-specific) or ../pushengage/best-practices.md (cross-platform). |
| API surface / "does method X exist" | Read version-matrix.md first; if not listed, fetch only URLs from references.md. |
Integration flow (greenfield)
Five parts in order. Earlier artifacts feed later steps.
Part 1 — Discovery & prereqs
Before touching anything, gather:
- Target platforms — does the customer ship to both stores, or just one? Look for
ios/andandroid/folders. - React Native version — read from
package.json'sdependencies."react-native". Critical compatibility check:- RN
>= 0.78→ install latest SDK (^1.0.0). - RN
< 0.78→ upgrade RN to 0.78+ (required — no supported SDK build for older RN; the Turbo Modules API requires it). Seeversion-matrix.md.
- RN
- Existing SDK pin — does
package.jsonalready have@pushengage/pushengage-react-native? If so, what version? applicationId(Android) — read fromandroid/app/build.gradle'sdefaultConfig.applicationId.- iOS Bundle Identifier — confirm from Xcode (Signing & Capabilities → Bundle Identifier on the main target).
- PushEngage account state — ask:
- Have you already created PushEngage sites for this app? (One per platform.)
- Do you have a Firebase project (Android)?
- Do you have an Apple Developer account with Push Notifications enabled (iOS)?
- Expo vs Bare: if
package.jsonhasexpoas a dep, flag: Expo Go does not support custom native modules. Customer must use Expo Dev Client (expo-dev-client) and runexpo prebuildonce to exposeios/andandroid/.
Output a one-paragraph integration plan in chat. Wait for the customer to confirm before any edits.
Part 2 — Provider setup (out-of-code, before any code edits)
Do these BEFORE editing code. The dashboard's App ID UUIDs are needed for setAppId(...); Firebase + APNs have real-world setup steps that block code progress.
- Android target →
firebase-setup.md. Defers to../pushengage-android/firebase-setup.mdfor the click-path. Captures Sender ID, Service Account JSON,google-services.json,applicationId. - iOS target →
apns-setup.md. Defers to../pushengage-ios/provider-setup.mdfor the click-path. Captures.p12, password, Bundle Identifier.
Part 3 — Dashboard setup
Read dashboard-setup.md. A cross-platform RN app needs TWO PushEngage sites (one Android, one iOS) — each generates its own App ID UUID. Capture BOTH UUIDs and confirm them back to the customer.
Part 4 — In-code edits
4a — Install + native config
Read install.md. Apply:
package.json— install vianpm install @pushengage/pushengage-react-native(or yarn). If RN < 0.78, upgrade RN to 0.78+ first — no supported SDK build for older RN.- Android:
android/gradle.properties(newArchEnabled=true),android/build.gradle(google-services classpath + jitpack repo),android/app/build.gradle(MUSTapply plugin: "com.google.gms.google-services"in the app module — the buildscript classpath alone is not enough),AndroidManifest.xml(POST_NOTIFICATIONS),google-services.jsonplacement. - iOS:
ios/Podfile(ENV['RCT_NEW_ARCH_ENABLED'] = '1', NSE/NCE target blocks withpod 'PushEngageExtension', '1.0.0'— the 1.0.0 two-pod split, noAPPLICATION_EXTENSION_API_ONLYoverride needed),Info.plist(PushEngage_App_Group_Key, background modes),cd ios && pod install.
Auto-edit + announce posture for config files. Show the diff, then apply.
4b — JS + ObjC init code
Read init-code.md. Apply:
index.js/index.tsx— import from@pushengage/pushengage-react-native(NOTpushengage-react-native),PushEngage.setAppId(...)withPlatform.OS === 'ios' ? ... : ...BEFOREAppRegistry.registerComponent,if (__DEV__) PushEngage.enableLogging(true);.- iOS AppDelegate — swizzling enabled in the delegate's
init,setInitialInfoindidFinishLaunchingWithOptionsbefore React Native boots. RN ≥ 0.77 templates generateAppDelegate.swift(use the Swift deltas ininit-code.mdSection 2); upgraded apps may still haveAppDelegate.mm(use the ObjC++ template). onValueChangedlistener in root component'suseEffectwith() => subscription.remove()cleanup.- Register
getInitialNotification()alongsideonValueChangedin the root effect for iOS cold-boot tap recovery (RN-BP-13). New in 1.0.0; resolvesnullon Android. - No Android
MainApplication.ktPushEngage init needed — RN plugin handles native init.
Diff + confirm posture for index.js, the AppDelegate (.swift or .mm), and the root App.tsx (when adding the listener).
Insert the App ID UUID(s) captured in Part 3.
4c — Runtime permission flow
Read permission-flow.md. Set up requestNotificationPermission() behind a user action (button, end-of-onboarding) — NOT in index.js or root useEffect. SDK auto-calls subscribe() on grant; don't double-call.
For Android: confirm POST_NOTIFICATIONS is in Manifest (Step 4a).
4d — NSE setup (iOS, required for rich notifications)
If iOS target: read nse-setup.md. Walk through Xcode NSE target creation, deployment target iOS 15.1, Podfile target block with pod 'PushEngageExtension', '1.0.0' (the extension-safe pod from the 1.0.0 two-pod split, NOT pod 'PushEngage', '~>0.0.5'), App Groups capability on BOTH main and NSE targets, matching PushEngage_App_Group_Key.
Skip entirely if Android-only.
4e — NCE setup (optional)
Default: SKIP. Read nce-setup.md if customer asks for custom in-notification UI.
Part 5 — Verification
Per target platform:
- Real device:
- Android: must have Google Play Services. NOT a Play-Services-less emulator.
- iOS: must be a physical device. APNs does NOT work on simulator.
- Install + run + trigger permission request → grant.
- PushEngage dashboard → corresponding site → Push Broadcasts → send test → confirm arrival.
If verification fails: run static audit (audit-checks.md), then escalate to pushengage-debug/SKILL.md.
Part 6 — Post-integration best-practices nudge
Short message. Pick 2–3 rules applicable to what you integrated:
- XP-BP-01 — move App ID UUID(s) out of source (
react-native-config,--env). - RN-BP-04 — defer
requestNotificationPermission()to a meaningful moment. - RN-BP-05 — wrap
enableLogging(true)in__DEV__. - RN-BP-02 — confirm
onValueChangedis in the root component'suseEffect, won't drift to a downstream screen during future refactors. - RN-BP-13 — register
getInitialNotification()alongsideonValueChangedfor iOS cold-boot tap recovery. - ANDROID-BP-13 (if Android target) — document OEM battery optimization in customer's FAQ.
Debugging entry points
Escalate to pushengage-debug/SKILL.md when:
- "Module 'pushengage-react-native' could not be resolved" → RN-AUDIT-01 / KI-RN001 (stale import name).
- "iOS build fails with 'Application extensions cannot use PushEngage'" → RN-AUDIT-03 (extension target links
pod 'PushEngage'instead ofpod 'PushEngageExtension', '1.0.0'). The oldAPPLICATION_EXTENSION_API_ONLYoverride / KI-RN002 path is obsolete in 1.0.0 (two-pod split). - "iOS build fails with '@import' / c++ modules disabled" → KI-RN008. Note: the
CLANG_ALLOW_NON_MODULAR_INCLUDES_IN_FRAMEWORK_MODULESoverride this depended on is obsolete in 1.0.0 (two-pod split); the app target's@import PushEngageresolves without it. - "Rich-push image doesn't load" → RN-AUDIT-03 (stale pod pin) and RN-AUDIT-09 (App Groups).
- "Cold-boot deep link is lost" → RN-AUDIT-12 (listener registered too late) / RN-AUDIT-17 (missing
getInitialNotification) / KI-RN003. 1.0.0 provides thegetInitialNotification()recovery API for iOS. - "Works on Android but not iOS" (or vice versa) → RN-AUDIT-07 / KI-RN006.
- "Permission dialog never shows on Android" → RN-AUDIT-05 / KI-RN004.
- "Android push never arrives, FCM token never registers" → RN-AUDIT-15 / KI-RN010 (missing google-services plugin apply).
- "iOS build crashes with Turbo Module error after
npm install" → RN-AUDIT-14 / KI-RN009 (RN/SDK version mismatch).
Edit posture per file class
- Auto-edit + announce:
package.json,ios/Podfile,android/build.gradle,android/settings.gradle,android/app/build.gradle,android/gradle.properties,AndroidManifest.xml,Info.plist, NSEInfo.plist,google-services.jsonplacement. - Diff + confirm:
index.js/index.tsx,ios/<App>/AppDelegate.mm, rootApp.tsx(when adding listener). Entry-point code. - Customer-driven (you instruct): Xcode UI — NSE/NCE target creation, App Groups capability, Push Notifications + Background Modes capabilities. Don't
.pbxproj-edit. - Never touch: anything outside integration scope. No formatting passes, no RN version bumps, no Gradle bumps beyond required.
Where the RN spoke borrows from other spokes
The RN spoke references content files from the iOS and Android spokes to avoid duplication. By file path only — you read the referenced content file but does NOT load the other spoke's SKILL.md.
| What | Where |
|---|---|
| Firebase Console click-path | ../pushengage-android/firebase-setup.md |
| Apple Developer + Keychain click-path | ../pushengage-ios/provider-setup.md |
| Long-form NCE template + category consistency rules | ../pushengage-ios/nce-setup.md |
| Manual (non-swizzling) iOS forwards template | ../pushengage-ios/init-code.md |
Never read the other spokes' SKILL.mds. Only their content files.
What you must not do
- Don't import from
'pushengage-react-native'(missing scope). Always'@pushengage/pushengage-react-native'. RN-AUDIT-01. - Don't invent PushEngage method names. Check
version-matrix.md. If unsure, fetch an allowlisted URL fromreferences.md. If still unsure, say so. - Use
version-matrix.mdas the source of truth for the package name and pod pins — scoped import, andPushEngageExtension 1.0.0for NSE/NCE targets. - Don't copy the SDK's example app verbatim. It has two known gaps: missing
POST_NOTIFICATIONSin Manifest, unconditionalenableLogging(true). (It DOES applycom.google.gms.google-services— that step is still required in customer apps.) The skill'sinstall.mdandinit-code.mdcover the correct patterns. - Don't auto-edit
index.jsorAppDelegate.mmwithout showing the diff and getting confirmation. Entry-point code is diff + confirm. - Don't touch files outside integration scope.
- Don't fetch URLs not in
references.md. - Don't ask the customer for Service Account JSON or
.p12contents in chat. Both are secrets — they upload directly to the dashboard. - Don't add
pod 'PushEngage'to the main app target in Podfile. Pulled transitively. NSE / NCE target blocks getpod 'PushEngageExtension', '1.0.0'(the extension-safe pod from the 1.0.0 two-pod split), notpod 'PushEngage'. RN-BP-06. - Don't add a
MainApplication.ktPushEngage init block — the plugin handles it. - Don't follow the SDK example app's unconditional
enableLogging(true)pattern — gate behind__DEV__.