Source: https://www.pushengage.com/api/ai-agents # Build with AI Agents PushEngage ships two official tools for AI agents, one for **operating your account** and one for **integrating our SDK into your app**: - **[MCP Server](#mcp-server)**: a [Model Context Protocol](https://modelcontextprotocol.io/) server that connects assistants such as Claude Desktop, Claude Code, and Cursor to your PushEngage account. Send and schedule notifications, run A/B tests, build audiences, inspect analytics, and manage site settings just by asking. - **[Agent Skills](#agent-skills)**: a skill pack that teaches AI coding agents to integrate and debug the PushEngage Mobile SDK across **iOS, Android, Flutter, and React Native** apps. They work great together: install the Agent Skills plugin in Claude Code and the MCP server is configured for you automatically. ## MCP Server The [`@pushengage/mcp`](https://github.com/awesomemotive/pushengage-mcp) server lets you manage your PushEngage account from any MCP-capable assistant, in plain language. Once connected, just describe what you want: - _"Send a notification titled 'Sale ends tonight', message 'Last call, 50% off', linking to [https://example.com/sale."](https://example.com/sale.%22)_ - _"Schedule that for 9 AM in each subscriber's local timezone."_ - _"Run an A/B test of two headlines and auto-roll-out the winner by click rate."_ - _"Create a segment for visitors of /pricing."_ - _"How many subscribers do I have, and what was my click rate over the last 30 days?"_ - _"Set my default notification expiry to 7 days."_ ### Requirements - A [PushEngage](https://www.pushengage.com/) account (free or paid) with at least one site. - Node.js 18 or newer (the assistant runs the server via `npx`). - An MCP-capable client such as Claude Desktop, Claude Code, Cursor, or any other client that speaks MCP over stdio. ### Install The easiest path is the one-click MCP Bundle: 1. Download the latest `pushengage-mcp-.mcpb` file from the [GitHub releases page](https://github.com/awesomemotive/pushengage-mcp/releases/latest). 2. Open it with Claude Desktop (double-click it, or drag it onto the window) and click **Install**. Prefer manual config? Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or the equivalent on your platform, then restart Claude Desktop: ```json { "mcpServers": { "pushengage": { "command": "npx", "args": ["-y", "@pushengage/mcp"] } } } ``` Run this in your terminal: ```bash claude mcp add pushengage -- npx -y @pushengage/mcp ``` Or install the [PushEngage Agent Skills plugin](#agent-skills), which configures the MCP server for you automatically. One-click install: [Add to Cursor](https://cursor.com/install-mcp?name=pushengage&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBwdXNoZW5nYWdlL21jcCJdfQ%3D%3D) Or edit `~/.cursor/mcp.json` (or open the command palette → **Cursor Settings** → **MCP** → **Add new global MCP server**): ```json { "mcpServers": { "pushengage": { "command": "npx", "args": ["-y", "@pushengage/mcp"] } } } ``` Run this in your terminal: ```bash codex mcp add pushengage -- npx -y @pushengage/mcp ``` Or add it to `~/.codex/config.toml`: ```toml [mcp_servers.pushengage] command = "npx" args = ["-y", "@pushengage/mcp"] ``` One-click install: [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=pushengage&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40pushengage%2Fmcp%22%5D%7D) Or run **MCP: Add Server** from the command palette, or add this to `.vscode/mcp.json` in your workspace: ```json { "servers": { "pushengage": { "type": "stdio", "command": "npx", "args": ["-y", "@pushengage/mcp"] } } } ``` Add this to `~/.gemini/settings.json`: ```json { "mcpServers": { "pushengage": { "command": "npx", "args": ["-y", "@pushengage/mcp"] } } } ``` Add this to `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "pushengage": { "command": "npx", "args": ["-y", "@pushengage/mcp"] } } } ``` Navigate to **Settings** → **AI** → **MCP Servers**, click **\+ Add**, and paste: ```json { "pushengage": { "command": "npx", "args": ["-y", "@pushengage/mcp"] } } ``` Add this to `opencode.json`: ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "pushengage": { "type": "local", "command": ["npx", "-y", "@pushengage/mcp"], "enabled": true } } } ``` Open **Settings** → **Connections** → **MCP Servers**, click **Add a custom MCP**, and paste: ```json { "mcpServers": { "pushengage": { "command": "npx", "args": ["-y", "@pushengage/mcp"] } } } ``` Any client that speaks MCP over stdio works. Configure it to run: ```bash npx -y @pushengage/mcp ``` ### First run: logging in Authentication is browser-based, so your credentials never touch the assistant. 1. Ask: **"Log me into PushEngage."** The server opens a browser tab to the PushEngage authorize page. 2. Click **Authorize**. The tab confirms success and an access token is saved locally with `0600` permissions. 3. Ask: **"Show my PushEngage sites,"** then **"Use site 12345"** to pick the site to work with. The selection is remembered across restarts. ### Available tools The server exposes tools across the whole platform: | Area | What you can do | | --- | --- | | Authentication & sites | Log in and out, list your sites, select the site to work with. | | Sending notifications | List, send, and schedule notifications: one-shot, per-subscriber timezone, recurring, and A/B tests. | | Audiences | Create and list segments, audience groups, and custom subscriber attributes. | | Campaigns & automations | Inspect drip autoresponders, triggered campaigns, RSS auto push, and workflows, with analytics. | | Analytics | Lifetime totals and per-day/week/month timeseries for subscribers, sends, views, clicks, and CTR. | | Site settings | Site details, campaign defaults, and service worker settings. | For the full tool reference, configuration options, and troubleshooting, see the [pushengage-mcp README](https://github.com/awesomemotive/pushengage-mcp#readme). ## Agent Skills [PushEngage Agent Skills](https://github.com/awesomemotive/pushengage-skills) help an AI coding agent integrate and debug the PushEngage push-notification SDK in your app. The skills follow two open standards: the [Agent Skills (`SKILL.md`) standard](https://agentskills.io/specification) and the [Agent Plugins format](https://agent-plugins.org/), so they work with any agent that supports either one, including Claude Code, Cursor, Codex, Copilot, Windsurf, VS Code, and others. The pack contains six skills: | Skill | Purpose | | --- | --- | | `pushengage` | Entry skill that detects your platform and routes to the right one. | | `pushengage-ios` | iOS integration and debugging. | | `pushengage-android` | Android integration and debugging. | | `pushengage-flutter` | Flutter integration and debugging. | | `pushengage-react-native` | React Native integration and debugging. | | `pushengage-debug` | Shared diagnostic skill for broken integrations. | ### Install Install as a plugin: ```text /plugin marketplace add awesomemotive/pushengage-skills /plugin install pushengage ``` Installing the plugin also configures the official [MCP server](#mcp-server), so the agent can operate your PushEngage account too. Works with Cursor, Codex, Copilot, Windsurf, and other SKILL.md-compatible agents via [skills.sh](https://skills.sh): ```bash npx skills add awesomemotive/pushengage-skills ``` The repository conforms to the open [Agent Plugins format](https://agent-plugins.org/) (`plugin.json` manifest, `skills/` directory, and `mcp.json`), so any client on the [compatible clients list](https://agent-plugins.org/compatible-clients) can load it as a plugin. That currently includes VS Code, Cursor, GitHub Copilot, ChatGPT & Codex, Kiro, Hermes Agent, OpenClaw, Grok Bot, and NanoClaw. The Agent Plugins specification leaves installation up to each client, so use your client's own plugin-install mechanism and point it at the `awesomemotive/pushengage-skills` repository. Clients that support the plugin's `mcp.json` also get the official [MCP server](#mcp-server) configured automatically. The skills live under `skills/` in the [pushengage-skills repository](https://github.com/awesomemotive/pushengage-skills). Copy the skill folders into your agent's skills directory, or point your agent at the repository. Each skill activates automatically based on the project type it detects. ### Usage After installing, just talk to your agent: - _"Help me integrate PushEngage into this app."_ runs the full integration flow. - _"PushEngage notifications aren't arriving on my iPhone."_ runs the diagnostic flow. - _"Audit my PushEngage setup."_ runs the static audit. The plugin detects your platform automatically and hands off to the matching integration or debug skill. Prefer to follow the steps yourself? Head to the [Mobile SDK docs](/api/mobile-sdk). ## Docs for Your Agent This documentation is built to be read by AI agents as well as humans: - Every page has a markdown version. Append `.md` to any page URL (for example, [/api/ai-agents.md](https://www.pushengage.com/api/ai-agents.md)), or use the **Copy as markdown** button at the top of each page. - [llms.txt](https://www.pushengage.com/api/llms.txt) is an index of every page with links to the markdown versions. - [llms-full.txt](https://www.pushengage.com/api/llms-full.txt) contains the entire documentation in a single file, ready to drop into an agent's context. To onboard an agent, paste a prompt like this: ```text Read https://www.pushengage.com/api/ai-agents.md and set up the PushEngage MCP server and Agent Skills in this project. ``` ## Resources - [pushengage-mcp on GitHub](https://github.com/awesomemotive/pushengage-mcp): source, full tool reference, and troubleshooting. - [`@pushengage/mcp` on npm](https://www.npmjs.com/package/@pushengage/mcp): the MCP server package. - [pushengage-skills on GitHub](https://github.com/awesomemotive/pushengage-skills): the Agent Skills marketplace and plugin. --- Source: https://www.pushengage.com/api/mobile-sdk # Mobile SDK The PushEngage Mobile SDK enables push notification capabilities for iOS, Android, Flutter, and React Native applications. Integrate once and your team can send contextual, personalized messages directly to your app users. ## Choose Your Platform | Platform | Language | Distribution | | --- | --- | --- | | [iOS →](/api/mobile-sdk/ios/quickstart) | Swift / Objective-C | APNs | | [Android →](/api/mobile-sdk/android/quickstart) | Java / Kotlin | FCM | | [Flutter →](/api/mobile-sdk/flutter/quickstart) | Dart | APNs + FCM | | [React Native →](/api/mobile-sdk/react-native/quickstart) | TypeScript / JavaScript | APNs + FCM | ## Get Your App ID Every platform requires a PushEngage **App ID** to initialize the SDK. To find yours: 1. Log in to your [PushEngage Dashboard](https://app.pushengage.com). 2. Navigate to **Site Settings → Installation**. 3. Select your platform tab (**Android SDK** or **iOS SDK**). 4. Copy the **App ID** displayed on the page. You will use this App ID in the **Initialize the SDK** step of your platform's Quickstart. ## Integrate with an AI Agent Using Claude Code, Cursor, or another AI coding agent? Install the official [PushEngage Agent Skills](/api/ai-agents#agent-skills) and ask it to _"integrate PushEngage into this app"_. The skills walk the agent through the full integration and can diagnose broken setups on any of the four platforms. ## Version & Compatibility | SDK | Min OS Version | Language / Runtime | Registry | | --- | --- | --- | --- | | iOS | iOS 12.0+ | Swift / Objective-C | CocoaPods, SPM | | Android | API 16+ (Android 4.1) | Java / Kotlin | JitPack (Gradle) | | Flutter | Flutter 3.x+ | Dart | pub.dev | | React Native | RN 0.78+ | TypeScript / JavaScript | npm / yarn | React Native Version If you are using React Native **0.77.0 or earlier**, install SDK version `0.0.1`: ```bash npm install @pushengage/pushengage-react-native@0.0.1 ``` --- Source: https://www.pushengage.com/api/mobile-sdk/android/jetpack-compose # Using PushEngage Android SDK with Jetpack Compose PushEngage Android SDK works with Jetpack Compose out of the box. This guide covers initialization, notification permissions, subscriber management, deep linking, and best practices for Compose-based apps. ## Compose Compatibility PushEngage Android SDK uses `ComponentActivity` as its base parameter type for activity-dependent methods. Since Compose activities extend `ComponentActivity`, the SDK works natively with Compose — no adapters or wrappers needed. | SDK Method | Parameter Type | Compose Compatible | | --- | --- | :-: | | `requestNotificationPermission()` | `ComponentActivity` | Yes | | `subscribe()` | No activity needed (or `ComponentActivity` with callback) | Yes | | All other methods | Static (no activity needed) | Yes | ## Setup ### 1\. Add Dependencies In your app-level `build.gradle.kts`: ```kotlin plugins { id("com.android.application") id("com.google.gms.google-services") id("org.jetbrains.kotlin.android") } dependencies { // PushEngage SDK implementation("com.github.awesomemotive:pushengage-android-sdk:") implementation(platform("com.google.firebase:firebase-bom:")) // Jetpack Compose (your existing Compose dependencies) implementation("androidx.activity:activity-compose:") implementation("androidx.compose.material3:material3:") implementation("androidx.navigation:navigation-compose:") } ``` In your `settings.gradle.kts`: ```kotlin dependencyResolutionManagement { repositories { google() mavenCentral() maven { url = uri("https://jitpack.io") } } } ``` ### 2\. Initialize in Application Class Initialize PushEngage in your `Application` class — this is the same regardless of whether you use Compose or traditional Views. ```kotlin import android.app.Application import com.pushengage.pushengage.PushEngage class MyApp : Application() { override fun onCreate() { super.onCreate() PushEngage.Builder() .addContext(this) .setAppId("YOUR_APP_ID") .build() // Optional: enable logging during development PushEngage.enableLogging(true) } } ``` ### 3\. Set Up Your Compose Activity Use `ComponentActivity` (or `AppCompatActivity` — both work) as your main activity: ```kotlin import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.MaterialTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { AppContent() } } } } ``` ## Notification Permissions (Android 13+) Starting with Android 13 (API 33), apps must request the `POST_NOTIFICATIONS` runtime permission. PushEngage SDK handles this for you — just pass your Compose activity. ### Option A: Use PushEngage SDK's Built-In Permission Request (Recommended) The simplest approach. PushEngage handles the permission dialog and automatically subscribes the user when granted. ```kotlin import androidx.activity.ComponentActivity import androidx.compose.runtime.* import androidx.compose.material3.* import androidx.compose.ui.platform.LocalContext import com.pushengage.pushengage.PushEngage import com.pushengage.pushengage.Callbacks.PushEngagePermissionCallback @Composable fun NotificationPermissionScreen() { val activity = LocalContext.current as ComponentActivity var permissionGranted by remember { mutableStateOf(false) } var permissionRequested by remember { mutableStateOf(false) } if (!permissionRequested) { Button(onClick = { permissionRequested = true PushEngage.requestNotificationPermission(activity, object : PushEngagePermissionCallback { override fun onPermissionResult(granted: Boolean, error: Error?) { permissionGranted = granted // SDK automatically calls subscribe() when granted } } ) }) { Text("Enable Push Notifications") } } else if (permissionGranted) { Text("Notifications enabled!") } } ``` How it works under the hood The SDK uses one of two mechanisms depending on your activity type: - **Plain `ComponentActivity`** (the default for Compose apps): the SDK launches a transparent helper activity to drive the system permission dialog. - **`FragmentActivity` subclass** (e.g., `AppCompatActivity`): the SDK attaches a headless Fragment to your activity instead. Either way, the user sees only the standard Android permission prompt — the mechanism is invisible. ### Option B: Handle Permission Yourself, Then Subscribe If you want more control over the permission UX — for example, showing a rationale screen before the system dialog — handle the permission in Compose and then call `subscribe()`. ```kotlin import android.Manifest import android.os.Build import androidx.activity.ComponentActivity import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.runtime.* import androidx.compose.material3.* import androidx.compose.ui.platform.LocalContext import com.pushengage.pushengage.PushEngage import com.pushengage.pushengage.Callbacks.PushEngageResponseCallback @Composable fun CustomPermissionFlow() { val activity = LocalContext.current as ComponentActivity var showRationale by remember { mutableStateOf(true) } fun subscribeToPushEngage() { PushEngage.subscribe(activity, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { // Subscription successful } override fun onFailure(errorCode: Int?, errorMessage: String?) { // Handle subscription failure } }) } val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission() ) { isGranted -> if (isGranted) { // Permission granted — now subscribe with PushEngage subscribeToPushEngage() } } if (showRationale) { // Your custom rationale screen AlertDialog( onDismissRequest = { showRationale = false }, title = { Text("Stay in the loop") }, text = { Text("Enable notifications to get updates on your orders, exclusive deals, and important alerts.") }, confirmButton = { Button(onClick = { showRationale = false if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } else { // Pre-Android 13: permission granted by default subscribeToPushEngage() } }) { Text("Enable") } }, dismissButton = { TextButton(onClick = { showRationale = false }) { Text("Not now") } } ) } } ``` ### Checking Permission Status `getNotificationPermissionStatus()` is synchronous, so it is safe to call directly inside `remember`. Note that this is a **point-in-time snapshot** — it reads the current OS state once at composition time and does not update if the user later changes notification settings in System Settings. ```kotlin @Composable fun NotificationPermissionGate() { // Reads permission status once at composition time. // Re-read on resume if you need up-to-date state after returning from Settings. val status = remember { PushEngage.getNotificationPermissionStatus() } when (status) { "granted" -> Text("Notifications are enabled") "denied" -> Text("Notifications are disabled. Enable them in Settings.") } } ``` note `getNotificationPermissionStatus()` returns only `"granted"` or `"denied"`. There is no "not yet requested" state — on Android 12 and below, it always returns `"granted"` since the permission is granted at install time. ## Subscriber Management in Compose All subscriber methods are static and don't require an Activity reference, so they work directly from any Composable. ### Get Subscriber ID PushEngage callbacks fire on a background thread. Use `suspendCancellableCoroutine` to bridge the callback back into the `LaunchedEffect` coroutine, which already runs on `Dispatchers.Main`. This ensures the state update happens on the main thread without any manual dispatcher switching. ```kotlin import com.pushengage.pushengage.PushEngage import com.pushengage.pushengage.Callbacks.PushEngageResponseCallback import kotlinx.coroutines.suspendCancellableCoroutine import kotlin.coroutines.resume @Composable fun SubscriberInfo() { var subscriberId by remember { mutableStateOf(null) } LaunchedEffect(Unit) { // suspendCancellableCoroutine bridges the callback back into this coroutine. // The resume runs on whatever thread the callback fires on, but the // coroutine resumes on Dispatchers.Main (LaunchedEffect's default). subscriberId = suspendCancellableCoroutine { cont -> PushEngage.getSubscriberId(object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { cont.resume(responseObject as? String) } override fun onFailure(errorCode: Int?, errorMessage: String?) { cont.resume(null) } }) } } subscriberId?.let { id -> Text("Subscriber: $id") } } ``` ### Add Segments ```kotlin import com.pushengage.pushengage.PushEngage import com.pushengage.pushengage.Callbacks.PushEngageResponseCallback fun addUserToSegment(segmentName: String) { PushEngage.addSegment(listOf(segmentName), object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { // Segment added } override fun onFailure(errorCode: Int?, errorMessage: String?) { // Handle error } } ) } ``` ### Add Subscriber Attributes Use `addSubscriberAttributes` to merge new attributes with existing ones, or `setSubscriberAttributes` to replace all attributes entirely. ```kotlin import org.json.JSONObject import com.pushengage.pushengage.PushEngage import com.pushengage.pushengage.Callbacks.PushEngageResponseCallback fun updateUserAttributes(name: String, plan: String) { val attributes = JSONObject().apply { put("name", name) put("plan", plan) } PushEngage.addSubscriberAttributes(attributes, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { // Attributes updated } override fun onFailure(errorCode: Int?, errorMessage: String?) { // Handle error } } ) } ``` ## Deep Linking with Compose Navigation When a user taps a PushEngage notification, the SDK opens your app via an Intent. In a Compose app, you need to bridge Intent data to Compose Navigation. ### 1\. Define Deep Link Routes ```kotlin import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import androidx.navigation.navDeepLink @Composable fun AppNavigation() { val navController = rememberNavController() NavHost(navController = navController, startDestination = "home") { composable("home") { HomeScreen(navController) } composable( route = "product/{productId}", deepLinks = listOf( navDeepLink { uriPattern = "myapp://product/{productId}" } ) ) { backStackEntry -> val productId = backStackEntry.arguments?.getString("productId") ProductScreen(productId = productId) } composable( route = "orders", deepLinks = listOf( navDeepLink { uriPattern = "myapp://orders" } ) ) { OrdersScreen() } } } ``` ### 2\. Configure AndroidManifest.xml ```xml ``` ### 3\. Handle Intent Data in Your Activity ```kotlin class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { MaterialTheme { AppNavigation() } } } } ``` When a notification with deep link URL `myapp://product/12345` is tapped, Compose Navigation automatically routes to the `ProductScreen` composable with `productId = "12345"`. ### 4\. Set Deep Links in PushEngage Dashboard In the PushEngage dashboard, when creating a notification campaign: - Set the **Deep Link** field to your deep link URI (e.g., `myapp://product/12345`) - The SDK will open this URL when the notification is tapped - Compose Navigation picks it up via the intent filter and routes accordingly ## Goal & Event Tracking Track conversion events from anywhere in your Compose UI: ```kotlin import com.pushengage.pushengage.PushEngage import com.pushengage.pushengage.model.request.Goal import com.pushengage.pushengage.model.request.TriggerCampaign // Track a purchase goal fun trackPurchase(amount: Double) { val goal = Goal( name = "purchase_complete", count = 1, value = amount ) PushEngage.sendGoal(goal) } // Send a trigger event fun sendCartAbandonTrigger(cartValue: String) { val trigger = TriggerCampaign( campaignName = "cart_abandon", eventName = "cart_abandoned", data = mapOf("cart_value" to cartValue) ) PushEngage.sendTriggerEvent(trigger) } ``` Use these in a Composable: ```kotlin @Composable fun CheckoutScreen(cartTotal: Double) { Button(onClick = { // Process payment... trackPurchase(cartTotal) }) { Text("Complete Purchase") } } ``` ## Price Drop & Inventory Alerts For e-commerce apps, trigger price drop and inventory alerts: ```kotlin import com.pushengage.pushengage.PushEngage import com.pushengage.pushengage.model.request.TriggerAlert fun addPriceDropAlert(productId: String, price: Double) { val alert = TriggerAlert( type = PushEngage.TriggerAlertType.priceDrop, productId = productId, link = "myapp://product/$productId", price = price ) PushEngage.addAlert(alert) } fun addInventoryAlert(productId: String) { val alert = TriggerAlert( type = PushEngage.TriggerAlertType.inventory, productId = productId, link = "myapp://product/$productId", price = 0.0, availability = PushEngage.TriggerAlertAvailabilityType.outOfStock ) PushEngage.addAlert(alert) } ``` ## Best Practices ### Do - **Initialize in Application class**, not in your Compose Activity. The SDK should be ready before any UI renders. - **Request permission after value demonstration.** Show the user what notifications will help with before asking. Don't prompt on the very first screen. - **Use `LaunchedEffect` for one-time SDK calls** (like getting subscriber ID) to avoid re-calling on recomposition. - **Handle the Android version check** — always check `Build.VERSION.SDK_INT >= TIRAMISU` before requesting `POST_NOTIFICATIONS`. - **Use deep link URIs** that map cleanly to your Compose Navigation routes. ### Don't - **Don't call async SDK methods (those with callbacks) inside `remember` blocks** — use `LaunchedEffect` for one-time async calls and event handlers (like `onClick`) for user-triggered actions. Synchronous methods like `getNotificationPermissionStatus()` are fine inside `remember`. - **Don't block the UI thread** with SDK callbacks — all PushEngage callbacks already run asynchronously. - **Don't request permission in `LaunchedEffect`** — permission requests must be triggered by user action (button tap), not automatically on composition. - **Don't forget to add `POST_NOTIFICATIONS` to AndroidManifest.xml** — Android 13+ silently denies the runtime permission request if this declaration is missing, regardless of what the SDK does. ## Migration Notes ### Coming from XML-based Activities If you're converting an existing PushEngage integration from XML Views to Compose: 1. **No SDK changes needed** — the same `PushEngage` API works in both 2. Replace `AppCompatActivity` with `ComponentActivity` (or keep `AppCompatActivity` — both work) 3. Move permission request logic into Composable event handlers 4. Replace Intent-based navigation with Compose Navigation deep links 5. Static SDK methods (`subscribe`, `addSegment`, etc.) work identically ### Coming from OneSignal If migrating from OneSignal to PushEngage in a Compose app: 1. Replace `OneSignal.initWithContext(this)` with `PushEngage.Builder().addContext(this).setAppId("...").build()` 2. Replace `OneSignal.promptForPushNotifications()` with `PushEngage.requestNotificationPermission(activity, callback)` 3. Replace OneSignal's `setNotificationOpenedHandler` with PushEngage deep links via Compose Navigation 4. OneSignal's tag system maps to PushEngage segments and attributes ## Next Steps - [Android Quickstart →](/api/mobile-sdk/android/quickstart) - [Android SDK Reference →](/api/mobile-sdk/android/sdk-reference) - [Mobile SDK Overview →](/api/mobile-sdk) --- Source: https://www.pushengage.com/api/mobile-sdk/android/quickstart # Android Quickstart This guide walks you through integrating PushEngage push notifications into a native Android application (Java or Kotlin). Estimated time: **15 minutes**. After completing this guide, your app will receive push notifications. See the [Android SDK Reference](/api/mobile-sdk/android/sdk-reference) for the full API. ## Prerequisites - Android Studio installed - Android project with `minSdkVersion` 16 (Android 4.1) or higher; API 21+ recommended - Firebase account ([create one free](https://console.firebase.google.com)) - PushEngage account and App ID (see [Get Your App ID](/api/mobile-sdk#get-your-app-id)) Let an AI agent do the integration for you Every step on this page can be handled by an AI coding agent. Install the official [PushEngage Agent Skills](/api/ai-agents#agent-skills) in Claude Code, Cursor, or another compatible agent: ```bash npx skills add awesomemotive/pushengage-skills ``` Then ask it to _"integrate PushEngage into this app"_. The skills walk the agent through the full setup and can also diagnose a broken integration. ## Step 1 — Installation ### Configure the Repository **If your project uses a `settings.gradle` file (centralized dependency resolution):** settings.gradle ```groovy dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { google() mavenCentral() maven { url 'https://jitpack.io' } } } ``` **If your project uses a project-level `build.gradle`:** build.gradle (project level) ```groovy buildscript { repositories { google() jcenter() } dependencies { classpath "com.google.gms:google-services:4.4.2" } } allprojects { repositories { google() maven { url 'https://jitpack.io' } } } ``` ### Add the SDK Dependency build.gradle (app level) ```groovy plugins { id 'com.android.application' id 'com.google.gms.google-services' } dependencies { implementation 'com.github.awesomemotive:pushengage-android-sdk:0.1.0' implementation platform('com.google.firebase:firebase-bom:33.5.1') } ``` ## Step 2 — Firebase Setup PushEngage uses Firebase Cloud Messaging (FCM) to deliver push notifications to Android devices. ### Create a Firebase Project 1. Open the [Firebase console](https://console.firebase.google.com) and sign in. 2. Click **Add Project** (or select an existing one) and complete the setup wizard. ### Add Your Android App 1. In the Firebase project dashboard, click the **Android icon**. 2. Enter your app's **package name** (found in `android/app/build.gradle` under `applicationId`). 3. Click **Register app**. 4. Download **google-services.json** and place it at `android/app/google-services.json`. ### Generate Service Account JSON 1. Firebase console → **Settings icon** → **Project settings** → **Service accounts** tab. 2. Click **Generate new private key** and save the downloaded JSON file securely. ### Retrieve Your Sender ID 1. Firebase console → **Settings icon** → **Project settings** → **Cloud Messaging** tab. 2. Copy the **Sender ID**. ## Step 3 — Connect to PushEngage Dashboard 1. Log in to your [PushEngage Dashboard](https://app.pushengage.com). 2. Navigate to **Site Settings → Installation → Android SDK** tab. 3. Enter your **Firebase Sender ID**. 4. Upload your **Service Account JSON** file. 5. Click **Update** and copy the **App ID** shown on the page. ## Step 4 — Initialize the SDK If your application does not have a custom `Application` class, create one: PEApplication.kt ```kotlin import android.app.Application import com.pushengage.pushengage.PushEngage class PEApplication : Application() { override fun onCreate() { super.onCreate() val pushEngage = PushEngage.Builder() .addContext(applicationContext) .setAppId("YOUR_APP_ID") .build() // Recommended: set a branded small icon instead of the default bell PushEngage.setSmallIconResource("your_icon_name") } } ``` PEApplication.java ```java import android.app.Application; import com.pushengage.pushengage.PushEngage; public class PEApplication extends Application { @Override public void onCreate() { super.onCreate(); PushEngage pushEngage = new PushEngage.Builder() .addContext(getApplicationContext()) .setAppId("YOUR_APP_ID") .build(); // Recommended: set a branded small icon instead of the default bell PushEngage.setSmallIconResource("your_icon_name"); } } ``` Register the custom Application class in `AndroidManifest.xml`: AndroidManifest.xml ```xml ``` note Replace `YOUR_APP_ID` with the App ID from Step 3. The `setSmallIconResource` name must match a drawable resource in your `res/drawable/` directory. If omitted or invalid, a default bell icon is used. ## Step 5 — Request Notification Permission On Android 13 (API 33) and above, you must request notification permission explicitly. Call this from your main Activity — which must extend `ComponentActivity` (e.g., `AppCompatActivity`, `FragmentActivity`, or `androidx.activity.ComponentActivity` directly). If your Activity extends plain `android.app.Activity`, switch its base class first. MainActivity.kt ```kotlin PushEngage.requestNotificationPermission(this, object : PushEngagePermissionCallback { override fun onPermissionResult(granted: Boolean, error: Error?) { if (granted) { // The SDK automatically subscribes the user when permission is granted Log.d("PushEngage", "Permission granted and user subscribed") } else { Log.d("PushEngage", "Permission denied: ${error?.message}") } } }) ``` MainActivity.java ```java PushEngage.requestNotificationPermission(this, new PushEngagePermissionCallback() { @Override public void onPermissionResult(boolean granted, Error error) { if (granted) { // The SDK automatically subscribes the user when permission is granted Log.d("PushEngage", "Permission granted and user subscribed"); } else { Log.d("PushEngage", "Permission denied: " + (error != null ? error.getMessage() : "")); } } }); ``` note On Android 12 (API 32) and below, notification permission is automatically granted. The callback fires immediately with `granted = true`. ## Step 6 — Send a Test Notification 1. Build and run your app on a **physical Android device** — push notifications do not work in the emulator. 2. Grant notification permission when prompted. 3. In the PushEngage Dashboard, go to **Campaign → Push Broadcasts → Create New Push Broadcast** and send a test. You should receive the notification within a few seconds. Your integration is complete. ## Troubleshooting **Build error: "google-services plugin not applied"** Ensure `id 'com.google.gms.google-services'` is in the `plugins` block of your app-level `build.gradle`, applied **after** `com.android.application`. **Notification permission denied — no second prompt** On Android 13+, after denial the system blocks subsequent `requestNotificationPermission` calls. The only path back is **Settings → Apps → → Notifications**. Surface a one-tap deep link to `Settings.ACTION_APP_NOTIFICATION_SETTINGS` in your UI. **Notifications not received on device** - Confirm `google-services.json` is at `android/app/google-services.json` (not the project root). - Confirm the Sender ID and Service Account JSON in the PushEngage Dashboard match your Firebase project. - Test on a physical device, not an emulator (FCM requires Google Play Services). - Enable verbose logging: `PushEngage.enableLogging(true)` — watch for the FCM token registration log line. **Notifications work for a while, then stop** Some Android device manufacturers aggressively restrict background services for apps the user has force-stopped. Ask affected users to disable battery optimization for your app: **Settings → Battery → Battery Optimization → Don't optimize**. **Custom small icon not appearing** If `setSmallIconResource("name")` references a drawable that doesn't resolve, the SDK silently uses a default bell icon. Verify the drawable exists at `res/drawable/name.xml` (or `.png`). ## Next Steps - [Android SDK Reference →](/api/mobile-sdk/android/sdk-reference) - [Mobile SDK Overview →](/api/mobile-sdk) --- Source: https://www.pushengage.com/api/mobile-sdk/android/sdk-reference # Android SDK Reference Complete API reference for the PushEngage Android SDK. For setup and installation, see the [Android Quickstart](/api/mobile-sdk/android/quickstart). The SDK supports Java and Kotlin. Android API 16 (Android 4.1) or higher is required; API 21+ recommended. [![GitHub release](https://img.shields.io/github/v/release/awesomemotive/pushengage-android-sdk.svg?label=GitHub)](https://github.com/awesomemotive/pushengage-android-sdk/releases) ## Initialization ### PushEngage.Builder Initializes the SDK. Call this once in your `Application.onCreate()` before using any other SDK feature. The builder registers the application context and your App ID, and `build()` installs the SDK singleton. The App ID can be obtained from your [PushEngage Dashboard](https://app.pushengage.com) under **Site Settings → Installation → Android SDK**. note This must be called before using any other SDK features. It associates all subsequent operations with your specific PushEngage application. #### Syntax ```java new PushEngage.Builder() .addContext(Context context) .setAppId(String appId) .build() ``` #### Parameters `context`: Your application `Context`. Required — if omitted, `build()` logs an error and returns `null` without initializing the SDK. The SDK stores the application context internally, so passing an `Activity` does not leak it. `appId`: A String representing the App ID from your PushEngage Dashboard. If `null` or blank, initialization still completes but the SDK logs an error and skips sync and subscriber requests until a valid App ID is configured. #### Usage ```java public class PEApplication extends Application { @Override public void onCreate() { super.onCreate(); PushEngage pushEngage = new PushEngage.Builder() .addContext(getApplicationContext()) .setAppId("YOUR_APP_ID") .build(); } } ``` ```kotlin class PEApplication : Application() { override fun onCreate() { super.onCreate() val pushEngage = PushEngage.Builder() .addContext(applicationContext) .setAppId("YOUR_APP_ID") .build() } } ``` Re-initialization Calling `build()` again is safe and refreshes the configuration. If the App ID differs from the previous build, the SDK clears the cached subscriber state and re-registers the device against the new App ID on the next sync. Calling `build()` with an invalid App ID preserves a previously stored valid one. ## Notification Permission ### requestNotificationPermission Requests notification permission from the user. For Android 13 (API 33) and above, this will show the system permission dialog. For older versions, the permission is automatically granted and the callback will be invoked with granted=true. This method works with both ComponentActivity (Compose) and FragmentActivity. When permission is granted, the SDK automatically calls `PushEngage.subscribe()` for you, so you don't need to call it manually in the callback. #### Syntax ```java requestNotificationPermission(ComponentActivity activity, PushEngagePermissionCallback callback) ``` #### Parameters `activity`: The `ComponentActivity` (works with both Compose and traditional Activities) `callback`: The `PushEngagePermissionCallback` callback to be invoked with the permission result #### Usage ```java // Works with ComponentActivity (Compose) PushEngage.requestNotificationPermission(this, new PushEngagePermissionCallback() { @Override public void onPermissionResult(boolean granted, Error error) { if (granted) { Log.d("Permission", "User is now subscribed!"); } else { // Handle permission denied Log.d("Permission", "Permission denied: " + error.getMessage()); } } }); ``` ```kotlin // Works with ComponentActivity (Compose) PushEngage.requestNotificationPermission(this, object : PushEngagePermissionCallback { override fun onPermissionResult(granted: Boolean, error: Error?) { if (granted) { Log.d("Permission", "User is now subscribed!") } else { // Handle permission denied Log.d("Permission", "Permission denied: ${error?.message}") } } }) ``` ### getNotificationPermissionStatus Get the current notification permission status for the application. This method returns the permission status synchronously as a string. #### Syntax ```java getNotificationPermissionStatus() ``` #### Returns A `String` indicating the current notification permission state: - `"granted"`: The application is authorized to post user notifications - `"denied"`: The application is not authorized to post user notifications #### Usage ```java String permissionStatus = PushEngage.getNotificationPermissionStatus(); switch (permissionStatus) { case "granted": Log.d("Permission", "Notifications are allowed"); break; case "denied": Log.d("Permission", "Notifications are denied"); break; default: Log.d("Permission", "Unknown permission status"); break; } ``` ```kotlin val permissionStatus = PushEngage.getNotificationPermissionStatus() when (permissionStatus) { "granted" -> Log.d("Permission", "Notifications are allowed") "denied" -> Log.d("Permission", "Notifications are denied") else -> Log.d("Permission", "Unknown permission status") } ``` ## Subscription ### subscribe Subscribe the user to push notifications. This method checks the current permission status and subscription state to determine the appropriate action. If notification permission is not granted, it will automatically request permission first. The callback will invoke `onSuccess()` when the user successfully subscribes or `onFailure()` if the subscription fails (including cases where permission is denied by the user). #### Syntax ```java subscribe(ComponentActivity activity, PushEngageResponseCallback callback) ``` #### Parameters `activity`: The `ComponentActivity` required for permission handling (works with both Compose and traditional Activities) `callback`: The callback to be invoked with the subscribe result. Returns success or failure of the subscription operation. #### Usage ```java PushEngage.subscribe(this, new PushEngageResponseCallback() { @Override public void onSuccess(Object result) { Log.d("Subscription", "User subscribed successfully"); } @Override public void onFailure(Integer errorCode, String errorMessage) { Log.e("Subscription", "Failed to subscribe: " + errorMessage); } }); ``` ```kotlin PushEngage.subscribe(this, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Log.d("Subscription", "User subscribed successfully") } override fun onFailure(errorCode: Int?, errorMessage: String?) { Log.e("Subscription", "Failed to subscribe: $errorMessage") } }) ``` ### unsubscribe Unsubscribe the user from push notifications. This stops the user from receiving notifications but keeps their profile and preferences in the system. The user can be re-subscribed later using the `subscribe()` method. #### Syntax ```java unsubscribe(PushEngageResponseCallback callback) ``` #### Parameters `callback`: The callback to be invoked with the unsubscribe result. - `result`: A boolean indicating the unsubscription result. - Will be `true` if the unsubscription was successful. - Will be `false` if the unsubscription operation failed. #### Usage ```java PushEngage.unsubscribe(new PushEngageResponseCallback() { @Override public void onSuccess(Object result) { Log.d("Subscription", "User unsubscribed successfully"); } @Override public void onFailure(Integer errorCode, String errorMessage) { Log.e("Subscription", "Failed to unsubscribe: " + errorMessage); } }); ``` ```kotlin PushEngage.unsubscribe(object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Log.d("Subscription", "User unsubscribed successfully") } override fun onFailure(errorCode: Int?, errorMessage: String?) { Log.e("Subscription", "Failed to unsubscribe: $errorMessage") } }) ``` ### getSubscriptionStatus Check whether the user is currently subscribed to push notifications. #### Syntax ```java getSubscriptionStatus(PushEngageResponseCallback callback) ``` #### Parameters `callback`: The callback to be invoked with the subscription status result. - `result`: A boolean indicating the user's subscription status. - Will be `true` if user is subscribed to push notifications. - Will be `false` if user is not subscribed (unsubscribed or never subscribed). #### Usage ```java PushEngage.getSubscriptionStatus(new PushEngageResponseCallback() { @Override public void onSuccess(Object result) { Boolean isSubscribed = (Boolean) result; if (isSubscribed) { Log.d("Subscription", "User is subscribed"); } else { Log.d("Subscription", "User is not subscribed"); } } @Override public void onFailure(Integer errorCode, String errorMessage) { Log.e("Subscription", "Error checking subscription: " + errorMessage); } }); ``` ```kotlin PushEngage.getSubscriptionStatus(object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { val isSubscribed = responseObject as? Boolean ?: false if (isSubscribed) { Log.d("Subscription", "User is subscribed") } else { Log.d("Subscription", "User is not subscribed") } } override fun onFailure(errorCode: Int?, errorMessage: String?) { Log.e("Subscription", "Error checking subscription: $errorMessage") } }) ``` ### getSubscriptionNotificationStatus Check whether the user can actually receive push notifications by verifying both subscription status and notification permission. The user can receive notifications only if they are subscribed AND the app has notification permission granted. #### Syntax ```java getSubscriptionNotificationStatus(PushEngageResponseCallback callback) ``` #### Parameters `callback`: The callback to be invoked with the subscription notification status. - `result`: A boolean indicating whether the user can receive notifications. - Will be `true` if user can receive notifications (subscribed AND permission granted). - Will be `false` if user cannot receive notifications (not subscribed or permission denied). #### Usage ```java PushEngage.getSubscriptionNotificationStatus(new PushEngageResponseCallback() { @Override public void onSuccess(Object result) { Boolean canReceiveNotifications = (Boolean) result; if (canReceiveNotifications) { Log.d("Subscription", "User can receive notifications"); } else { Log.d("Subscription", "User cannot receive notifications"); } } @Override public void onFailure(Integer errorCode, String errorMessage) { Log.e("Subscription", "Error checking notification status: " + errorMessage); } }); ``` ```kotlin PushEngage.getSubscriptionNotificationStatus(object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { val canReceiveNotifications = responseObject as? Boolean ?: false if (canReceiveNotifications) { Log.d("Subscription", "User can receive notifications") } else { Log.d("Subscription", "User cannot receive notifications") } } override fun onFailure(errorCode: Int?, errorMessage: String?) { Log.e("Subscription", "Error checking notification status: $errorMessage") } }) ``` ## Associate Profile ID Profile IDs serve as unique identifiers for your subscribers, enabling you to recognize them across multiple devices. Each subscriber can be assigned just one profile ID. This ID should be a string, and you have the flexibility to use any value, such as an email or phone number. ### getSubscriberId Retrieve the unique subscriber ID for a user. PushEngage generates this ID for every user based on their subscription data. Sometimes, this ID is referred to as the 'subscriber\_hash'. The subscriber ID remains consistent unless there's a change in the user's subscription. If the user is not subscribed, it will return null. #### Syntax ```java getSubscriberId(PushEngageResponseCallback callback) ``` #### Parameters `callback`: The callback to be invoked with the subscriber ID result. - `subscriberId`: The unique subscriber ID for the user. - Will be a `String` containing the subscriber ID if the user is subscribed. - Will be `null` if the user is not subscribed. #### Usage ```java PushEngage.getSubscriberId(new PushEngageResponseCallback() { @Override public void onSuccess(Object result) { String subscriberId = (String) result; if (subscriberId != null) { Log.d("Subscriber", "Subscriber ID: " + subscriberId); } else { Log.d("Subscriber", "User is not subscribed"); } } @Override public void onFailure(Integer errorCode, String errorMessage) { Log.e("Subscriber", "Error getting subscriber ID: " + errorMessage); } }); ``` ```kotlin PushEngage.getSubscriberId(object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { val subscriberId = responseObject as? String if (subscriberId != null) { Log.d("Subscriber", "Subscriber ID: $subscriberId") } else { Log.d("Subscriber", "User is not subscribed") } } override fun onFailure(errorCode: Int?, errorMessage: String?) { Log.e("Subscriber", "Error getting subscriber ID: $errorMessage") } }) ``` ### addProfileId This method allows you to set a profile ID for the current subscriber. If a profile ID already exists, it will be replaced with the new value. #### Syntax ```java addProfileId(String profileId, PushEngageResponseCallback callback) ``` #### Parameters `profileId`: A String representing the profile ID to be associated with the subscriber. `callback`(optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java PushEngage.addProfileId("your_id", new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin PushEngage.addProfileId("your_id", object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ## User Identity The `identify` and `logout` methods manage the predefined personal-data fields stored on the current subscriber. Use them to tie a push subscription to your own first-party user data, and to clear that data on sign-out without unsubscribing the device. The 12 valid keys are: `first_name`, `last_name`, `email`, `phone`, `gender`, `dob`, `language`, `profile_id`, `country`, `city`, `state`, `zip`. Values must be `String`, `Number`, or `Boolean` — other types are rejected client-side. ### identify Upsert one or more of the 12 predefined subscriber fields. If every requested field already matches the locally cached value (and the cache is fresh — within 24 hours of the last successful sync), the call short-circuits with `onSuccess(null)` and no network request is sent. A numeric `profile_id` is coerced to its string form before being sent. #### Syntax ```java identify(JSONObject fields, PushEngageResponseCallback callback) ``` #### Parameters `fields`: A `JSONObject` containing one or more of the 12 valid subscriber fields. Must contain at least one key. `callback` (optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java try { JSONObject fields = new JSONObject(); fields.put("first_name", "Jane"); fields.put("last_name", "Doe"); fields.put("email", "jane@example.com"); fields.put("profile_id", "user_12345"); PushEngage.identify(fields, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); } catch (JSONException e) { e.printStackTrace(); } ``` ```kotlin val fields = JSONObject().apply { put("first_name", "Jane") put("last_name", "Doe") put("email", "jane@example.com") put("profile_id", "user_12345") } PushEngage.identify(fields, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int?, errorMessage: String?) { //Handle the Failure event. } }) ``` ### logout Remove a set of predefined personal fields from the current subscriber while keeping the device subscribed for push. Call this when a user signs out of your app to detach their PII from the push subscription. Passing `null` or an empty list removes the default PII field set: `first_name`, `last_name`, `email`, `phone`, `gender`, `dob`, `profile_id`. Passing a list of field names scopes the removal to those specific fields. If none of the requested field names exist in the locally cached subscriber data (and the cache is fresh — within 24 hours of the last successful sync), the call short-circuits with `onSuccess(null)` and no network request is sent. #### Syntax ```java logout(List fieldNames, PushEngageResponseCallback callback) ``` #### Parameters `fieldNames`: A `List` of field names to remove. Pass `null` or an empty list to clear the default PII set. `callback` (optional): An interface designed as a callback to handle API responses asynchronously. #### Usage Clear the default PII set: ```java PushEngage.logout(null, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin PushEngage.logout(null, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int?, errorMessage: String?) { //Handle the Failure event. } }) ``` Clear specific fields: ```java List fieldsToClear = new ArrayList<>(); fieldsToClear.add("email"); fieldsToClear.add("phone"); PushEngage.logout(fieldsToClear, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin val fieldsToClear = listOf("email", "phone") PushEngage.logout(fieldsToClear, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int?, errorMessage: String?) { //Handle the Failure event. } }) ``` ## Subscriber Details ### getSubscriberDetails This method retrieves subscriber details based on a provided list of strings, which can include city, state, country, device, device type, segments, etc. #### Syntax ```java getSubscriberDetails(List values, PushEngageResponseCallback callback) ``` #### Parameters `values`: A list of strings that can contain any values from `PushEngage.SubscriberFields`. `callback`: An interface designed as a callback to handle API responses asynchronously. #### Usage ```java List subscriberDetailsList = new ArrayList<>(); subscriberDetailsList.add(PushEngage.SubscriberFields.City); subscriberDetailsList.add(PushEngage.SubscriberFields.State); PushEngage.getSubscriberDetails(subscriberDetailsList, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the failure event. } }); ``` ```kotlin val subscriberDetailsList: MutableList = ArrayList() subscriberDetailsList.add(PushEngage.SubscriberFields.City) subscriberDetailsList.add(PushEngage.SubscriberFields.State) PushEngage.getSubscriberDetails( subscriberDetailsList, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` #### Available Fields | Field | Type | Description | | --- | --- | --- | | `city` | string | Subscriber's city | | `state` | string | Subscriber's state or region | | `country` | string | Subscriber's country | | `device` | string | Device name | | `device_type` | string | Device type (e.g., mobile, tablet) | | `user_agent` | string | App user agent string | | `host` | string | Host identifier | | `timezone` | string | Subscriber's timezone | | `has_unsubscribed` | boolean | Whether the subscriber has unsubscribed | | `ts_created` | string | ISO 8601 timestamp of subscription creation | | `segments` | array | Segment IDs the subscriber belongs to | ## Segments Segments are used to group subscribers so that you can send personalized notifications. Segments can be created based on attributes, categories, and more. ### addSegment This method enables you to add the current subscriber to segments. #### Syntax ```java addSegment(List segmentId, PushEngageResponseCallback callback) ``` #### Parameters `segmentId`: A list of segment IDs to be added. `callback`(optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java List segmentList = new ArrayList(); segmentList.add("sports"); PushEngage.addSegment(segmentList, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin val segmentList: MutableList = ArrayList() segmentList.add("sports") PushEngage.addSegment(segmentList, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ### addDynamicSegment This method enables you to add the current subscriber to a segment for a specified duration, measured in days. After this period, the segment will be automatically removed from the subscriber. #### Syntax ```java addDynamicSegment(List segments, PushEngageResponseCallback callback) ``` #### Parameters `segments`: A list of segment objects representing dynamic segments to be added. - Contains the segment data to be associated with the user. `callback` (optional): An interface designed as a callback to handle API responses asynchronously. - Called when the operation completes with success or failure information. #### Usage ```java AddDynamicSegmentRequest addDynamicSegmentRequest = new AddDynamicSegmentRequest(); List segments = new ArrayList<>(); AddDynamicSegmentRequest.Segment segment = addDynamicSegmentRequest.new Segment("sports", 5); segments.add(segment); PushEngage.addDynamicSegment(segments, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin val addDynamicSegmentRequest = AddDynamicSegmentRequest() val segments: MutableList = ArrayList() val segment = addDynamicSegmentRequest.Segment("sports", 5) segments.add(segment) PushEngage.addDynamicSegment(segments, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ### removeSegment This method allows you to remove the current subscriber from segments. #### Syntax ```java removeSegment(List segmentId, PushEngageResponseCallback callback) ``` #### Parameters `segmentId`: A list of segment IDs to be removed. `callback`(optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java List segmentList = new ArrayList(); segmentList.add("sports"); PushEngage.removeSegment(segmentList, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin val segmentList: MutableList = ArrayList() segmentList.add("sports") PushEngage.removeSegment(segmentList, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ## Attributes Attributes are key-value pairs that allow you to store additional information about your subscribers. You can utilize attributes to segment your subscribers and send personalized notifications. ### addSubscriberAttributes Use this method to add or update attributes for a subscriber. If an attribute with the specified key already exists, the existing value will be replaced. #### Syntax ```java addSubscriberAttributes(JSONObject obj, PushEngageResponseCallback callback) ``` #### Parameters `obj`: A `JSONObject` containing the subscriber attributes to be added. `callback`(optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java try { JSONObject jsonObject = new JSONObject(); jsonObject.put("age", "25"); jsonObject.put("height", "6.1"); PushEngage.addSubscriberAttributes(jsonObject, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); } catch (JSONException e) { e.printStackTrace(); } ``` ```kotlin val jsonObject = JSONObject() jsonObject.put("age", "25") jsonObject.put("height", "6.1") PushEngage.addSubscriberAttributes( jsonObject, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ### setSubscriberAttributes This method allows you to set attributes for a subscriber, replacing any previously associated attributes. Use this method when you need to entirely reset the attributes with new values. #### Syntax ```java setSubscriberAttributes(JSONObject obj, PushEngageResponseCallback callback) ``` #### Parameters `obj`: A `JSONObject` containing the updated subscriber attributes. `callback`(optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java try { JSONObject jsonObject = new JSONObject(); jsonObject.put("age", "25"); jsonObject.put("height", "6.1"); PushEngage.setSubscriberAttributes(jsonObject, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); } catch (JSONException e) { e.printStackTrace(); } ``` ```kotlin val jsonObject = JSONObject() jsonObject.put("age", "25") jsonObject.put("height", "6.1") PushEngage.setSubscriberAttributes( jsonObject, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ### getSubscriberAttributes Retrieve the attributes associated with the current subscriber using this method. #### Syntax ```java getSubscriberAttributes(PushEngageResponseCallback callback) ``` #### Parameters `callback`: An interface designed as a callback to handle API responses asynchronously. - `result`: The subscriber attributes data. - Will contain the user's attributes if successfully retrieved. - Will be `null` if no attributes are found or retrieval fails. #### Usage ```java PushEngage.getSubscriberAttributes(new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { // Handle the Failure event. } }); ``` ```kotlin PushEngage.getSubscriberAttributes(object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ### deleteSubscriberAttributes This method allows you to remove one or more attributes from the current subscriber. Provide an array of attribute names you wish to remove. Passing an empty array will result in the removal of all the subscriber's attributes. #### Syntax ```java deleteSubscriberAttributes(List values, PushEngageResponseCallback callback) ``` #### Parameters `values`: A `List` containing attribute names to be deleted. `callback`(optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java List attributeList = new ArrayList<>(); attributeList.add("age"); attributeList.add("height"); PushEngage.deleteSubscriberAttributes(attributeList, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin val attributeList: MutableList = ArrayList() attributeList.add("age") attributeList.add("height") PushEngage.deleteSubscriberAttributes(attributeList, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int, errorMessage: String) { //Handle the Failure event. } }) ``` ## Automated Notifications Automated notifications include all types of triggered campaigns, such as cart abandonment, price drop, back in stock, and browse abandonment. By default, automated notifications are enabled for all subscribers. ### automatedNotification This method allows you to enable/disable automated notifications for the current subscriber. #### Syntax ```java automatedNotification(TriggerStatusType status, PushEngageResponseCallback callback) ``` #### Parameters `status`: The trigger status type indicating the status of the trigger campaign. `callback`: An interface designed as a callback to handle API responses asynchronously. #### Usage Enable Automated Notifications ```java PushEngage.automatedNotification(PushEngage.TriggerStatusType.enabled, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { Toast.makeText(TriggerCampaignActivity.this, "Trigger Enabled Successfully", Toast.LENGTH_LONG).show(); } @Override public void onFailure(Integer errorCode, String errorMessage) { Toast.makeText(TriggerCampaignActivity.this, "Trigger Enabled Failed", Toast.LENGTH_LONG).show(); } }); ``` ```kotlin PushEngage.automatedNotification(PushEngage.TriggerStatusType.enabled, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Toast.makeText(this@TriggerCampaignActivity, "Trigger Enabled Successfully", Toast.LENGTH_LONG).show() } override fun onFailure(errorCode: Int?, errorMessage: String?) { Toast.makeText(this@TriggerCampaignActivity, "Trigger Enabled Failed", Toast.LENGTH_LONG).show() } }) ``` Disable Automated Notifications ```java PushEngage.automatedNotification(PushEngage.TriggerStatusType.disabled, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { Toast.makeText(TriggerCampaignActivity.this, "Trigger Disabled Successfully", Toast.LENGTH_LONG).show(); } @Override public void onFailure(Integer errorCode, String errorMessage) { Toast.makeText(TriggerCampaignActivity.this, "Trigger Disabled Failed", Toast.LENGTH_LONG).show(); } }); ``` ```kotlin PushEngage.automatedNotification(PushEngage.TriggerStatusType.disabled, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Toast.makeText(this@TriggerCampaignActivity, "Trigger Disabled Successfully", Toast.LENGTH_LONG).show() } override fun onFailure(errorCode: Int?, errorMessage: String?) { Toast.makeText(this@TriggerCampaignActivity, "Trigger Disabled Failed", Toast.LENGTH_LONG).show() } }) ``` ## Triggered Campaigns ### sendTriggerEvent Detect your visitor's behavior to send automated push notifications to the right person at the right time. #### Syntax ```java sendTriggerEvent(TriggerCampaign trigger, PushEngageResponseCallback callback) ``` #### Parameters `trigger`: The TriggerCampaign object representing the campaign event to be triggered. `callback`: An interface designed as a callback to handle API responses asynchronously. #### Usage ```java Map dataMap = new HashMap<>(); dataMap.put("custom_key", "custom_value"); TriggerCampaign triggerCampaign = new TriggerCampaign( "name_of_campaign", "name_of_event", "your_reference_id", // referenceId (optional) null, // profileId (optional) dataMap // data (optional) ); PushEngage.sendTriggerEvent(triggerCampaign, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { Toast.makeText(TriggerEntryActivity.this, "Send Trigger Alert Successfully", Toast.LENGTH_LONG).show(); } @Override public void onFailure(Integer errorCode, String errorMessage) { Toast.makeText(TriggerEntryActivity.this, errorMessage != null ? errorMessage : "Error occurred", Toast.LENGTH_LONG).show(); } }); ``` ```kotlin val dataMap: MutableMap = mutableMapOf() dataMap["custom_key"] = "custom_value" val triggerCampaign = TriggerCampaign( campaignName = "name_of_campaign", eventName = "name_of_event", referenceId = "your_reference_id", // optional data = dataMap // optional ) PushEngage.sendTriggerEvent(triggerCampaign, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Toast.makeText(this@TriggerEntryActivity, "Send Trigger Alert Successfully", Toast.LENGTH_LONG).show() } override fun onFailure(errorCode: Int?, errorMessage: String?) { Toast.makeText(this@TriggerEntryActivity, errorMessage.toString(), Toast.LENGTH_LONG).show() } }) ``` ### addAlert Re-engage your customers and increase conversion using Price Drop Alert Campaigns and Inventory Alert Campaigns. #### Syntax ```java addAlert(TriggerAlert alert, PushEngageResponseCallback callback) ``` #### Parameters `alert`: A `TriggerAlert` object describing the alert. All positional fields must be provided when constructing from Java; Kotlin callers may use named parameters and omit optional fields. | Field | Type | Required | Description | | --- | --- | :-: | --- | | `type` | `PushEngage.TriggerAlertType` | Yes | Alert type: `priceDrop` or `inventory` | | `productId` | `String` | Yes | Unique identifier for the product | | `link` | `String` | Yes | URL to the product page | | `price` | `Double` | Yes | Current price of the product | | `variantId` | `String?` | No | Product variant identifier | | `expiryTimestamp` | `Date?` | No | When the alert expires | | `alertPrice` | `Double?` | No | Target price that triggers the alert. Omitted from the request when null; the server may apply a default. | | `availability` | `PushEngage.TriggerAlertAvailabilityType?` | No | `inStock` or `outOfStock`. Omitted from the request when null; the server may apply a default. | | `profileId` | `String?` | No | Associate the alert with a specific subscriber profile | | `mrp` | `Double?` | No | Maximum retail price of the product | | `data` | `Map?` | No | Custom key-value pairs to attach to the alert | `callback`: An interface designed as a callback to handle API responses asynchronously. #### Usage Price Drop ```java Map dataMap = new HashMap<>(); dataMap.put("custom_key", "custom_value"); TriggerAlert triggerAlert = new TriggerAlert( PushEngage.TriggerAlertType.priceDrop, // type "product_id", // productId "product_link", // link 100.0, // price "product_variant_id", // variantId (optional) null, // expiryTimestamp: Date (optional) 102.0, // alertPrice (optional) PushEngage.TriggerAlertAvailabilityType.inStock, // availability (optional) null, // profileId (optional) null, // mrp (optional) dataMap // data (optional) ); PushEngage.addAlert(triggerAlert, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { Toast.makeText(AddAlertActivity.this, "Add Alert Successfully", Toast.LENGTH_LONG).show(); } @Override public void onFailure(Integer errorCode, String errorMessage) { Toast.makeText(AddAlertActivity.this, errorMessage, Toast.LENGTH_LONG).show(); } }); ``` ```kotlin val dataMap: MutableMap = mutableMapOf() dataMap["custom_key"] = "custom_value" val triggerAlert = TriggerAlert( type = PushEngage.TriggerAlertType.priceDrop, productId = "product_id", link = "product_link", price = 100.0, variantId = "product_variant_id", // optional alertPrice = 102.0, // optional availability = PushEngage.TriggerAlertAvailabilityType.inStock, // optional data = dataMap // optional ) PushEngage.addAlert(triggerAlert, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Toast.makeText(this@AddAlertActivity, "Add Alert Successfully", Toast.LENGTH_LONG).show() } override fun onFailure(errorCode: Int?, errorMessage: String?) { Toast.makeText(this@AddAlertActivity, errorMessage, Toast.LENGTH_LONG).show() } }) ``` Back in Stock Alert ```java Map dataMap = new HashMap<>(); dataMap.put("custom_key", "custom_value"); TriggerAlert triggerAlert = new TriggerAlert( PushEngage.TriggerAlertType.inventory, // type "product_id", // productId "product_link", // link 100.0, // price "product_variant_id", // variantId (optional) null, // expiryTimestamp: Date (optional) null, // alertPrice (optional) PushEngage.TriggerAlertAvailabilityType.outOfStock, // availability (optional; omitted when null, server may default) null, // profileId (optional) null, // mrp (optional) dataMap // data (optional) ); PushEngage.addAlert(triggerAlert, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { Toast.makeText(AddAlertActivity.this, "Add Alert Successfully", Toast.LENGTH_LONG).show(); } @Override public void onFailure(Integer errorCode, String errorMessage) { Toast.makeText(AddAlertActivity.this, errorMessage, Toast.LENGTH_LONG).show(); } }); ``` ```kotlin val dataMap: MutableMap = mutableMapOf() dataMap["custom_key"] = "custom_value" val triggerAlert = TriggerAlert( type = PushEngage.TriggerAlertType.inventory, productId = "product_id", link = "product_link", price = 100.0, variantId = "product_variant_id", // optional availability = PushEngage.TriggerAlertAvailabilityType.outOfStock, // optional; omitted when null, server may default data = dataMap // optional ) PushEngage.addAlert(triggerAlert, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Toast.makeText(this@AddAlertActivity, "Add Alert Successfully", Toast.LENGTH_LONG).show() } override fun onFailure(errorCode: Int?, errorMessage: String?) { Toast.makeText(this@AddAlertActivity, errorMessage, Toast.LENGTH_LONG).show() } }) ``` ## Custom Events ### trackEvent Track a custom event for the current subscriber. Custom events are used to start or exit campaign workflows based on in-app activity — for example, adding an item to cart, completing a purchase, or any other action you define. Workflows are configured from the PushEngage Dashboard. #### Syntax ```java trackEvent(TrackEvent event, PushEngageResponseCallback callback) ``` #### Parameters `event`: A `TrackEvent` object describing the event payload. | Field | Type | Required | Description | | --- | --- | :-: | --- | | `eventName` | `String` | Yes | Name of the event (e.g., `"MySite.AddToCart"`). | | `data` | `Map` | No | Custom key-value data. Values must be strings, numbers, or booleans — other types are rejected client-side with error code `400` before any network request is made. | | `profileId` | `String` | No | The profile ID of the subscriber. | | `provider` | `String` | No | Provider name. Defaults to `"PushEngage"`. | | `eventType` | `String` | No | Event type. Defaults to `"PushEngage.CustomEvent"`. | `callback` (optional): An interface designed as a callback to handle API responses asynchronously. #### Usage ```java Map eventData = new HashMap<>(); eventData.put("product_id", "123"); eventData.put("product_name", "Product Name"); eventData.put("price", 49.99); TrackEvent event = new TrackEvent("MySite.AddToCart", eventData); PushEngage.trackEvent(event, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { //Handle the Success event. } @Override public void onFailure(Integer errorCode, String errorMessage) { //Handle the Failure event. } }); ``` ```kotlin val eventData = mapOf( "product_id" to "123", "product_name" to "Product Name", "price" to 49.99 ) val event = TrackEvent(eventName = "MySite.AddToCart", data = eventData) PushEngage.trackEvent(event, object : PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { //Handle the Success event. } override fun onFailure(errorCode: Int?, errorMessage: String?) { //Handle the Failure event. } }) ``` ## Goal Tracking Goal Tracking will help you assign conversion goals & value to your notification campaigns. You can set up a default goal and have it integrated for all your campaigns. ### sendGoal #### Syntax ```java sendGoal(Goal goal, PushEngageResponseCallback callback) ``` #### Parameters `goal`: Goal object representing the goal to be tracked. `callback`: An interface designed as a callback to handle API responses asynchronously. #### Usage ```java Goal goal = new Goal("name_of_goal", 1, 10.0); PushEngage.sendGoal(goal, new PushEngageResponseCallback() { @Override public void onSuccess(Object responseObject) { Toast.makeText(GoalActivity.this, "Goal Added Successfully", Toast.LENGTH_LONG).show(); } @Override public void onFailure(Integer errorCode, String errorMessage) { Toast.makeText(GoalActivity.this, "Failure", Toast.LENGTH_LONG).show(); } }); ``` ```kotlin val goal = Goal("name_of_goal", 1, 10.0) PushEngage.sendGoal(goal, object: PushEngageResponseCallback { override fun onSuccess(responseObject: Any?) { Toast.makeText(this@GoalActivity, "Goal Added Successfully", Toast.LENGTH_LONG).show() } override fun onFailure(errorCode: Int?, errorMessage: String?) { Toast.makeText(this@GoalActivity, "Failure", Toast.LENGTH_LONG).show() } }) ``` ## Utilities ### getSdkVersion Retrieves the current version of the PushEngage Android SDK, returning a string that represents the SDK's current version. #### Syntax ```java getSdkVersion() ``` #### Returns A String representing the current version of the PushEngage Android SDK. #### Usage ```java PushEngage.getSdkVersion(); ``` ```kotlin PushEngage.getSdkVersion() ``` ### setSmallIconResource Sets the resource name of the small icon used for notifications. The small icon appears in the status bar when a notification is displayed. #### Syntax ```java setSmallIconResource(String resourceName) ``` #### Parameters `resourceName`: A string representing the resource name of the small icon. #### Usage ```java String resourceName = "your_small_icon_name"; PushEngage.setSmallIconResource(resourceName); ``` ```kotlin val resourceName = "your_small_icon_name" PushEngage.setSmallIconResource(resourceName) ``` note It is recommended to set a valid resource name to ensure proper display of notifications. If an invalid resource name is provided, the default bell icon specified by the PushEngage library will be used. ### setBadgeCount Control the numeric badge associated with notifications the SDK builds afterwards. Android does not expose a system API for a numeric launcher-icon badge — only a dot indicator on supported launchers, plus a count surfaced in the long-press shortcut menu. - `count == 0` — clears all active notifications, which removes the launcher dot. - `count > 0` — applied via `NotificationCompat.Builder.setNumber(count)` to notifications the SDK builds afterwards; the value appears in the long-press menu. - Negative values are coerced to `0`. #### Syntax ```java setBadgeCount(int count) ``` #### Parameters `count`: The badge count to set. Use `0` to clear. #### Usage ```java PushEngage.setBadgeCount(5); // Clear the badge PushEngage.setBadgeCount(0); ``` ```kotlin PushEngage.setBadgeCount(5) // Clear the badge PushEngage.setBadgeCount(0) ``` ### setFcmConfigErrorListener Register a callback that fires when the SDK detects a mismatch between your app's local Firebase configuration and the configuration registered for your PushEngage site. Use this during integration to catch sender-ID or project-ID drift early — when a mismatch is detected at sync time, the SDK also skips the subscriber-add call so a permanently-undeliverable subscriber is not created on the server. The listener is invoked with one of these error codes: | Code | Constant | Meaning | | --- | --- | --- | | `5001` | `FCM_SENDER_ID_MISMATCH` | `google-services.json` sender ID does not match the PushEngage dashboard. | | `5002` | `FCM_PROJECT_ID_MISMATCH` | `google-services.json` project ID does not match the service-account JSON. | | `5003` | `FCM_LOCAL_CONFIG_INVALID` | Firebase Installations rejected `google-services.json` as invalid for the app. | | `5004` | `FCM_CONFIG_BOTH_MISMATCH` | Both sender ID and project ID differ from the dashboard configuration. | note The listener may be invoked on a background thread (OkHttp dispatcher, Firebase Installations callback, or the caller's thread). Marshal to the UI thread before touching UI. Pass `null` to clear a previously-registered listener. #### Syntax ```java setFcmConfigErrorListener(FcmConfigErrorListener listener) ``` #### Parameters `listener`: The `FcmConfigErrorListener` to invoke on config errors, or `null` to clear. #### Usage ```java PushEngage.setFcmConfigErrorListener(new FcmConfigErrorListener() { @Override public void onFcmConfigError(int errorCode, String message) { Log.e("PushEngage", "FCM config error " + errorCode + ": " + message); } }); ``` ```kotlin PushEngage.setFcmConfigErrorListener { errorCode, message -> Log.e("PushEngage", "FCM config error $errorCode: $message") } ``` ### enableLogging Enables or disables verbose debug logging for the SDK. When enabled, the SDK prints detailed logs to Logcat that are useful for diagnosing integration issues. danger Disable logging in production builds to avoid leaking internal SDK state to device logs. #### Syntax ```java enableLogging(boolean shouldEnable) ``` #### Parameters `shouldEnable`: Pass `true` to turn on debug logging, `false` to turn it off. #### Usage ```java // Enable during development PushEngage.enableLogging(true); // Disable for production PushEngage.enableLogging(false); ``` ```kotlin // Enable during development PushEngage.enableLogging(true) // Disable for production PushEngage.enableLogging(false) ``` --- Source: https://www.pushengage.com/api/mobile-sdk/flutter/quickstart # Flutter Quickstart This guide walks you through integrating PushEngage push notifications into a Flutter application targeting both Android and iOS. Estimated time: **15 minutes**. After completing this guide, your app will receive push notifications on both platforms. See the [Flutter SDK Reference](/api/mobile-sdk/flutter/sdk-reference) for the full API. Migration Guide: 0.0.x → 1.0.0 1.0.0 bumps the underlying iOS native SDK to 1.0.0, which splits the SDK into two CocoaPods so notification extension targets no longer link app-only code. Existing iOS integrations need three changes: 1. **Remove the Podfile build-setting override** — delete the `APPLICATION_EXTENSION_API_ONLY = 'No'` line from your `post_install` block. It is no longer required. 2. **Switch extension target pods** — in `ios/Podfile`, replace `pod 'PushEngage', '0.0.6'` (or earlier) inside each extension target block with `pod 'PushEngageExtension'`. The main `Runner` target continues to use `pod 'PushEngage'` (pulled transitively by `pushengage_flutter_sdk`). 3. **Update extension Swift code** — in `NotificationService.swift` (and `NotificationViewController.swift` if present), replace `import PushEngage` with `import PushEngageExtension`, and the `PushEngage` class prefix on extension API calls with `PushEngageExtension`. Method names are unchanged. ```swift // Before import PushEngage PushEngage.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) PushEngage.serviceExtensionTimeWillExpire(request, content: bestAttemptContent) PushEngage.getCustomUIPayLoad(for: notification.request) // After import PushEngageExtension PushEngageExtension.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) PushEngageExtension.serviceExtensionTimeWillExpire(request, content: bestAttemptContent) PushEngageExtension.getCustomUIPayLoad(for: notification.request) ``` **Dart API breaking change:** `deepLinkStream` events now always carry `data` as a `Map` on both platforms. On Android the SDK previously delivered `data` as a JSON-encoded string — if your handler was decoding it manually, remove that step. ## Prerequisites - Flutter SDK 3.x or higher - Android Studio (for Android builds) and/or Xcode (for iOS builds) - Firebase account ([create one free](https://console.firebase.google.com)) — required for Android - Apple Developer account with a valid App ID — required for iOS - APNs certificate or key ([create one](https://www.pushengage.com/documentation/guide-to-creating-ios-apns-certificate/)) — required for iOS - PushEngage account and App ID (see [Get Your App ID](/api/mobile-sdk#get-your-app-id)) Let an AI agent do the integration for you Every step on this page can be handled by an AI coding agent. Install the official [PushEngage Agent Skills](/api/ai-agents#agent-skills) in Claude Code, Cursor, or another compatible agent: ```bash npx skills add awesomemotive/pushengage-skills ``` Then ask it to _"integrate PushEngage into this app"_. The skills walk the agent through the full setup and can also diagnose a broken integration. ## Step 1 — Install the SDK Add the dependency to `pubspec.yaml`: pubspec.yaml ```yaml dependencies: pushengage_flutter_sdk: ^1.0.0 ``` Then run: ```bash flutter pub get ``` ## Step 2 — Android Setup ### Firebase Cloud Messaging (FCM) 1. Open the [Firebase console](https://console.firebase.google.com) and sign in. 2. Click **Add Project** (or select an existing one). 3. Click the **Android icon** to add an Android app. 4. Enter your app's **package name** (found in `android/app/build.gradle` under `applicationId`). 5. Download **google-services.json** and place it at `android/app/google-services.json`. 6. Generate the **Service Account JSON**: Firebase console → **Settings** → **Service accounts** → **Generate new private key**. 7. Retrieve the **Sender ID**: Firebase console → **Settings** → **Cloud Messaging** tab. ### Apply the Google Services Gradle Plugin `google-services.json` only takes effect when the Google Services Gradle plugin is applied. Add it in two places: 1. In your **project-level** `android/build.gradle`, add the classpath under `buildscript` → `dependencies`: android/build.gradle ```groovy buildscript { dependencies { classpath 'com.google.gms:google-services:4.4.0' } } ``` 2. In your **app-level** `android/app/build.gradle`, apply the plugin: android/app/build.gradle ```groovy plugins { id "com.android.application" id "kotlin-android" id "com.google.gms.google-services" // The Flutter Gradle Plugin must be applied after the Android and Kotlin plugins. id "dev.flutter.flutter-gradle-plugin" } ``` Without this plugin, FCM registration fails and no Android push token is generated. ### Set the Activity Base Class PushEngage's runtime permission and subscribe calls require the host activity to be a `ComponentActivity`. Flutter's default `FlutterActivity` is **not** one, so `requestNotificationPermission()` and `subscribe()` fail with an `INVALID_ACTIVITY` error. For `requestNotificationPermission()` the Dart layer reports this as a silent `false` (no exception is thrown); `subscribe()` returns a failure result with the error attached. Make `MainActivity` extend `FlutterFragmentActivity`: android/app/src/main/kotlin/.../MainActivity.kt ```kotlin import io.flutter.embedding.android.FlutterFragmentActivity class MainActivity: FlutterFragmentActivity() ``` ### Connect to PushEngage Dashboard 1. Log in to your [PushEngage Dashboard](https://app.pushengage.com). 2. Navigate to **Site Settings → Installation → Android SDK** tab. 3. Enter your **Firebase Sender ID** and upload the **Service Account JSON**. 4. Click **Update** and copy the **App ID**. ## Step 3 — iOS Setup Open your iOS workspace in Xcode: ```text your_project_name/ios/Runner.xcworkspace ``` ### Enable Xcode Capabilities 1. Select the root project and choose the **Runner** target. 2. Go to **Signing & Capabilities** → **\+ Capability** → **Push Notifications**. 3. Click **\+ Capability** → **Background Modes**, then check **Remote notifications** and **Background fetch**. ### Connect iOS to PushEngage Dashboard 1. PushEngage Dashboard → **Site Settings → Installation → iOS SDK** tab. 2. Upload your APNs certificate or key. 3. Copy the **App ID**. ## Step 4 — Add Notification Service Extension (iOS) ### Create the Extension 1. In Xcode, go to **File → New → Target** → **Notification Service Extension** → **Next**. 2. Name it `PushEngageNotificationServiceExtension` → **Finish**. Click **Cancel** when prompted to activate. 3. Set the new target's **Deployment Target** to **iOS 12** or above. ### Configure Podfile Open `ios/Podfile` and add the extension target at the bottom of the file: ios/Podfile (additions) ```ruby target 'PushEngageNotificationServiceExtension' do use_frameworks! pod 'PushEngageExtension' end ``` Extension-only module Notification extensions link the companion `PushEngageExtension` pod, not the main `PushEngage` pod. `PushEngageExtension` is the extension-safe core that ships the three notification-extension APIs (`didReceiveNotificationExtensionRequest`, `serviceExtensionTimeWillExpire`, `getCustomUIPayLoad`). Linking the wrong pod can break push delivery on production builds. Run in your `ios/` directory: ```bash pod repo update pod install ``` ### Implement the Extension Replace the auto-generated `NotificationService.swift`: PushEngageNotificationServiceExtension/NotificationService.swift ```swift import UserNotifications import PushEngageExtension @available(iOSApplicationExtension 12.0, *) class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? var request: UNNotificationRequest? override func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { self.request = request self.contentHandler = contentHandler self.bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent if let bestContent = bestAttemptContent { PushEngageExtension.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) contentHandler(bestContent) } } override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let request = request, let bestAttemptContent = bestAttemptContent { guard let content = PushEngageExtension.serviceExtensionTimeWillExpire( request, content: bestAttemptContent ) else { contentHandler(bestAttemptContent) return } contentHandler(content) } } } ``` ## Step 5 — Add Notification Content Extension (iOS, Optional) 1. In Xcode, go to **File → New → Target** → **Notification Content Extension** → **Next**. 2. Name it `PushEngageNotificationContentExtension` → **Finish**. Click **Cancel** when prompted. 3. Set Deployment Target to **iOS 12** or above. 4. Add to `ios/Podfile`: ```ruby target 'PushEngageNotificationContentExtension' do use_frameworks! pod 'PushEngageExtension' end ``` Run `pod install` in the `ios/` directory. ## Step 6 — Add App Groups (iOS) 1. Select the **Runner** target in Xcode → **Signing & Capabilities** → **\+ Capability** → **App Groups**. 2. Add a new App Group: `group.com.yourcompany.yourapp`. 3. In `ios/Runner/Info.plist`, add: - Key: `PushEngage_App_Group_Key` - Value: `group.com.yourcompany.yourapp` 4. In `PushEngageNotificationServiceExtension/Info.plist`, add the same key and value. 5. Select the extension target → enable the same App Group under **Signing & Capabilities**. ## Step 7 — Initialize the SDK In `lib/main.dart`: lib/main.dart ```dart import 'package:flutter/material.dart'; import 'package:pushengage_flutter_sdk/pushengage_flutter_sdk.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); _initPushEngage(); } Future _initPushEngage() async { await PushEngage.setAppId('YOUR_APP_ID'); // Trigger the system permission prompt; on grant, the SDK auto-subscribes. await PushEngage.requestNotificationPermission(); } @override Widget build(BuildContext context) { return const MaterialApp( title: 'My App', home: HomeScreen(), ); } } ``` For iOS distribution, initialize PushEngage in `ios/Runner/AppDelegate.swift`: ios/Runner/AppDelegate.swift ```swift import Flutter import UIKit import PushEngage @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { override init() { super.init() PushEngage.swizzleInjection(isEnabled: true) } override func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { if #available(iOS 10.0, *) { UNUserNotificationCenter.current().delegate = self } GeneratedPluginRegistrant.register(with: self) PushEngage.setBadgeCount(count: 0) PushEngage.setNotificationWillShowInForegroundHandler { notification, completion in if notification.contentAvailable == 1 { completion(nil) } else { completion(notification) } } return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } ``` note Replace `YOUR_APP_ID` with the App ID from Steps 2 or 3. ## Step 8 — Send a Test Notification 1. Build and run on a **physical device** (Android or iOS). 2. Accept the notification permission prompt. 3. In the PushEngage Dashboard → **Campaign → Push Broadcasts → Create New Push Broadcast** → send a test. ## Troubleshooting **Android: notifications not received** Verify `google-services.json` is at `android/app/google-services.json` and that the Sender ID in the PushEngage Dashboard matches Firebase. **iOS: build sandboxing error** In Xcode → **Build Settings** → set **User Script Sandboxing** to **No**. ## Next Steps - [Flutter SDK Reference →](/api/mobile-sdk/flutter/sdk-reference) - [Mobile SDK Overview →](/api/mobile-sdk) --- Source: https://www.pushengage.com/api/mobile-sdk/flutter/sdk-reference # Flutter SDK Reference Complete API reference for the PushEngage Flutter SDK. For setup and installation, see the [Flutter Quickstart](/api/mobile-sdk/flutter/quickstart). The SDK supports Flutter 3.x and above. It uses FCM for Android and APNs for iOS. [![pub.dev](https://img.shields.io/pub/v/pushengage_flutter_sdk.svg?label=pub.dev)](https://pub.dev/packages/pushengage_flutter_sdk) [![GitHub release](https://img.shields.io/github/v/release/awesomemotive/pushengage-flutter-sdk.svg?label=GitHub)](https://github.com/awesomemotive/pushengage-flutter-sdk/releases) ## Initialization ### setAppId Sets the application ID for PushEngage. This method sets the application ID for your PushEngage integration. #### Syntax ```dart setAppId(String appId) ``` #### Parameters `appId`: The application ID to be set for PushEngage integration. #### Usage Dart ```dart await PushEngage.setAppId('your_app_id_here'); ``` ### setEnvironment Switch the SDK between the `staging` and `production` backends. This is intended for internal testing against the PushEngage staging environment — most apps should leave the default (production) untouched. Call before `setAppId` `setEnvironment` must be invoked before `setAppId`. The native Android SDK caches its base URLs when the app id is set, and switching the environment after initialization is unsupported. #### Syntax ```dart setEnvironment(Environment environment) ``` #### Parameters `environment`: `Environment.staging` or `Environment.production`. #### Usage Dart ```dart import 'package:pushengage_flutter_sdk/model/environment.dart'; await PushEngage.setEnvironment(Environment.production); await PushEngage.setAppId('YOUR_APP_ID'); ``` ## Notification Permission ### requestNotificationPermission Requests notification permission from the user. For Android 13 (API 33) and above, this will show the system permission dialog. For older versions, the permission is automatically granted. #### Syntax ```dart requestNotificationPermission() ``` #### Returns A `PushEngageResult` containing a boolean value indicating whether the permission was granted or not. The result includes success/error status and the permission state. #### Usage Dart ```dart PushEngageResult result = await PushEngage.requestNotificationPermission(); if (result.isSuccess) { bool isGranted = result.data ?? false; print('Permission granted: $isGranted'); } else { print('Error requesting permission: ${result.error}'); } ``` ### getNotificationPermissionStatus Get the current notification permission status for the application. #### Syntax ```dart getNotificationPermissionStatus() -> Future> ``` #### Returns A `PushEngageResult` containing a string indicating the current notification permission state: - `"granted"`: The application is authorized to post user notifications - `"denied"`: The application is not authorized to post user notifications #### Usage Dart ```dart PushEngageResult result = await PushEngage.getNotificationPermissionStatus(); if (result.isSuccess) { String status = result.data ?? ''; if (status == 'granted') { print('Notifications are enabled'); } else { print('Notifications are disabled'); } } else { print('Error getting permission status: ${result.error}'); } ``` ## Subscription ### subscribe Subscribe the user to push notifications. This method checks the current permission status and subscription state to determine the appropriate action. If notification permission is not granted, it will automatically request permission first. #### Syntax ```dart subscribe() ``` #### Returns A `PushEngageResult` containing: - `true`: Subscribe operation completed successfully - `false`: Subscribe operation failed #### Usage Dart ```dart PushEngageResult result = await PushEngage.subscribe(); if (result.isSuccess) { bool success = result.data ?? false; if (success) { print('User subscribed successfully'); } else { print('Subscribe operation failed'); } } else { print('Error subscribing user: ${result.error}'); } ``` ### unsubscribe Unsubscribe the user from push notifications. This stops the user from receiving notifications but keeps their profile and preferences in the system. The user can be re-subscribed later using the `subscribe()` method. #### Syntax ```dart unsubscribe() ``` #### Returns A `PushEngageResult` containing: - `true`: Unsubscribe operation completed successfully - `false`: Unsubscribe operation failed #### Usage Dart ```dart PushEngageResult result = await PushEngage.unsubscribe(); if (result.isSuccess) { bool success = result.data ?? false; if (success) { print('User unsubscribed successfully'); } else { print('Unsubscribe operation failed'); } } else { print('Error unsubscribing user: ${result.error}'); } ``` ### getSubscriptionStatus Check whether the user is currently subscribed to push notifications. #### Syntax ```dart getSubscriptionStatus() ``` #### Returns A `PushEngageResult` containing a boolean value indicating the subscription status: - `true`: User is subscribed to push notifications - `false`: User is not subscribed (unsubscribed or never subscribed) #### Usage Dart ```dart PushEngageResult result = await PushEngage.getSubscriptionStatus(); if (result.isSuccess) { bool isSubscribed = result.data ?? false; if (isSubscribed) { print('User is subscribed to push notifications'); } else { print('User is not subscribed to push notifications'); } } else { print('Error getting subscription status: ${result.error}'); } ``` ### getSubscriptionNotificationStatus Check whether the user can actually receive push notifications by verifying both subscription status and notification permission. The user can receive notifications only if they are subscribed AND the app has notification permission granted. #### Syntax ```dart getSubscriptionNotificationStatus() ``` #### Returns A `PushEngageResult` containing a boolean value indicating the complete notification capability: - `true`: User can receive notifications (subscribed AND permission granted) - `false`: User cannot receive notifications (not subscribed or permission denied) #### Usage Dart ```dart PushEngageResult result = await PushEngage.getSubscriptionNotificationStatus(); if (result.isSuccess) { bool canReceiveNotifications = result.data ?? false; if (canReceiveNotifications) { print('User can receive push notifications'); } else { print('User cannot receive push notifications'); } } else { print('Error getting notification capability: ${result.error}'); } ``` ## Associate Profile ID Profile IDs serve as unique identifiers for your subscribers, enabling you to recognize them across multiple devices. Each subscriber can be assigned just one profile ID. This ID should be a string, and you have the flexibility to use any value, such as an email or phone number. ### getSubscriberId Retrieve the unique subscriber ID for a user. PushEngage generates this ID for every user based on their subscription data. The subscriber ID remains consistent unless there's a change in the user's subscription. If the user is not subscribed, it will return null. #### Syntax ```dart getSubscriberId() ``` #### Returns A `PushEngageResult` containing: - `String`: The subscriber ID if the user is subscribed and has a valid hash - `null`: If the user is not subscribed or doesn't have a valid hash #### Usage Dart ```dart PushEngageResult result = await PushEngage.getSubscriberId(); if (result.isSuccess) { String? subscriberId = result.data; if (subscriberId != null) { print('Subscriber ID: $subscriberId'); } else { print('User is not subscribed or no valid ID available'); } } else { print('Error getting subscriber ID: ${result.error}'); } ``` ### addProfileId This method allows you to set a profile ID for the current subscriber. If a profile ID already exists, it will be replaced with the new value. #### Syntax Dart ```dart addProfileId(String profileId) ``` #### Parameters `profileId`: A string representing the unique profile ID to be assigned to the subscriber. - Used to identify the subscriber across multiple devices. - Can be any unique identifier like email, user ID, or phone number. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future addProfile() async { PushEngageResult result = await PushEngage.addProfileId('your_profile_id'); if (result.isSuccess) { print('Profile ID added successfully: ${result.data}'); } else { print('Failed to add profile ID: ${result.error}'); } } ``` ## User Identity The `identify` and `logout` methods manage the predefined personal-data fields stored on the current subscriber. Use them to tie a push subscription to your own first-party user data and to clear that data on sign-out without unsubscribing the device. The 12 valid keys are: `first_name`, `last_name`, `email`, `phone`, `gender`, `dob`, `language`, `profile_id`, `country`, `city`, `state`, `zip`. Values must be `String`, `num`, or `bool` — other types are rejected by the native layer. ### identify Upsert one or more of the 12 predefined subscriber fields. Repeat calls with the same payload short-circuit locally and resolve without a network round-trip, as long as the cache is fresh (within 24 hours of the last successful sync). #### Syntax ```dart identify(IdentifyFields fields) ``` #### Parameters `fields`: An `IdentifyFields` object. Only the properties you set are sent; null properties are omitted. At least one non-null property is required. | Property | Type | Description | | --- | --- | --- | | `firstName` | `Object?` | Subscriber's first name | | `lastName` | `Object?` | Subscriber's last name | | `email` | `Object?` | Email address | | `phone` | `Object?` | Phone number | | `gender` | `Object?` | Gender | | `dob` | `Object?` | Date of birth | | `language` | `Object?` | Locale/language code | | `profileId` | `Object?` | Your own user ID; numeric values are auto-coerced to string | | `country` | `Object?` | Country | | `city` | `Object?` | City | | `state` | `Object?` | State / region | | `zip` | `Object?` | Postal code | #### Returns A `PushEngageResult` containing the native success message, or the error on failure. #### Usage Dart ```dart import 'package:pushengage_flutter_sdk/model/identify_fields.dart'; final fields = IdentifyFields( firstName: 'Jane', lastName: 'Doe', email: 'jane@example.com', profileId: 'user_12345', ); final result = await PushEngage.identify(fields); if (result.isSuccess) { print('Subscriber fields updated'); } else { print('Identify failed: ${result.error}'); } ``` ### logout Remove a set of predefined personal fields from the current subscriber while keeping the device subscribed for push. Call this when a user signs out of your app to detach their PII from the push subscription. Passing `null` or an empty list removes the default PII field set: `first_name`, `last_name`, `email`, `phone`, `gender`, `dob`, `profile_id`. Passing a list of field names scopes the removal to those specific fields. If none of the requested field names are currently cached locally (and the cache is fresh — within 24 hours of the last successful sync), the call resolves without a network request. #### Syntax ```dart logout(List? fieldNames) ``` #### Parameters `fieldNames`: An optional list of field names to remove. Pass `null` or an empty list to clear the default PII set. #### Returns A `PushEngageResult` containing the native success message, or the error on failure. #### Usage Dart ```dart // Clear the default PII set await PushEngage.logout(null); // Clear specific fields await PushEngage.logout(['email', 'phone']); ``` ## Custom Events ### trackEvent Track a custom event for the current subscriber. Custom events are used to start or exit campaign workflows based on in-app activity — for example, adding an item to cart, completing a purchase, or any other action you define. Workflows are configured from the PushEngage Dashboard. #### Syntax ```dart trackEvent(TrackEventPayload event) ``` #### Parameters `event`: A `TrackEventPayload` describing the event. | Property | Type | Required | Description | | --- | --- | :-: | --- | | `eventName` | `String` | Yes | Name of the event (e.g., `"MySite.AddToCart"`). Must be non-empty. | | `data` | `Map?` | No | Custom key-value data. Values should be strings, numbers, or booleans; the Flutter layer does not validate them, so other types may be dropped or rejected by the backend. | | `profileId` | `String?` | No | Profile ID of the subscriber. | | `provider` | `String?` | No | Provider name. Defaults to `"PushEngage"` in the native SDK. | | `eventType` | `String?` | No | Event type. Defaults to `"PushEngage.CustomEvent"` in the native SDK. | #### Returns A `PushEngageResult` containing the native success message, or the error on failure. #### Usage Dart ```dart import 'package:pushengage_flutter_sdk/model/track_event_payload.dart'; final event = TrackEventPayload( eventName: 'MySite.AddToCart', data: { 'product_id': '123', 'product_name': 'Product Name', 'price': 49.99, }, ); final result = await PushEngage.trackEvent(event); if (result.isSuccess) { print('Event tracked'); } else { print('Failed to track event: ${result.error}'); } ``` ## Subscriber Details ### getSubscriberDetails This method retrieves subscriber details based on a provided list of strings, which can include city, state, country, device, device type, segments, etc. #### Syntax Dart ```dart getSubscriberDetails(List? values) ``` #### Parameters `values`: A list of strings specifying which subscriber fields to retrieve. See the Available Fields table below for all valid keys. Pass `null` to request all available fields (behavior is platform-dependent — see the note below). - Specify which subscriber details you want to retrieve. - Can include fields like city, state, country, device type, segments, etc. Fetching all fields is platform-dependent Pass `null` to request all fields. On **iOS**, this omits the query parameter and reliably returns the complete record. On **Android**, a `null` or empty list is sent as an empty `?fields=` query parameter (the plugin treats the two the same), so whether all fields are returned depends on the backend accepting it. Returns a failure when not subscribed `getSubscriberDetails` resolves with a failure if the user is not currently subscribed (consistent across iOS, Android, and the underlying native SDKs). #### Returns A `PushEngageResult?>` where `data` contains a map of the requested subscriber fields, or `null` if no data is available. #### Usage Dart ```dart Future fetchSubscriberDetails() async { final List keys = [ 'city', 'device', 'host', 'user_agent', 'has_unsubscribed', 'device_type', 'timezone', 'country', 'ts_created', 'state', ]; PushEngageResult?> result = await PushEngage.getSubscriberDetails(keys); if (result.isSuccess) { print('City: ${result.data?['city']}'); print('Country: ${result.data?['country']}'); } else { print('Failed to get subscriber details: ${result.error}'); } } ``` #### Available Fields | Field | Type | Description | | --- | --- | --- | | `city` | string | Subscriber's city | | `state` | string | Subscriber's state or region | | `country` | string | Subscriber's country | | `device` | string | Device name | | `device_type` | string | Device type (e.g., mobile, tablet) | | `user_agent` | string | App user agent string | | `host` | string | Host identifier | | `timezone` | string | Subscriber's timezone | | `has_unsubscribed` | boolean | Whether the subscriber has unsubscribed | | `ts_created` | string | ISO 8601 timestamp of subscription creation | | `segments` | array | Segment IDs the subscriber belongs to | ## Segments Segments are used to group subscribers so that you can send personalized notifications. Segments can be created based on attributes, categories, and more. ### addSegment This method enables you to add the current subscriber to segments. #### Syntax Dart ```dart addSegment(List segments) ``` #### Parameters `segments`: A list of segment IDs to be added. - Contains the IDs of segments you want to add the subscriber to. - Each segment ID should be a valid string identifier from your PushEngage dashboard. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future addSegments() async { PushEngageResult result = await PushEngage.addSegment(['segment1', 'segment2', 'segment3']); if (result.isSuccess) { print('Segments added: ${result.data}'); } else { print('Failed to add segments: ${result.error}'); } } ``` ### addDynamicSegment This method enables you to add the current subscriber to a segment for a specified duration, measured in days. After this period, the segment will be automatically removed from the subscriber. #### Syntax Dart ```dart addDynamicSegment(List segments) ``` #### Parameters `segments`: A list of segment objects representing dynamic segments to be added. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future addDynamicSegments() async { final segments = [ DynamicSegment(name: 'segment1', duration: 5), DynamicSegment(name: 'segment2', duration: 7), ]; PushEngageResult result = await PushEngage.addDynamicSegment(segments); if (result.isSuccess) { print('Dynamic segments added: ${result.data}'); } else { print('Failed to add dynamic segments: ${result.error}'); } } ``` ### removeSegment This method allows you to remove the current subscriber from segments. #### Syntax Dart ```dart removeSegment(List segments) ``` #### Parameters `segments`: A list of segment IDs to be removed. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future removeSegments() async { PushEngageResult result = await PushEngage.removeSegment(['segment1', 'segment2', 'segment3']); if (result.isSuccess) { print('Segments removed: ${result.data}'); } else { print('Failed to remove segments: ${result.error}'); } } ``` ## Attributes Attributes are key-value pairs that allow you to store additional information about your subscribers. You can utilize attributes to segment your subscribers and send personalized notifications. ### addSubscriberAttributes Use this method to add or update attributes for a subscriber. If an attribute with the specified key already exists, the existing value will be replaced. #### Syntax Dart ```dart addSubscriberAttributes(Map attributes) ``` #### Parameters `attributes`: A `Map` containing the subscriber attributes to be added. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future addAttributes() async { PushEngageResult result = await PushEngage.addSubscriberAttributes({ 'age': 25, 'height': '6.1', }); if (result.isSuccess) { print('Attributes added: ${result.data}'); } else { print('Failed to add attributes: ${result.error}'); } } ``` ### setSubscriberAttributes This method allows you to set attributes for a subscriber, replacing any previously associated attributes. Use this method when you need to entirely reset the attributes with new values. #### Syntax Dart ```dart setSubscriberAttributes(Map attributes) ``` #### Parameters `attributes`: A `Map` containing the updated subscriber attributes. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future setAttributes() async { PushEngageResult result = await PushEngage.setSubscriberAttributes({ 'age': 25, 'height': '6.1', }); if (result.isSuccess) { print('Attributes set: ${result.data}'); } else { print('Failed to set attributes: ${result.error}'); } } ``` ### getSubscriberAttributes Retrieve the attributes associated with the current subscriber using this method. #### Syntax Dart ```dart getSubscriberAttributes() ``` #### Returns A `PushEngageResult>` where `data` contains all custom attributes set on the subscriber. #### Usage Dart ```dart Future fetchAttributes() async { PushEngageResult> result = await PushEngage.getSubscriberAttributes(); if (result.isSuccess) { print('Subscriber attributes: ${result.data}'); } else { print('Failed to get attributes: ${result.error}'); } } ``` ### deleteSubscriberAttributes This method allows you to remove one or more attributes from the current subscriber. Provide an array of attribute names you wish to remove. Passing an empty array will result in the removal of all the subscriber's attributes. #### Syntax Dart ```dart deleteSubscriberAttributes(List attributes) ``` #### Parameters `attributes`: A `List` containing attribute names to be deleted. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future deleteAttributes() async { PushEngageResult result = await PushEngage.deleteSubscriberAttributes(['age', 'height']); if (result.isSuccess) { print('Attributes deleted: ${result.data}'); } else { print('Failed to delete attributes: ${result.error}'); } } ``` ## Automated Notifications Automated notifications include all types of triggered campaigns, such as cart abandonment, price drop, back in stock, and browse abandonment. By default, automated notifications are enabled for all subscribers. ### automatedNotification This method allows you to enable/disable automated notifications for the current subscriber. #### Syntax Dart ```dart automatedNotification(TriggerStatusType status) ``` #### Parameters `status`: The trigger status type indicating the status of the trigger campaign. - Use `TriggerStatusType.enabled` to enable automated notifications. - Use `TriggerStatusType.disabled` to disable automated notifications. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Enable Automated Notifications Dart ```dart Future enableAutomatedNotifications() async { PushEngageResult result = await PushEngage.automatedNotification(TriggerStatusType.enabled); if (result.isSuccess) { print('Automated notifications enabled: ${result.data}'); } else { print('Failed to enable: ${result.error}'); } } ``` Disable Automated Notifications Dart ```dart Future disableAutomatedNotifications() async { PushEngageResult result = await PushEngage.automatedNotification(TriggerStatusType.disabled); if (result.isSuccess) { print('Automated notifications disabled: ${result.data}'); } else { print('Failed to disable: ${result.error}'); } } ``` ## Triggered Campaigns ### sendTriggerEvent Detect your visitor's behavior to send automated push notifications to the right person at the right time. #### Syntax Dart ```dart sendTriggerEvent(TriggerCampaign trigger) ``` #### Parameters `trigger`: The TriggerCampaign object representing the campaign event to be triggered. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future sendCampaignEvent() async { final trigger = TriggerCampaign( campaignName: 'Welcome Campaign', eventName: 'start', referenceId: '12345', // optional profileId: 'user_67890', // optional data: {'key1': 'value1'}, // optional ); PushEngageResult result = await PushEngage.sendTriggerEvent(trigger); if (result.isSuccess) { print('Trigger event sent: ${result.data}'); } else { print('Failed to send trigger: ${result.error}'); } } ``` ### addAlert Re-engage your customers and increase conversion using Price Drop Alert Campaigns and Inventory Alert Campaigns. #### Syntax Dart ```dart addAlert(TriggerAlert alert) ``` #### Parameters `alert`: The TriggerAlert object representing the alert to be added. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Price Drop Dart ```dart Future addPriceDropAlert() async { final alert = TriggerAlert( type: TriggerAlertType.priceDrop, productId: 'product_id', link: 'https://example.com/product/product_id', price: 100.0, alertPrice: 89.99, // optional: target price; omitted when null (server may apply a default) availability: TriggerAlertAvailabilityType.inStock, // optional; omitted when null (server may apply a default) ); PushEngageResult result = await PushEngage.addAlert(alert); if (result.isSuccess) { print('Price drop alert added: ${result.data}'); } else { print('Failed to add alert: ${result.error}'); } } ``` Back in Stock Alert Dart ```dart Future addInventoryAlert() async { final alert = TriggerAlert( type: TriggerAlertType.inventory, productId: 'product_id', link: 'https://example.com/product/product_id', price: 100.0, availability: TriggerAlertAvailabilityType.outOfStock, // optional; omitted when null (server may apply a default) ); PushEngageResult result = await PushEngage.addAlert(alert); if (result.isSuccess) { print('Inventory alert added: ${result.data}'); } else { print('Failed to add alert: ${result.error}'); } } ``` ## Goal Tracking ### sendGoal Goal Tracking will help you assign conversion goals and value to your notification campaigns. You can set up a default goal and have it integrated for all your campaigns. #### Syntax Dart ```dart sendGoal(Goal goal) ``` #### Parameters `goal`: Goal object representing the goal to be tracked. #### Returns A `PushEngageResult` — on success, `data` holds the native success message; on failure, `data` is `null` and `error` holds the exception (`isSuccess == false`). #### Usage Dart ```dart Future trackGoal() async { final goal = Goal(name: 'purchase', count: 1, value: 10.0); PushEngageResult result = await PushEngage.sendGoal(goal); if (result.isSuccess) { print('Goal tracked: ${result.data}'); } else { print('Failed to track goal: ${result.error}'); } } ``` ## Deep Linking ### deepLinkStream Handle deep links in your Flutter app by listening to the `deepLinkStream` stream. The stream emits when a notification is tapped while the app is running. Cold-boot taps (notification taps that launch the app from a terminated state on iOS) are not delivered here — use [`getInitialNotification`](#getinitialnotification) for those. #### Syntax ```dart get deepLinkStream ``` #### Returns The stream emits a `Map` containing the deep link data: ```dart { 'deepLink': String, 'data': Map, } ``` Breaking change in 1.0.0 `data` is now always delivered as a `Map` on both platforms. On Android the SDK previously delivered `data` as a JSON-encoded string — if you were decoding it manually, remove that step. #### Usage Dart ```dart PushEngage.deepLinkStream.listen((data) { if (data != null) { print('Received deep link data: $data'); } else { print('No deep link data received.'); } }); ``` ### getInitialNotification Recover the notification that cold-launched the app from a terminated state. On iOS, `deepLinkStream` only emits for taps that happen after Dart can subscribe, so the first cold-boot tap is missed. `getInitialNotification` is the pull-based companion — call it once on startup (in addition to listening on `deepLinkStream`) and the SDK delivers the buffered cold-boot tap. iOS only This method resolves with `null` on Android. Cold-boot deep links on Android flow through the host activity's intent filter and are not buffered by the SDK. #### Syntax ```dart getInitialNotification() ``` #### Returns A `PushEngageResult?>` containing: - The cold-boot deep link payload (`{'deepLink': String, 'data': Map}`) on iOS, when one was buffered. - `null` — when there was no cold-boot tap to recover (warm-boot launches, the app was already running, repeated calls after the buffered value was consumed, or Android). The buffered value is consumed by the first successful call; subsequent calls resolve `null`. #### Usage Dart ```dart @override void initState() { super.initState(); _readInitialNotification(); } Future _readInitialNotification() async { final result = await PushEngage.getInitialNotification(); if (result.isSuccess && result.data != null) { print('Cold-boot deep link: ${result.data!['deepLink']}'); // Navigate based on result.data['deepLink'] / result.data['data'] } } ``` ## Utilities ### enableLogging Enables or disables verbose debug logging for the SDK. When enabled, the SDK prints diagnostic output to the console. danger Set `enableLogging` to `false` before releasing to production to avoid leaking internal SDK state to device logs. #### Syntax Dart ```dart enableLogging(bool shouldEnable) ``` #### Parameters `shouldEnable`: Pass `true` to turn on debug logging, `false` to turn it off. #### Usage Dart ```dart // Enable during development PushEngage.enableLogging(true); // Disable for production PushEngage.enableLogging(false); ``` ### getSdkVersion Retrieves the current version of the PushEngage Flutter Plugin, returning a string that represents the Plugin's current version. #### Syntax Dart ```dart getSdkVersion() ``` #### Returns A String representing the current version of the PushEngage Flutter Plugin. #### Usage Dart ```dart PushEngage.getSdkVersion(); ``` ### setSmallIconResource Sets the resource name of the small icon used for notifications. The small icon appears in the status bar when a notification is displayed. #### Syntax Dart ```dart setSmallIconResource(String resourceName) ``` #### Parameters `resourceName`: A string representing the resource name of the small icon. - Must be a valid Android drawable resource name. - Used to display a custom small icon in push notifications. #### Usage Dart ```dart void main() async { String resourceName = "your_small_icon_name"; await PushEngage.setSmallIconResource(resourceName); } ``` note It is recommended to set a valid resource name to ensure proper display of notifications. If an invalid resource name is provided, the default bell icon specified by the PushEngage library will be used. Android only This method only applies on Android. On iOS, the call is a no-op — iOS notification icons are configured via the app's asset catalog and Notification Service Extension. ### setBadgeCount Set the app icon badge count. - **iOS**: Sets the launcher icon badge directly. Uses `UNUserNotificationCenter.setBadgeCount` on iOS 16+, with `applicationIconBadgeNumber` fallback on older versions. - **Android**: Stores the value and applies it to notifications the SDK builds afterwards. Android has no system API for a numeric launcher-icon badge — the count surfaces in the long-press shortcut menu. Passing `0` additionally clears **all** active notifications (via `NotificationManagerCompat.cancelAll()`), dismissing the app's entire notification tray. Values outside the 32-bit signed integer range are coerced to `0` (badge cleared). Negative values within the Int32 range pass through to native, where Android treats them as `0` (cleared). #### Syntax ```dart setBadgeCount(int count) ``` #### Parameters `count`: The badge count to set. Pass `0` to clear the badge. #### Usage Dart ```dart // Set the badge await PushEngage.setBadgeCount(5); // Clear the badge await PushEngage.setBadgeCount(0); ``` ## Diagnostics (Android) The methods in this section are Android-only — they help diagnose Firebase Cloud Messaging configuration drift between the device's `google-services.json` and the configuration registered on the PushEngage dashboard. iOS calls resolve as no-ops so cross-platform code can call them unconditionally. ### runConfigValidation Re-run the SDK's FCM configuration validator. Mismatch details are delivered asynchronously through `onFcmConfigError`, not via the return value. Always resolves `true` on iOS (no FCM surface). #### Syntax ```dart runConfigValidation(String senderId, String projectId) ``` #### Parameters `senderId`: The Firebase sender ID expected by your PushEngage site. `projectId`: The Firebase project ID expected by your PushEngage site. #### Returns A `PushEngageResult`: - `true` — the configuration matches, the check was skipped (Firebase not yet initialized), or the call was a no-op on iOS. - `false` — a mismatch was detected (sender ID, project ID, or both). The specific mismatch details are also delivered asynchronously through `onFcmConfigError`. If the validator itself throws, the call resolves as a failure result (`CONFIG_VALIDATION_ERROR`) — it never returns `false` to signal "could not run". #### Usage Dart ```dart final result = await PushEngage.runConfigValidation('1234567890', 'my-firebase-project'); if (result.isSuccess) { print('Config validation dispatched: ${result.data}'); } ``` ### onFcmConfigError A stream of Firebase Cloud Messaging configuration errors emitted by the Android native SDK. Common causes are sender-ID or project-ID mismatches between `google-services.json` and the PushEngage dashboard. The Android error codes are: | Code | Meaning | | --- | --- | | `5001` | `FCM_SENDER_ID_MISMATCH` — `google-services.json` sender ID does not match the dashboard. | | `5002` | `FCM_PROJECT_ID_MISMATCH` — `google-services.json` project ID does not match the service-account JSON. | | `5003` | `FCM_LOCAL_CONFIG_INVALID` — Firebase Installations rejected `google-services.json` as invalid for the app. | | `5004` | `FCM_CONFIG_BOTH_MISMATCH` — both sender ID and project ID differ from the dashboard configuration. | iOS no-op The native iOS bridge does not emit FCM config errors. Listening on iOS is harmless and simply never fires. #### Syntax ```dart get onFcmConfigError ``` #### Returns A `Stream`. The `FcmConfigError` model has two fields: ```dart class FcmConfigError { final int code; final String message; } ``` #### Usage Dart ```dart import 'package:pushengage_flutter_sdk/model/fcm_config_error.dart'; late StreamSubscription _fcmErrorSub; @override void initState() { super.initState(); _fcmErrorSub = PushEngage.onFcmConfigError.listen((error) { print('FCM config error ${error.code}: ${error.message}'); }); } @override void dispose() { _fcmErrorSub.cancel(); super.dispose(); } ``` --- Source: https://www.pushengage.com/api/mobile-sdk/ios/appstore-compliance # iOS App Store Compliance Checklist A step-by-step guide to ensure your iOS app with PushEngage push notifications passes Apple's App Store review. Also covers migration from OneSignal and Firebase Cloud Messaging. **Applies to:** PushEngage iOS SDK v0.0.6+ | iOS 10.0+ | Integration Method | Minimum Swift | Minimum Xcode | | --- | :-: | :-: | | CocoaPods | 5.0 | 11+ | | Swift Package Manager | 5.9 | 15+ | ## How to Use This Checklist Work through each section in order as you integrate PushEngage and prepare for App Store submission. Each item includes a **Verify** step so you can confirm completion. **Legend:** - **Required** — your app will be rejected without this - **Recommended** — not strictly required but significantly reduces rejection risk - **Migration** — only needed if switching from another push service * * * ## Phase 1: Pre-Integration Setup ### APNs Certificate (.p12) — Required Apple Push Notification service requires authentication to send push notifications to your app. The PushEngage dashboard accepts **APNs .p12 certificates**. Certificate expiry `.p12` certificates expire annually. Set a calendar reminder 2–4 weeks before expiry to renew — push delivery will silently stop once a certificate expires. Use the **"Sandbox & Production"** certificate type to cover both development and App Store builds with a single certificate. - **Create an APNs certificate in the Apple Developer Portal** - Go to [Certificates, Identifiers & Profiles → Certificates](https://developer.apple.com/account/resources/certificates/list) - Click **"+"** and select **Apple Push Notification service SSL (Sandbox & Production)** - Complete the CSR flow via Keychain Access: 1. Open Keychain Access → Certificate Assistant → Request a Certificate From a Certificate Authority 2. Enter your email, leave CA Email Address empty, select **Saved to disk** 3. Upload the generated `.certSigningRequest` file to the Apple Developer Portal - Download the generated `.cer` file, double-click to install in Keychain Access - Open Keychain Access → My Certificates → find "Apple Push Services: com.yourapp.bundleid" - Right-click → Export → `.p12` format, set an export password - **Verify:** You have a `.p12` file and know the export password - **Upload the .p12 certificate to the PushEngage Dashboard** - Go to **Site Settings → Installation → iOS SDK** - Fill in the following fields: - **App Push Id** — your app's bundle identifier (e.g., `com.yourcompany.yourapp`) - **Certificate (.p12)** — upload the `.p12` file exported from Keychain Access - **Private Key Password** — the export password set during the `.p12` export - Click **Update** - **Verify:** PushEngage Dashboard shows the certificate is configured for iOS push ### App ID Configuration — Required - **Enable Push Notifications for your App ID** - Go to [Certificates, Identifiers & Profiles → Identifiers](https://developer.apple.com/account/resources/identifiers/list) - Select your App ID → under **Capabilities**, enable **Push Notifications** - **Verify:** Push Notifications shows a green checkmark next to your App ID - **Create or update your Provisioning Profile** - After enabling Push Notifications on your App ID, regenerate your provisioning profile - Download and install the new profile in Xcode - **Verify:** Xcode → Signing & Capabilities shows no provisioning profile errors * * * ## Phase 2: Xcode Project Configuration ### Capabilities & Entitlements — Required - **Add Push Notifications capability** - Xcode → your target → Signing & Capabilities → **\+ Capability** → Push Notifications - This adds the `aps-environment` entitlement to your app - **Verify:** Your `.entitlements` file contains: ```xml aps-environment development ``` _(Xcode sets this to `production` automatically for App Store/TestFlight builds.)_ - **Add Background Modes capability with Remote notifications** - Xcode → your target → Signing & Capabilities → **\+ Capability** → Background Modes - Check **Remote notifications** and **Background fetch** - **Verify:** Your `Info.plist` contains: ```xml UIBackgroundModes fetch remote-notification ``` - **Add App Groups capability** — Required for SDK to function - The PushEngage SDK uses an App Group-backed `UserDefaults` suite for all internal state (device token, subscriber data, permission status). Without this, the SDK silently loses all stored state between app launches. - Xcode → your target → Signing & Capabilities → **\+ Capability** → App Groups - Create a group identifier, e.g., `group.com.yourcompany.yourapp` - Add the **same** App Group to your main app target, Notification Service Extension, and any other extensions - Add `PushEngage_App_Group_Key` to **each target's** `Info.plist` with the same group identifier - **Verify:** All targets list the same App Group in their `.entitlements` files: ```xml com.apple.security.application-groups group.com.yourcompany.yourapp ``` - **Verify:** Each `Info.plist` contains: ```xml PushEngage_App_Group_Key group.com.yourcompany.yourapp ``` ### Notification Service Extension — Required for Rich Notifications & Delivery Tracking Required for full functionality The Notification Service Extension is **required** for rich notifications (images, action buttons), accurate delivery tracking, and badge management. Without it, the SDK can only deliver basic text notifications. - **Create a Notification Service Extension target** - Xcode → File → New → Target → **Notification Service Extension** - Name it (e.g., `PushEngageNotificationServiceExtension`) - Set the same deployment target as your main app (iOS 10.0+) - Add the PushEngage SDK as a dependency to this target - **Verify:** Your project has a separate Notification Service Extension target - **Add App Groups to the extension** - Use the same App Group identifier as your main app - Add `PushEngage_App_Group_Key` to the extension's `Info.plist` - **Verify:** Extension's `.entitlements` file contains the matching App Group ### Notification Content Extension — Optional The Notification Content Extension allows custom notification UI. Only add this if you need custom notification layouts. - **Create a Notification Content Extension target** (if needed) - Xcode → File → New → Target → **Notification Content Extension** - Configure `UNNotificationExtensionCategory` in the extension's `Info.plist` - **Verify:** Extension responds to the correct notification category - **Add App Groups to the Content Extension** - Use the same App Group identifier as your main app and Service Extension - Add `PushEngage_App_Group_Key` to the Content Extension's `Info.plist` - **Verify:** Content Extension's `.entitlements` file contains the matching App Group * * * ## Phase 3: Privacy & Data Compliance ### Privacy Manifest (PrivacyInfo.xcprivacy) — Recommended Apple maintains a [specific list of third-party SDKs](https://developer.apple.com/support/third-party-SDK-requirements/) required to include privacy manifests and code signatures. PushEngage is not currently on this list, so Apple will not reject your app solely because the SDK lacks a bundled privacy manifest. However, **your app** must still declare its own data collection and Required Reason API usage — which includes APIs called by PushEngage. - **Create your app's privacy manifest** (if not already present) - Xcode → File → New → File → App Privacy → **Privacy Manifest** - Declare data collection that PushEngage performs (see table below) - Declare Required Reason APIs used by PushEngage (see section below) - **Verify:** `PrivacyInfo.xcprivacy` exists in your app bundle - **Check for ITMS-91053 warnings during upload** - These are informational, not blocking, since PushEngage is not on Apple's required list - Address them by adding the appropriate declarations to your app's privacy manifest #### Data Collected by PushEngage iOS SDK | Data Type | Purpose | Linked to User | Tracking | | --- | --- | :-: | :-: | | Device ID (APNs token) | Push notification delivery | No | No | | Device model | Analytics & delivery optimization | No | No | | Device manufacturer | Analytics (hardcoded `"Apple"`) | No | No | | Device type (phone/tablet) | Analytics & delivery optimization | No | No | | User agent string | Analytics (derived from device model, e.g. `"Apple iPhone14,2"`) | No | No | | OS version | Compatibility & analytics | No | No | | Timezone | Scheduled notification delivery | No | No | | Language / locale | Notification localization | No | No | | Screen dimensions | Notification display optimization | No | No | | App bundle identifier | App identification | No | No | | Notification permission state | Subscription management | No | No | | APNs environment (dev/prod) | Delivery routing | No | No | IP address and location **IP address:** The SDK does not collect or send IP addresses from the device. PushEngage's backend servers receive the device's IP address as part of standard HTTP communication. Disclose this in your App Store privacy nutrition labels if your privacy policy covers server-side data. **Location:** The SDK does not request GPS/location data or use CoreLocation. It only checks whether your app has declared location usage description keys in `Info.plist` and reports this as a boolean flag. **IDFA:** The SDK does **not** collect IDFA, does **not** require App Tracking Transparency, and does **not** track users across apps. #### Required Reason APIs The PushEngage SDK uses the following API from Apple's "Required Reason" list: - [ ] **Declare `NSPrivacyAccessedAPITypes` in your app's privacy manifest** - `UserDefaults` (NSUserDefaults) — Reason: `CA92.1` (App Functionality: app-specific data storage) - No other Required Reason APIs are used by the SDK - **Verify:** No ITMS-91053 warnings when uploading to App Store Connect ### App Privacy "Nutrition Labels" — Required When you submit your app to App Store Connect, you must disclose what data your app collects — including data collected by third-party SDKs. - [ ] **Complete the App Privacy section in App Store Connect** - App Store Connect → Your App → **App Privacy** - Declare the data types listed in the table above - For each data type, specify: collected, purpose, linked to user identity, used for tracking - **Verify:** App Store Connect shows your privacy disclosures with no warnings #### Recommended Disclosures for PushEngage | App Store Connect Category | Disclose? | Details | | --- | :-: | --- | | Do you or your third-party partners collect data from this app? | Yes | | | Identifiers → Device ID | Yes | App Functionality (push delivery). Not linked to user identity unless your app links it via `addProfileId`. Not used for tracking. | | Usage Data → Product Interaction | Yes | SDK tracks notification views and clicks for delivery analytics. Not linked to user identity. Not used for tracking. | | Diagnostics (crash data, performance) | No | SDK does not collect crash reports or performance diagnostics. | ### App Tracking Transparency (ATT) — Not Required for PushEngage - [ ] **Confirm ATT is NOT required for PushEngage** - PushEngage does not use IDFA or track users across apps - You do **not** need to add `NSUserTrackingUsageDescription` to your `Info.plist` for PushEngage - **Verify:** Search your PushEngage integration code for `ATTrackingManager` — it should not be present - **Note:** If _other_ SDKs in your app use IDFA, you still need ATT for those * * * ## Phase 4: App Store Review Guidelines Compliance Apple's review guidelines have specific rules about push notifications. Violating these is one of the most common causes of rejection. ### Content & Behavior Rules — Required - **Do NOT require push notifications to use the app** (Guidelines 4.5.4, 5.1.2) - Apps cannot require users to enable push notifications to access features or receive compensation - Do not gate content, features, or rewards behind push notification opt-in - **Verify:** Test your app with notifications denied — all features should work - **Keep notification content relevant to the app** (Guideline 2.5.16) - Notifications must relate to your app's core content and functionality - Do not send notifications unrelated to what the user signed up for - **Verify:** Review your notification campaigns in PushEngage Dashboard — all should relate to app content - **No advertising in push notifications without explicit opt-in** (Guideline 4.5.4) - Push notifications must not be used for promotions or direct marketing unless users have explicitly opted in via consent language in your app - You must provide a method to opt out of such messages - Promotional content for your own app features is generally acceptable if the user opted in - **Verify:** Review notification templates — none should contain unsolicited promotional content - **No spam or unsolicited notifications** (Guideline 4.5.4) - Send notifications at reasonable frequency; content should provide value - Do not use push as a re-engagement mechanism for inactive users without consent - **Verify:** Check notification frequency in PushEngage analytics — avoid more than 2–3 per day unless user-triggered - **Provide notification preferences** — Recommended - Include an in-app setting for users to manage notification types or frequency - Reviewers look favorably on this even though it's not strictly required - **Verify:** Your app has a Settings screen with notification preferences ### Permission Request Best Practices — Recommended - **Request notification permission at an appropriate time** - Do NOT request permission immediately on first launch - Show a pre-permission screen explaining the value of notifications before the system prompt - Request permission after the user has experienced your app's core value - **Verify:** Test a fresh install — the system permission prompt should not appear on the first screen - **Handle permission denial gracefully** - If the user denies, do not repeatedly prompt - Provide a path to enable notifications later via Settings - **Verify:** Deny permissions and use the app — no repeated prompts or degraded experience * * * ## Phase 5: Migration Compliance Skip if new integration Skip this section if this is a fresh integration without a previous push notification service. ### Migrating from OneSignal — Migration Privacy manifest impact OneSignal is on Apple's required third-party SDK list. Removing it removes its privacy manifest and code signature requirements from your app. - **Remove OneSignal SDK completely** - Remove from Podfile / Package.swift - Delete all OneSignal import statements and API calls - Remove OneSignal's Notification Service Extension (if separate from yours) - **Verify:** ```bash grep -r "OneSignal" --include="*.swift" --include="*.m" --include="*.h" . # Should return no results ``` - **Remove OneSignal's privacy manifest entries** - Remove any OneSignal-specific entries from your privacy manifest - Add PushEngage's data collection declarations (see Phase 3) - **Verify:** Your `PrivacyInfo.xcprivacy` no longer references OneSignal data types - **Update App Privacy nutrition labels** - OneSignal and PushEngage collect similar but not identical data - Review your App Store Connect privacy disclosures and update accordingly - **Verify:** App Store Connect privacy section reflects PushEngage's data collection - **Upload your APNs credential to PushEngage** - Your existing APNs `.p12` certificate works with PushEngage — no change needed to the certificate itself - Go to **Site Settings → Installation → iOS SDK** and upload the same `.p12` certificate you used with OneSignal - **Verify:** Test push notification delivered successfully via PushEngage - **Map subscriber data** - OneSignal Player IDs do not transfer to PushEngage - Device tokens are re-registered automatically when the PushEngage SDK initializes - If you stored OneSignal Player IDs in your backend, plan for the subscriber migration - **Verify:** New subscribers appear in PushEngage Dashboard after SDK switch ### Migrating from Firebase Cloud Messaging (FCM) — Migration Other Firebase SDKs FirebaseMessaging is on Apple's required third-party SDK list. If you remove it but keep other Firebase SDKs (e.g., FirebaseCore, FirebaseCrashlytics), those still require their own privacy manifests. - **Keep Firebase dependencies if using other Firebase services** - PushEngage uses APNs directly, not FCM - If you use Firebase for Analytics, Crashlytics, etc., keep those dependencies - Remove only FCM-specific notification handling code - **Verify:** Other Firebase services still work after removing FCM notification code - **Remove FCM notification handling code** - Remove `FIRMessaging` delegate methods and `FIRMessagingDelegate` conformance - Remove the `messaging:didReceiveRegistrationToken:` handler - Keep Firebase initialization if using other Firebase services - **Verify:** ```bash grep -r "FIRMessaging\|FirebaseMessaging" --include="*.swift" --include="*.m" . # Should return no notification-specific results ``` - **Update privacy manifest for FCM removal** - If FCM had entries in your privacy manifest, remove them - Firebase Analytics (if kept) has its own privacy manifest — ensure it's still included - **Verify:** Build succeeds with no ITMS-91053 warnings - **Upload your APNs credential to PushEngage** - FCM wraps APNs internally; PushEngage connects to APNs directly - Go to **Site Settings → Installation → iOS SDK** and upload a `.p12` certificate (see Phase 1 if you need to create one) - **Verify:** Test push delivered via PushEngage (not through FCM relay) - **Handle dual-SDK transition period** (if migrating gradually) - If running both services temporarily, ensure only one service handles each notification - Use notification categories or payload keys to distinguish - **Verify:** No duplicate notifications received during transition * * * ## Phase 6: Pre-Submission Final Checklist Run through this section before every App Store submission. ### Entitlements & Capabilities - [ ] Push Notifications capability enabled in Xcode - [ ] Background Modes → Remote notifications enabled - [ ] App Groups configured with `PushEngage_App_Group_Key` in `Info.plist` for all targets - [ ] `aps-environment` set to `production` for App Store builds (Xcode sets this automatically) - [ ] Provisioning profile is current and includes Push Notifications **Verify the `aps-environment` entitlement in your built app:** ```bash codesign -d --entitlements :- /path/to/YourApp.app 2>&1 | grep aps-environment # Should show: production ``` ### Privacy - [ ] `PrivacyInfo.xcprivacy` exists in your app bundle with PushEngage data declarations - [ ] `UserDefaults` declared as Required Reason API with reason `CA92.1` - [ ] App Privacy nutrition labels completed in App Store Connect - [ ] No ITMS-91053 or ITMS-91061 warnings in Xcode or Transporter ### Notifications - [ ] Test push notification received on a **physical device** (Simulator cannot receive push notifications) - [ ] Rich notifications display correctly (images, action buttons) — requires Notification Service Extension - [ ] Deep links from notifications navigate to the correct screens - [ ] Notification permission denial does not break any app functionality - [ ] No notification-gated content or forced opt-ins - [ ] Notification content is relevant to app functionality ### Code Cleanup - [ ] No previous SDK references (OneSignal / FCM) remain in code - [ ] No debug APNs configuration in production build - [ ] PushEngage logging disabled for production: `PushEngage.enableLogging = false` - [ ] No hardcoded test device tokens in code ### Build & Archive - [ ] Archive build succeeds with no signing errors - [ ] Transporter / Xcode upload shows no new warnings - [ ] TestFlight build receives push notifications correctly * * * ## Common App Store Rejection Reasons | Rejection Reason | Guideline | How to Avoid | | --- | --- | --- | | App requires push notifications to function | 4.5.4, 5.1.2 | Ensure all features work without notifications enabled | | Notifications used for unsolicited marketing | 4.5.4 | Only send promotions if user explicitly opted in; provide opt-out | | Notifications unrelated to app content | 2.5.16 | Keep notification content related to your app's functionality | | Excessive or irrelevant notifications | 4.5.4 | Limit frequency; keep content relevant to app | | Missing privacy disclosures for push data | 5.1.1 | Complete App Privacy section and privacy manifest | | Invalid provisioning profile | N/A | Regenerate profile after enabling Push Notifications capability | | Missing push entitlement | N/A | Add Push Notifications capability in Xcode | | Privacy manifest warnings | N/A | Include `PrivacyInfo.xcprivacy` with required declarations | * * * ## iOS Version Considerations | Requirement | iOS 10–11 | iOS 12+ | iOS 17+ | iOS 18+ | | --- | :-: | :-: | :-: | :-: | | Push Notifications capability | Required | Required | Required | Required | | Background Modes | Required | Required | Required | Required | | App-level Privacy Manifest | Required for all App Store submissions since May 2024 | | | | | Provisional Notifications | N/A | Available | Available | Available | | Notification Categories | Available | Available | Available | Available | | Priority Notifications | N/A | N/A | N/A | Available | Provisional notifications iOS 12 introduced provisional (quiet) notifications that require no user permission prompt. PushEngage SDK v0.0.6 does not request provisional authorization — it requests `.alert`, `.badge`, and `.sound` only. If you need provisional delivery, you would need to request it separately via `UNUserNotificationCenter` before calling PushEngage initialization. Privacy Manifest The privacy manifest is an App Store Connect submission requirement, not an iOS runtime feature. You need it in your app bundle regardless of which iOS version you target. * * * ## Useful Commands & Verification Tools **Check entitlements in your built app:** ```bash codesign -d --entitlements :- /path/to/YourApp.app ``` **Search for old SDK references:** ```bash # Search for OneSignal grep -r "OneSignal" --include="*.swift" --include="*.m" --include="*.h" \ --include="*.plist" --include="*.pbxproj" . # Search for Firebase Messaging grep -r "FIRMessaging\|FirebaseMessaging" --include="*.swift" --include="*.m" \ --include="*.plist" --include="*.pbxproj" . ``` **Verify privacy manifest in built app:** ```bash find /path/to/YourApp.app -name "PrivacyInfo.xcprivacy" ``` **Check .p12 certificate expiry date:** ```bash # View certificate details including expiry date openssl pkcs12 -in YourCert.p12 -nokeys | openssl x509 -noout -dates # Look for "notAfter" — that is your expiry date ``` **Test APNs connectivity with your .p12 certificate:** ```bash # Step 1: Convert .p12 to .pem (enter your export password when prompted) openssl pkcs12 -in YourCert.p12 -out apns-cert.pem -nodes # If you see an "unsupported" or "Mac verify error" with OpenSSL 3 (e.g. Homebrew), # retry with the legacy provider: # openssl pkcs12 -legacy -in YourCert.p12 -out apns-cert.pem -nodes # Step 2: Get your device token from Xcode console when your app registers # Step 3: Send a test push curl -v --cert apns-cert.pem \ --header "apns-topic: com.yourapp.bundleid" \ --header "apns-push-type: alert" \ --data '{"aps":{"alert":"Test from curl","sound":"default"}}' \ --http2 https://api.push.apple.com/3/device/YOUR_DEVICE_TOKEN # For sandbox/development testing use: # https://api.sandbox.push.apple.com/3/device/YOUR_DEVICE_TOKEN ``` * * * ## Next Steps - [iOS Quickstart →](/api/mobile-sdk/ios/quickstart) - [iOS SDK Reference →](/api/mobile-sdk/ios/sdk-reference) - [Mobile SDK Overview →](/api/mobile-sdk) - [Apple App Store Review Guidelines →](https://developer.apple.com/app-store/review/guidelines/) - [Apple Privacy Manifest Documentation →](https://developer.apple.com/documentation/bundleresources/privacy-manifest-files) - [Apple Third-Party SDK Requirements →](https://developer.apple.com/support/third-party-SDK-requirements/) --- Source: https://www.pushengage.com/api/mobile-sdk/ios/quickstart # iOS Quickstart This guide walks you through integrating PushEngage push notifications into a native iOS application. Estimated time: **15 minutes**. After completing this guide, your app will receive push notifications with rich media support. See the [iOS SDK Reference](/api/mobile-sdk/ios/sdk-reference) for the full API. Migration Guide: 0.1.0 → 1.0.0 The SDK is now split into two modules so notification extension targets no longer link app-only code (this fixes a CocoaPods regression that broke push delivery on production builds). - **`PushEngage`** — link to your app target only. App-target code is unchanged. - **`PushEngageExtension`** — link to your Notification Service / Content Extension target(s). For existing 0.1.0 integrations, three things change in each extension target: 1. **Dependency** - CocoaPods: `pod 'PushEngage'` → `pod 'PushEngageExtension'` - SPM: link the `PushEngageExtension` product instead of `PushEngage` 2. **Import**: `import PushEngage` → `import PushEngageExtension` (Objective-C: `@import PushEngage;` → `@import PushEngageExtension;`) 3. **Call sites**: replace the `PushEngage` class prefix with `PushEngageExtension` — method names are unchanged. ```swift // Before PushEngage.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) PushEngage.serviceExtensionTimeWillExpire(request, content: bestAttemptContent) PushEngage.getCustomUIPayLoad(for: notification.request) // After PushEngageExtension.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) PushEngageExtension.serviceExtensionTimeWillExpire(request, content: bestAttemptContent) PushEngageExtension.getCustomUIPayLoad(for: notification.request) ``` You can also remove the `APPLICATION_EXTENSION_API_ONLY = 'NO'` Podfile `post_install` build-setting override from earlier versions — it is no longer needed. **Objective-C with modules disabled:** if an app target builds with `CLANG_ENABLE_MODULES = NO` (or uses Objective-C++), also add `#import ` where you use the notification model types (`PENotification`, `PENotificationOpenResult`, `SubscriberDetailsData`). Targets with modules enabled (the default) need nothing extra. ## Prerequisites - Xcode installed - Apple Developer account with a valid App ID configured on the Apple Developer Portal - APNs certificate (.p12) ([create one](https://www.pushengage.com/documentation/guide-to-creating-ios-apns-certificate/)) - PushEngage account and App ID (see [Get Your App ID](/api/mobile-sdk#get-your-app-id)) Let an AI agent do the integration for you Every step on this page can be handled by an AI coding agent. Install the official [PushEngage Agent Skills](/api/ai-agents#agent-skills) in Claude Code, Cursor, or another compatible agent: ```bash npx skills add awesomemotive/pushengage-skills ``` Then ask it to _"integrate PushEngage into this app"_. The skills walk the agent through the full setup and can also diagnose a broken integration. ## Step 1 — Install the SDK Choose either Swift Package Manager or CocoaPods. Use only one method. 1. In Xcode, go to **File → Add Package Dependencies**. 2. Paste `https://github.com/awesomemotive/pushengage-ios-sdk` into the search bar. 3. Click **Add Package**. 4. Under **Add to Target**, select your main app target. 5. Click **Add Package**. If CocoaPods is not installed, run: ```bash sudo gem install cocoapods ``` Initialize a Podfile in your project root: ```bash pod init ``` Add the PushEngage dependency to your Podfile: Podfile ```ruby target 'YourProjectName' do use_frameworks! pod 'PushEngage' end ``` Install the pods: ```bash pod repo update pod install ``` Open the generated `.xcworkspace` file in Xcode going forward (not `.xcodeproj`). ## Step 2 — Configure Xcode Capabilities ### Enable Push Notifications 1. In Xcode, select the root project in the Project Navigator and choose your main app target. 2. Go to **Signing & Capabilities**. 3. Click **\+ Capability** and add **Push Notifications**. If Push Notifications is not visible in Xcode: go to your Apple Developer account → **Certificates, Identifiers & Profiles** → select your App ID → enable **Push Notifications** → return to Xcode and try again. ### Enable Background Modes 1. Click **\+ Capability** and add **Background Modes**. 2. Check both **Remote notifications** and **Background fetch**. ## Step 3 — Connect to PushEngage Dashboard 1. Log in to your [PushEngage Dashboard](https://app.pushengage.com). 2. Navigate to **Site Settings → Installation → iOS SDK**. Under **"1. Configure your Apple iOS APNs settings"**: 3. Enter your **App Push Id** (your iOS Bundle Identifier, e.g. `com.example.MyApp`). 4. Upload your **APNs Certificate (.p12)**. 5. Enter the **Private Key Password** you set when exporting the .p12. 6. Click **Update**. Under **"2. Install the SDK"**: 7. Click **Copy** next to the **App ID** — you'll pass it to `setAppID` in the next step. ## Step 4 — Initialize the SDK Add PushEngage initialization to your AppDelegate: AppDelegate.swift ```swift import UIKit import PushEngage class AppDelegate: UIResponder, UIApplicationDelegate { override init() { super.init() PushEngage.swizzleInjection(isEnabled: true) } func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { PushEngage.setAppID(id: "YOUR_APP_ID") PushEngage.setInitialInfo(for: application, with: launchOptions) PushEngage.enableLogging = true // Prompt for notification permission. On grant, the SDK registers with // APNs and subscribes the device automatically. Without this call the // system prompt never appears and no subscriber is created. PushEngage.requestNotificationPermission { granted, error in print("Notification permission granted: \(granted)") } return true } } ``` AppDelegate.m ```objc #import "AppDelegate.h" @import PushEngage; @implementation AppDelegate - (instancetype)init { self = [super init]; if (self) { [PushEngage swizzleInjectionWithIsEnabled:YES]; } return self; } - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [PushEngage setAppIDWithId:@"YOUR_APP_ID"]; [PushEngage setInitialInfoFor:application with:launchOptions]; [PushEngage setEnableLogging:YES]; // Prompt for notification permission. On grant, the SDK registers with // APNs and subscribes the device automatically. Without this call the // system prompt never appears and no subscriber is created. [PushEngage requestNotificationPermissionWithCompletion:^(BOOL granted, NSError * _Nullable error) { NSLog(@"Notification permission granted: %d", granted); }]; return YES; } @end ``` YourApp.swift ```swift import SwiftUI import PushEngage @main struct YourApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate var body: some Scene { WindowGroup { ContentView() } } } class AppDelegate: NSObject, UIApplicationDelegate { override init() { super.init() PushEngage.swizzleInjection(isEnabled: true) } func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil ) -> Bool { PushEngage.setAppID(id: "YOUR_APP_ID") PushEngage.setInitialInfo(for: application, with: launchOptions) PushEngage.enableLogging = true // Prompt for notification permission. On grant, the SDK registers with // APNs and subscribes the device automatically. Without this call the // system prompt never appears and no subscriber is created. PushEngage.requestNotificationPermission { granted, error in print("Notification permission granted: \(granted)") } return true } } ``` note Replace `YOUR_APP_ID` with the App ID copied in Step 3. Production builds Remove or set `PushEngage.enableLogging = false` before releasing to production. Leaving it enabled exposes internal SDK data in production app logs. ## Step 5 — Send a Test Notification 1. Build and run your app on a **physical iOS device** — push notifications do not work in the simulator. 2. Accept the notification permission prompt when it appears. 3. In the PushEngage Dashboard, go to **Campaign → Push Broadcasts → Create New Push Broadcast**. 4. Send a test notification to yourself. You should receive the notification within a few seconds. Your integration is complete. To unlock rich notifications and images, continue with the steps below. ## Enable Rich Notifications The following steps enable rich notifications (images, action buttons) and app badge sync. They require adding iOS extension targets to your Xcode project. ## Step 6 — Add Notification Service Extension The Notification Service Extension enables rich notifications (images, action buttons). It is required for full PushEngage functionality. ### Create the Extension 1. In Xcode, go to **File → New → Target**. 2. Select **Notification Service Extension** and click **Next**. 3. Name it `PushEngageNotificationServiceExtension` and click **Finish**. 4. When prompted to activate the scheme, click **Cancel** — this keeps Xcode's debugging focus on your main app. 5. Select the new extension target in the Project Navigator and set its **Deployment Target** to **iOS 12** or above. ### Add SDK Dependency to the Extension Extension-only module Notification extensions link the companion `PushEngageExtension` module, not the main `PushEngage` module. `PushEngageExtension` is extension-safe and ships the three notification-extension APIs (`didReceiveNotificationExtensionRequest`, `serviceExtensionTimeWillExpire`, `getCustomUIPayLoad`). Linking the wrong module will break push delivery on production builds. 1. Select your project in the navigator. 2. Select the `PushEngageNotificationServiceExtension` target. 3. Go to **General → Frameworks, Libraries, and Embedded Content**. 4. Click **+**, find `PushEngageExtension`, and add it. Add a sibling target for the extension at the top level of your Podfile: Podfile ```ruby target 'YourProjectName' do use_frameworks! pod 'PushEngage' end target 'PushEngageNotificationServiceExtension' do use_frameworks! pod 'PushEngageExtension' end ``` Run: ```bash pod repo update pod install ``` ### Implement the Extension Replace the auto-generated `NotificationService.swift` with: PushEngageNotificationServiceExtension/NotificationService.swift ```swift import UserNotifications import PushEngageExtension @available(iOSApplicationExtension 12.0, *) class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? var request: UNNotificationRequest? override func didReceive( _ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void ) { self.request = request self.contentHandler = contentHandler self.bestAttemptContent = request.content.mutableCopy() as? UNMutableNotificationContent if let bestContent = bestAttemptContent { PushEngageExtension.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) contentHandler(bestContent) } } override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let request = request, let bestAttemptContent = bestAttemptContent { guard let content = PushEngageExtension.serviceExtensionTimeWillExpire( request, content: bestAttemptContent ) else { contentHandler(bestAttemptContent) return } contentHandler(content) } } } ``` ## Step 7 — Add Notification Content Extension (Optional) The Notification Content Extension renders the custom expanded UI for PushEngage notifications sent with a matching category. Without it, notifications still deliver normally, but any custom expanded layout falls back to iOS's default view. ### Create the Extension 1. In Xcode, go to **File → New → Target**. 2. Select **Notification Content Extension** and click **Next**. 3. Name it `PushEngageNotificationContentExtension` and click **Finish**. 4. When prompted to activate the scheme, click **Cancel**. 5. Set the Deployment Target to **iOS 12** or above. 6. Open the extension's `Info.plist`. Under **NSExtension → NSExtensionAttributes → UNNotificationExtensionCategory**, set the array to contain the category identifier(s) you'll send with custom-UI notifications (must match the `categoryIdentifier` check in your extension code). ### Add SDK Dependency 1. Select your project in the navigator. 2. Select the `PushEngageNotificationContentExtension` target. 3. Go to **General → Frameworks, Libraries, and Embedded Content**. 4. Click **+**, find `PushEngageExtension`, and add it. Add to your Podfile: Podfile (addition) ```ruby target 'PushEngageNotificationContentExtension' do use_frameworks! pod 'PushEngageExtension' end ``` Run `pod install`. ### Implement the Extension PushEngageNotificationContentExtension/NotificationViewController.swift ```swift import UIKit import UserNotifications import UserNotificationsUI import PushEngageExtension @available(iOSApplicationExtension 12.0, *) class NotificationViewController: UIViewController, UNNotificationContentExtension { override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .white } func didReceive(_ notification: UNNotification) { if notification.request.content.categoryIdentifier == "your_category_identifier" { let payload = PushEngageExtension.getCustomUIPayLoad(for: notification.request) // Pass payload to your custom SwiftUI or UIKit view } } } ``` ## Step 8 — Add App Groups App Groups enable communication between the main app and notification extensions. This step is required. 1. Select your main app target in Xcode → **Signing & Capabilities** → **\+ Capability** → **App Groups**. 2. Click **+** to add a new App Group with a unique name, e.g., `group.com.yourcompany.yourapp`. 3. In your main app target's `Info.plist`, add: - Key: `PushEngage_App_Group_Key` - Value: `group.com.yourcompany.yourapp` 4. In `PushEngageNotificationServiceExtension`'s `Info.plist`, add the same key and value. 5. Select the extension target → **Signing & Capabilities** → enable the same App Group. ## Troubleshooting **Build error: User Script Sandboxing** In Xcode → **Build Settings** → search for **User Script Sandboxing** → set to **No**. **Conflict between Firebase SDK and PushEngage swizzling** Disable PushEngage swizzling and wire up all four delegate methods below — omitting any one will silently break notifications, click tracking, or foreground display: ```swift // In AppDelegate.init() PushEngage.swizzleInjection(isEnabled: false) // In AppDelegate func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { PushEngage.registerDeviceToServer(with: deviceToken) } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { PushEngage.receivedRemoteNotification(application: application, userInfo: userInfo, completionHandler: completionHandler) } // Set yourself as UNUserNotificationCenter delegate, then: func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { PushEngage.willPresentNotification(center: center, notification: notification, completionHandler: completionHandler) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { PushEngage.didReceiveRemoteNotification(with: response) completionHandler() } ``` ## Next Steps - [iOS SDK Reference →](/api/mobile-sdk/ios/sdk-reference) - [Mobile SDK Overview →](/api/mobile-sdk) --- Source: https://www.pushengage.com/api/mobile-sdk/ios/sdk-reference # iOS SDK Reference Complete API reference for the PushEngage iOS SDK. For setup and installation, see the [iOS Quickstart](/api/mobile-sdk/ios/quickstart). The SDK supports Swift, Objective-C, and SwiftUI applications. iOS 12.0+ is required. [![CocoaPods](https://img.shields.io/cocoapods/v/PushEngage.svg?label=CocoaPods)](https://cocoapods.org/pods/PushEngage) [![GitHub release](https://img.shields.io/github/v/release/awesomemotive/pushengage-ios-sdk.svg?label=GitHub)](https://github.com/awesomemotive/pushengage-ios-sdk/releases) ## Initialization ### swizzleInjection This method automates the setup process of the SDK through method swizzling. If developers prefer not to handle the SDK setup manually, calling this method in the init method of the Application AppDelegate is essential. Method swizzling allows the SDK to perform necessary configurations without explicit manual intervention. #### Syntax ```swift swizzleInjection(isEnabled: Bool) ``` #### Parameters `isEnabled`: A boolean value indicating whether to enable the SDK setup through method swizzling. - Set to `true` to enable automatic SDK configuration using method swizzling. - Set to `false` to require manual setup configuration. #### Usage Automated Setup - Swift ```swift PushEngage.swizzleInjection(isEnabled: true) ``` Manual Setup - Swift ```swift PushEngage.swizzleInjection(isEnabled: false) ``` ### setAppID Call this method in the AppDelegate to set the app push ID in the SDK, registering the subscriber to that specific app push ID. #### Syntax ```swift setAppID(id: String) ``` #### Parameters `id`: A String representing the app push ID to be set. - This ID uniquely identifies the app in the push notification service. - Must be a valid app push ID provided by PushEngage. #### Usage Swift ```swift PushEngage.setAppID(id: "YOUR_APP_PUSH_ID") ``` ### setEnvironment Set the environment for the SDK, allowing developers to switch between staging and production environments. #### Syntax ```swift setEnvironment(environment: PEEnvironment) ``` #### Parameters `environment`: An enum value of type `PEEnvironment` representing the desired environment to be set. - Use `.staging` for development and testing purposes. - Use `.production` for live app deployments. #### Usage Swift ```swift PushEngage.setEnvironment(environment: .staging) ``` ### setInitialInfo Provide necessary prerequisite information to the SDK for internal setup. #### Syntax ```swift setInitialInfo(for application: UIApplication, with launchOptions: [UIApplication.LaunchOptionsKey: Any]?) ``` #### Parameters `application`: An instance of UIApplication representing the host application. `launchOptions`: A dictionary of launch options passed to the application during launch. #### Usage Swift ```swift PushEngage.setInitialInfo(for: UIApplication.shared, with: launchOptions) ``` ### registerDeviceToServer Register Device Token Manually. Use this method to manually register the device token with the PushEngage server if swizzling is not used. #### Syntax ```swift registerDeviceToServer(with deviceToken: Data) ``` #### Parameters `deviceToken`: The device token obtained from Apple Push Notification service (APNs) as Data. - This token uniquely identifies the app installation on the device. - Required for sending push notifications to this specific device. #### Usage Swift ```swift func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { PushEngage.registerDeviceToServer(with: deviceToken) } ``` ## Notification Permission ### requestNotificationPermission Show prompt to user asking for notification permission. #### Syntax ```swift requestNotificationPermission(completion: @escaping (_ response: Bool, _ error: Error?) -> Void) ``` #### Parameters `completion`: A closure that gets called with the result of the permission request. - `isGranted`: A boolean indicating the permission result. - Will be `true` if notification permission was granted by the user. - Will be `false` if notification permission was denied by the user. - `error`: An optional Error object that contains details if the permission request failed. - Will be `nil` if the operation completed successfully. #### Returns Delivered asynchronously via completion closure: - `isGranted` (`Bool`): `true` if notification permission was granted. - `error` (`Error?`): An error object if the request failed, `nil` otherwise. #### Usage Swift ```swift PushEngage.requestNotificationPermission { isGranted, error in if isGranted { print("Notification permission granted successfully.") return } if let error = error { print("Failed to get notification permission: \(error.localizedDescription)") } else { print("Notification permission denied by user.") } } ``` ### getNotificationPermissionStatus Get the current notification permission status for the application. This method returns the permission status synchronously as a string. #### Syntax ```swift getNotificationPermissionStatus() -> String ``` #### Returns A `String` indicating the current notification permission state: - `"granted"`: The application is authorized to post user notifications - `"denied"`: The application is not authorized to post user notifications - `"notYetRequested"`: The user has not yet made a choice regarding notification permissions #### Usage Swift ```swift let permissionStatus = PushEngage.getNotificationPermissionStatus() switch permissionStatus { case "granted": print("Notifications are allowed") case "denied": print("Notifications are denied") case "notYetRequested": print("Permission not yet requested") default: print("Unknown permission status") } ``` ## Subscription ### subscribe Subscribe the user to receive push notifications. Use this method when you want to subscribe a user who has previously unsubscribed. #### Syntax ```swift subscribe(completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `completionHandler`: A completion handler that is called when the subscription operation is complete. - `response`: A boolean indicating the subscription result. - Will be `true` if the user was successfully subscribed to push notifications. - Will be `false` if the subscription operation failed. - `error`: An optional Error object that contains details if the subscription failed. - This will be `nil` if the operation completed successfully. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if successfully subscribed. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift PushEngage.subscribe { response, error in if response { print("Successfully subscribed to notifications.") return } if let error = error { print("Failed to subscribe: \(error.localizedDescription)") } else { print("Unknown error occurred while subscribing.") } } ``` Objective-C ```objc [PushEngage subscribeWithCompletionHandler:^(BOOL response, NSError * _Nullable error) { if (response) { NSLog(@"Successfully subscribed to notifications."); return; } if (error) { NSLog(@"Failed to subscribe: %@", error.localizedDescription); } else { NSLog(@"Unknown error occurred while subscribing."); } }]; ``` ### unsubscribe Unsubscribe the current subscriber from receiving push notifications. Once unsubscribed, the subscriber will no longer receive any push notifications. #### Syntax ```swift unsubscribe(completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `completionHandler`: A closure that is called when the unsubscription operation is complete. - `response`: A boolean indicating the unsubscription result. - Will be `true` if the user was successfully unsubscribed from push notifications. - Will be `false` if the unsubscription operation failed. - `error`: An optional Error object that contains details if the unsubscription failed. - This will be `nil` if the operation completed successfully. #### Returns Delivered asynchronously via `completionHandler`: - `result` (`Bool`): `true` if successfully unsubscribed. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift PushEngage.unsubscribe { result, error in if result { print("Successfully unsubscribed from push notifications") return } if let error = error { print("Failed to unsubscribe: \(error.localizedDescription)") } else { print("Unknown error occurred while unsubscribing") } } ``` Objective-C ```objc [PushEngage unsubscribeWithCompletionHandler:^(BOOL result, NSError * _Nullable error) { if (result) { NSLog(@"Successfully unsubscribed from push notifications"); return; } if (error) { NSLog(@"Failed to unsubscribe: %@", error.localizedDescription); } else { NSLog(@"Unknown error occurred while unsubscribing"); } }]; ``` ### getSubscriptionStatus Check whether the user is currently subscribed to push notifications. #### Syntax ```swift getSubscriptionStatus(completionHandler: @escaping (_ isSubscribed: Bool, _ error: Error?) -> Void) ``` #### Parameters `completionHandler`: A closure that provides the subscription status when the check is complete. - `isSubscribed`: A boolean indicating the user's subscription status. - Will be `true` if the user is subscribed to push notifications. - Will be `false` if the user is unsubscribed from push notifications. - `error`: An optional Error object that contains details if the status check failed. - This will be `nil` if the operation completed successfully. #### Returns Delivered asynchronously via `completionHandler`: - `isSubscribed` (`Bool`): `true` if the user is subscribed, `false` if unsubscribed. - `error` (`Error?`): An error object if the status check failed, `nil` on success. #### Usage Swift ```swift PushEngage.getSubscriptionStatus { isSubscribed, error in if let error = error { print("Failed to get subscription status: \(error.localizedDescription)") return } print("Is subscribed: \(isSubscribed)") } ``` Objective-C ```objc [PushEngage getSubscriptionStatusWithCompletionHandler:^(BOOL isSubscribed, NSError * _Nullable error) { if (error) { NSLog(@"Failed to get subscription status: %@", error.localizedDescription); return; } NSLog(@"Is subscribed: %d", isSubscribed); }]; ``` ### getSubscriptionNotificationStatus Check whether user can receive push notification. This method checks both the user's subscription status to push notification and their system-level notification permission status. A user can receive notifications only if they are subscribed to push notification and system-level notification permission status is granted. #### Syntax ```swift getSubscriptionNotificationStatus(completionHandler: @escaping (_ canReceiveNotifications: Bool, _ error: Error?) -> Void) ``` #### Parameters `completionHandler`: A closure that is called when the status check is complete. - `canReceiveNotifications`: A boolean indicating whether the user can receive notifications. - Will be `true` if the user is both subscribed and has notification permissions granted. - Will be `false` if the user is unsubscribed or has notification permissions denied. - `error`: An optional Error object that contains details if the status check failed. - This will be `nil` if the operation completed successfully. #### Returns Delivered asynchronously via `completionHandler`: - `canReceiveNotifications` (`Bool`): `true` if the subscriber can receive notifications. - `error` (`Error?`): An error object if the status check failed, `nil` on success. #### Usage Swift ```swift PushEngage.getSubscriptionNotificationStatus { canReceiveNotifications, error in if let error = error { print("Failed to get notification status: \(error.localizedDescription)") return } if canReceiveNotifications { print("User can receive push notifications") } else { print("User cannot receive push notifications") } } ``` ## Associate Profile ID Profile IDs serve as unique identifiers for your subscribers, enabling you to recognize them across multiple devices. Each subscriber can be assigned just one profile ID. This ID should be a string, and you have the flexibility to use any value, such as an email or phone number. ### getSubscriberId Retrieve the unique subscriber ID assigned to the current device by PushEngage. #### Syntax ```swift getSubscriberId(completion: @escaping (_ response: String?) -> Void) ``` #### Parameters | Parameter | Type | Description | | --- | --- | --- | | `completion` | closure | Called with the subscriber ID string, or `nil` if not yet subscribed. | #### Returns Delivered asynchronously via `completion`: - `response` (`String?`): The unique subscriber identifier, or `nil` if the device is not yet subscribed. #### Usage Swift ```swift PushEngage.getSubscriberId { subscriberId in if let subscriberId = subscriberId { print("Subscriber ID: \(subscriberId)") } else { print("Not yet subscribed") } } ``` ### addProfile This method allows you to set a profile ID for the current subscriber. If a profile ID already exists, it will be replaced with the new value. #### Syntax ```swift addProfile(for id: String, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `id`: The subscriber ID to associate with the SDK. `completionHandler`: A completion handler that provides the response of the method call as a boolean value, along with an optional error if the operation fails. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if the profile was associated successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift PushEngage.addProfile(for: "USER_ID") { response, error in if response { print("Profile associated successfully.") } else if let error = error { print("Failed to associate profile: \(error.localizedDescription)") } } ``` Objective-C ```objc [PushEngage addProfileFor:@"USER_ID" completionHandler:^(BOOL response, NSError * _Nullable error) { if (response) { NSLog(@"Profile associated successfully."); } else if (error) { NSLog(@"Failed to associate profile: %@", error.localizedDescription); } }]; ``` ## User Identity The `identify` and `logout` methods manage the predefined personal-data fields stored on the current subscriber. Use them to tie a push subscription to your own first-party user data, and to clear that data on sign-out without unsubscribing the device. The 12 valid keys are: `first_name`, `last_name`, `email`, `phone`, `gender`, `dob`, `language`, `profile_id`, `country`, `city`, `state`, `zip`. Values must be `String`, `NSNumber` (Int/Double/Float), or `Bool` — other types are rejected client-side. ### identify Upsert one or more of the 12 predefined subscriber fields. Repeat calls with the same payload short-circuit locally and fire the success callback without a network round-trip; the local cache has a 24h TTL so dashboard-side edits surface within a day. A numeric `profile_id` is coerced to its string form before being sent. #### Syntax ```swift identify(fields: Parameters, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `fields`: A dictionary containing one or more of the 12 valid subscriber fields. Must contain at least one key. `completionHandler` (optional): Closure fired with `(success, error)` once the call completes. #### Usage Swift ```swift let fields: [String: Any] = [ "first_name": "Jane", "last_name": "Doe", "email": "jane@example.com", "profile_id": "user_12345" ] PushEngage.identify(fields: fields) { success, error in if success { print("Subscriber fields updated") } else if let error = error { print("Identify failed: \(error.localizedDescription)") } } ``` Objective-C ```objc NSDictionary *fields = @{ @"first_name": @"Jane", @"last_name": @"Doe", @"email": @"jane@example.com", @"profile_id": @"user_12345" }; [PushEngage identifyWithFields:fields completionHandler:^(BOOL response, NSError * _Nullable error) { if (response) { NSLog(@"Subscriber fields updated"); } else if (error) { NSLog(@"Identify failed: %@", error.localizedDescription); } }]; ``` ### logout Remove a set of predefined personal fields from the current subscriber while keeping the device subscribed for push. Call this when a user signs out of your app to detach their PII from the push subscription. Passing `nil` or an empty array removes the default PII field set: `first_name`, `last_name`, `email`, `phone`, `gender`, `dob`, `profile_id`. Passing an array of field names scopes the removal to those specific fields. If none of the requested field names are currently cached locally, the call short-circuits with success and no network request is sent. #### Syntax ```swift logout(fieldNames: [String]?, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `fieldNames`: An optional array of field names to remove. Pass `nil` or an empty array to clear the default PII set. `completionHandler` (optional): Closure fired with `(success, error)` once the call completes. #### Usage Clear the default PII set: Swift ```swift PushEngage.logout(fieldNames: nil) { success, error in if success { print("Default PII fields cleared") } } ``` Objective-C ```objc [PushEngage logoutWithFieldNames:nil completionHandler:^(BOOL response, NSError * _Nullable error) { if (response) { NSLog(@"Default PII fields cleared"); } }]; ``` Clear specific fields: Swift ```swift PushEngage.logout(fieldNames: ["email", "phone"]) { success, error in if success { print("Email and phone cleared") } } ``` Objective-C ```objc [PushEngage logoutWithFieldNames:@[@"email", @"phone"] completionHandler:^(BOOL response, NSError * _Nullable error) { if (response) { NSLog(@"Email and phone cleared"); } }]; ``` ## Subscriber Details ### getSubscriberDetails This method retrieves subscriber details based on a provided list of strings, which can include city, state, country, device, device type, segments, etc. #### Syntax ```swift getSubscriberDetails(for keys: [String]?, completionHandler: ((_ response: SubscriberDetailsData?, _ error: Error?) -> Void)?) ``` #### Parameters `keys (Optional)`: An optional array of strings specifying the specific keys of information to retrieve for the subscriber. If no keys are provided, the API will return complete subscriber details. `completionHandler`: A closure that provides the response as a SubscriberDetailsData object representing the subscriber details, or an optional error object if any error occurs during the operation. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`SubscriberDetailsData?`): The subscriber details object, or `nil` if not available. - `error` (`Error?`): An error object if the retrieval failed, `nil` on success. #### Usage Swift ```swift let keys = ["country", "city"] // Optional: Retrieve specific keys like country and city. PushEngage.getSubscriberDetails(for: keys) { response, error in if let subscriberDetails = response { print("Subscriber Details: \(subscriberDetails)") } else { if let error = error { print("Failed to retrieve subscriber details: \(error.localizedDescription)") } else { print("Unknown error occurred while retrieving subscriber details.") } } } ``` #### Available Fields | Field | Type | Description | | --- | --- | --- | | `city` | string | Subscriber's city | | `state` | string | Subscriber's state or region | | `country` | string | Subscriber's country | | `device` | string | Device name | | `device_type` | string | Device type (e.g., mobile, tablet) | | `user_agent` | string | App user agent string | | `host` | string | Host identifier | | `timezone` | string | Subscriber's timezone | | `has_unsubscribed` | boolean | Whether the subscriber has unsubscribed | | `ts_created` | string | ISO 8601 timestamp of subscription creation | | `segments` | array | Segment IDs the subscriber belongs to | ## Segments Segments are used to group subscribers so that you can send personalized notifications. Segments can be created based on attributes, categories, and more. ### addSegments This method enables you to add the current subscriber to segments. #### Syntax ```swift addSegments(_ segments: [String], completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `segments`: An array of strings containing segment information to be added to the subscriber's profile. `completionHandler`: A closure that provides a response indicating whether the operation was successful (true if successful, false otherwise) and an optional error object if any error occurs during the operation. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if segments were added successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift PushEngage.addSegments(["Segment1", "Segment2"]) { response, error in if response { print("Segments added successfully.") } else { if let error = error { print("Failed to add segments: \(error.localizedDescription)") } else { print("Unknown error occurred while adding segments.") } } } ``` ### addDynamicSegments This method enables you to add the current subscriber to a segment for a specified duration, measured in days. After this period, the segment will be automatically removed from the subscriber. #### Syntax ```swift addDynamicSegments(_ dynamicSegments: [[String: Any]], completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `dynamicSegments`: An array of dictionaries where the keys are strings and the values can be of any type. Each dictionary represents a dynamic segment with a "name" key (string) and a "duration" key (integer) indicating the duration of the segment in days. `completionHandler`: A closure that provides a boolean response indicating whether the operation was successful (true if successful, false otherwise) and an optional error object if any error occurs during the operation. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if dynamic segments were added successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift let dynamicSegments: [[String: Any]] = [ ["name": "Cricket", "duration": 3], ["name": "Tennis", "duration": 7], ] PushEngage.addDynamicSegments(dynamicSegments) { response, error in if response { print("Dynamic segments updated successfully.") } else { if let error = error { print("Failed to update dynamic segments: \(error.localizedDescription)") } else { print("Unknown error occurred while updating dynamic segments.") } } } ``` ### removeSegments This method allows you to remove the current subscriber from segments. #### Syntax ```swift removeSegments(_ segments: [String], completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `segments`: An array of strings representing the segment names to be removed from the subscriber. `completionHandler`: A completion handler that provides the response of the method call as a boolean value, along with an optional error if the operation fails. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if segments were removed successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift PushEngage.removeSegments(["SegmentToRemove"]) { response, error in if response { print("Segments removed successfully.") } else { if let error = error { print("Failed to remove segments: \(error.localizedDescription)") } else { print("Unknown error occurred while removing segments.") } } } ``` ## Attributes Attributes are key-value pairs that allow you to store additional information about your subscribers. You can utilize attributes to segment your subscribers and send personalized notifications. ### addSubscriberAttributes Use this method to add or update attributes for a subscriber. If an attribute with the specified key already exists, the existing value will be replaced. note Replaces the deprecated `add(attributes:completionHandler:)`, which still works but now forwards to this method. #### Syntax ```swift addSubscriberAttributes(_ attributes: Parameters, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)? = nil) ``` #### Parameters `attributes`: A dictionary representing the attributes to be added or updated. Should be in the format \["attributeName": attributeValue\]. `completionHandler`: A closure that gets called after the update operation is completed. The closure provides a response boolean indicating success or failure and an optional error. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if attributes were added successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift let attributes = ["name": "Bob", "age": 30] PushEngage.addSubscriberAttributes(attributes) { success, error in if success { print("Attributes added/updated successfully.") } else { if let error = error { print("Error occurred: \(error.localizedDescription)") } else { print("Unknown error occurred.") } } } ``` note The attributes parameter supports \[String: Any\] type, allowing for a variety of attribute types to be added. ### setSubscriberAttributes This method allows you to set attributes for a subscriber, replacing any previously associated attributes. Use this method when you need to entirely reset the attributes with new values. note Replaces the deprecated `set(attributes:completionHandler:)`, which still works but now forwards to this method. #### Syntax ```swift setSubscriberAttributes(_ attributes: Parameters, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)? = nil) ``` #### Parameters `attributes`: A dictionary representing the attributes to be added. Should be in the format \["attributeName": attributeValue\]. `completionHandler`: A closure that gets called after the update operation is completed. The closure provides a response boolean indicating success or failure and an optional error. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if attributes were set successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift let attributes = ["name": "Bob", "age": 30] PushEngage.setSubscriberAttributes(attributes) { success, error in if success { print("Attributes added successfully.") } else { if let error = error { print("Error occurred: \(error.localizedDescription)") } else { print("Unknown error occurred.") } } } ``` note The attributes parameter supports \[String: Any\] type, allowing for a variety of attribute types to be added or updated. ### getSubscriberAttributes Retrieve the attributes associated with the current subscriber using this method. #### Syntax ```swift getSubscriberAttributes(completionHandler: @escaping(_ attributes: [String: Any]?, _ error: Error?) -> Void) ``` #### Parameters `completionHandler`: A completion handler that is called when the attribute retrieval is complete. - `attributes`: A dictionary containing the subscriber attributes as \[String: Any\]?. - Will contain the subscriber's attributes if successfully retrieved. - Will be `nil` if no attributes are found or retrieval fails. - `error`: An optional Error object that contains details if the attribute retrieval failed. - This will be `nil` if the operation completed successfully. #### Returns Delivered asynchronously via `completionHandler`: - `attributes` (`[String: Any]?`): A dictionary of attribute key-value pairs, or `nil` if none exist. - `error` (`Error?`): An error object if the retrieval failed, `nil` on success. #### Usage Swift ```swift PushEngage.getSubscriberAttributes { attributes, error in if let attributes = attributes { print("Subscriber attributes retrieved successfully: \(attributes)") } else { if let error = error { print("Error occurred: \(error.localizedDescription)") } else { print("Unknown error occurred.") } } } ``` ### deleteSubscriberAttributes This method allows you to remove one or more attributes from the current subscriber. Provide an array of attribute names you wish to remove. Passing an empty array will result in the removal of all the subscriber's attributes. #### Syntax ```swift deleteSubscriberAttributes(for keys: [String], completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `keys`: An array of strings representing the attribute keys to be removed. Pass an empty array to remove all subscriber attributes associated with the device. `completionHandler`: A completion handler that provides the response of the API call as a boolean value, along with an optional error if the operation fails. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if attributes were deleted successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift PushEngage.deleteSubscriberAttributes(for: ["AttributeKeyToDelete"]) { response, error in if response { print("Attributes deleted successfully.") } else { if let error = error { print("Failed to delete attributes: \(error.localizedDescription)") } else { print("Unknown error occurred while deleting attributes.") } } } ``` ## Automated Notifications Automated notifications include all types of triggered campaigns such as cart abandonment, price drop, back in stock, and browse abandonment. By default, automated notifications are enabled for all subscribers. ### automatedNotification Enable or disable all automated (triggered) notifications for the current subscriber. #### Syntax ```swift automatedNotification(status: TriggerStatusType, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `status`: A `TriggerStatusType` value indicating whether to enable or disable triggered campaigns. - `.enabled` — Enable automated notifications for this subscriber. - `.disabled` — Disable automated notifications for this subscriber. `completionHandler`: A completion handler called when the operation completes. - `response` (`Bool`): `true` if the status was updated successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift // Enable automated notifications PushEngage.automatedNotification(status: .enabled) { response, error in if response { print("Automated notifications enabled") } else if let error = error { print("Failed to enable: \(error.localizedDescription)") } } // Disable automated notifications PushEngage.automatedNotification(status: .disabled) { response, error in if response { print("Automated notifications disabled") } else if let error = error { print("Failed to disable: \(error.localizedDescription)") } } ``` ## Triggered Campaigns ### sendTriggerEvent Detect your visitor's behavior to send automated push notifications to the right person at the right time. #### Syntax ```swift sendTriggerEvent(triggerCampaign: TriggerCampaign, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `triggerCampaign`: The TriggerCampaign object representing the campaign event to be triggered. `completionHandler`: A completion handler that provides the response of the method call as a boolean value, along with an optional error if the operation fails. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if the trigger event was sent successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift let triggerCampaign = TriggerCampaign(campaignName: "name_of_campaign", eventName: "name_of_event", referenceId: "your_reference_id", //optional profileId: nil, //optional data: ["custom_key": "custom_value"] //optional ) PushEngage.sendTriggerEvent(triggerCampaign: triggerCampaign) { result, error in if result { print("Send Trigger Alert Successful") } else { print("Failure") } } ``` ### addAlert Re-engage your customers and increase conversion using Price Drop Alert Campaigns and Inventory Alert Campaigns. #### Syntax ```swift addAlert(triggerAlert: TriggerAlert, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `triggerAlert`: The TriggerAlert object representing the alert to be added. `completionHandler`: A completion handler that provides the response of the method call as a boolean value, along with an optional error if the operation fails. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if the trigger alert was added successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift let triggerAlert = TriggerAlert(type: .priceDrop, productId: "product_id", link: "product_link", price: 100.0, variantId: "product_variant_id", //optional expiryTimestamp: nil, //optional, Date? alertPrice: 102.0, //optional; omitted when nil (server may apply a default) availability: .inStock, //optional; omitted when nil (server may apply a default) data: ["custom_key": "custom_value"] //optional ) PushEngage.addAlert(triggerAlert: triggerAlert) { result, error in if result { print("Add Alert Successful") } else { print("Failure") } } ``` Swift ```swift let triggerAlert = TriggerAlert(type: .inventory, productId: "product_id", link: "product_link", price: 100.0, variantId: "product_variant_id", //optional expiryTimestamp: nil, //optional, Date? availability: .outOfStock, //optional; omitted when nil (server may apply a default) data: ["custom_key": "custom_value"] //optional ) PushEngage.addAlert(triggerAlert: triggerAlert) { result, error in if result { print("Add Alert Successful") } else { print("Failure") } } ``` ## Custom Events ### trackEvent Track a custom event for the current subscriber. Custom events are used to start or exit campaign workflows based on in-app activity — for example, adding an item to cart, completing a purchase, or any other action you define. Workflows are configured from the PushEngage Dashboard. #### Syntax ```swift trackEvent(name: String, properties: Parameters?, profileId: String?, provider: String?, eventType: String?, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters | Parameter | Type | Required | Description | | --- | --- | :-: | --- | | `name` | `String` | Yes | Event name (e.g., `"MySite.AddToCart"`). | | `properties` | `[String: Any]?` | No | Custom key-value payload. Values must be `String`, `NSNumber` (Int/Double/Float), or `Bool`. | | `profileId` | `String?` | No | Subscriber profile id to attribute the event to. | | `provider` | `String?` | No | Provider name. Defaults to `"PushEngage"`. | | `eventType` | `String?` | No | Event type. Defaults to `"PushEngage.CustomEvent"`. | | `completionHandler` | `((Bool, Error?) -> Void)?` | No | Closure fired with `(success, error)` once the call completes. | #### Usage Swift ```swift PushEngage.trackEvent(name: "MySite.AddToCart", properties: ["amount": 19.99, "currency": "USD"], profileId: "user-42", provider: nil, eventType: nil) { success, error in if success { print("Event tracked") } else if let error = error { print("Failed to track event: \(error.localizedDescription)") } } ``` Objective-C ```objc [PushEngage trackEventWithName:@"MySite.AddToCart" properties:@{@"amount": @19.99, @"currency": @"USD"} profileId:@"user-42" provider:nil eventType:nil completionHandler:^(BOOL response, NSError * _Nullable error) { if (response) { NSLog(@"Event tracked"); } else if (error) { NSLog(@"Failed to track event: %@", error.localizedDescription); } }]; ``` ## Goal Tracking Goal Tracking will help you assign conversion goals & value to your notification campaigns. You can set up a default goal and have it integrated for all your campaigns. ### sendGoal #### Syntax ```swift sendGoal(goal: Goal, completionHandler: ((_ response: Bool, _ error: Error?) -> Void)?) ``` #### Parameters `goal`: Goal object representing the goal to be tracked. `completionHandler`: A completion handler that provides the response of the method call as a boolean value, along with an optional error if the operation fails. #### Returns Delivered asynchronously via `completionHandler`: - `response` (`Bool`): `true` if the goal was tracked successfully. - `error` (`Error?`): An error object if the operation failed, `nil` on success. #### Usage Swift ```swift let goal = Goal(name: "purchase", count: 1, value: 10.0) PushEngage.sendGoal(goal: goal) { response, error in if response { print("Goal tracked successfully") } else if let error = error { print("Failed to track goal: \(error.localizedDescription)") } } ``` Objective-C ```objc Goal *goal = [[Goal alloc] initWithName:@"purchase" count:@1 value:@10.0]; [PushEngage sendGoalWithGoal:goal completionHandler:^(BOOL response, NSError * _Nullable error) { if (response) { NSLog(@"Goal tracked successfully"); } else if (error) { NSLog(@"Failed to track goal: %@", error.localizedDescription); } }]; ``` ## Deep Linking ### setNotificationOpenHandler Use this method to set the notification open handler during SDK initialization. When a notification is opened, this handler will take the necessary action and provide the required user information for deep linking. #### Syntax ```swift setNotificationOpenHandler(block: PENotificationOpenHandler?) ``` #### Parameters `block`: A closure of type `PENotificationOpenHandler?` that handles the notification open action. - This closure is called when a notification is opened by the user. - Use this closure to perform specific actions based on the opened notification. #### Usage Swift ```swift PushEngage.setNotificationOpenHandler { result in // Handle notification open action here if let actionID = result.notificationAction.actionID { switch actionID { case "Your_Custom_Action_ID_1": // Handle custom action 1 case "Your_Custom_Action_ID_2": // Handle custom action 2 default: // Handle default action } } } ``` ## Notification Handlers ### setNotificationWillShowInForegroundHandler Use this method to set the notification handler for when notifications are received while the app is in foreground mode. This allows you to handle notifications effectively when the app is active and in the foreground. #### Syntax ```swift setNotificationWillShowInForegroundHandler(block: PENotificationWillShowInForeground?) ``` #### Parameters `block`: A closure of type `PENotificationWillShowInForeground?` that handles notifications received while the app is in the foreground. - This closure is passed from the AppDelegate. - Allows you to customize the behavior when dealing with incoming notifications in the foreground. #### Usage Swift ```swift PushEngage.setNotificationWillShowInForegroundHandler { notification, completion in //Replace with your custom code if notification.contentAvailable == 1 { // In case a completion handler is not set, it will be called after 25 seconds. completion(nil) } else { completion(notification) } } ``` ### receivedRemoteNotification Handle Remote Notifications Manually. Use this method to handle remote notifications manually if swizzling is not used or if you want to customize the notification handling behavior. #### Syntax ```swift receivedRemoteNotification(application:userInfo:completionHandler:) ``` #### Parameters `application`: `UIApplication` instance. `userInfo`: The remote notification payload received from APNs as `[AnyHashable: Any]`. `completionHandler`: The completion handler provided by the host application for background fetch completion. This handler must be called after processing the notification. #### Returns A boolean value indicating if any background work was started by the SDK. #### Usage Swift ```swift func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let didStartBackgroundWork = PushEngage.receivedRemoteNotification(application: application, userInfo: userInfo, completionHandler: completionHandler) if !didStartBackgroundWork { // Handle the notification in the foreground, if required. } } ``` ### willPresentNotification Handle the presentation of notifications while the app is in the foreground. Use this method to manage how a notification is presented when the app is active. By default, notifications may not be shown when the app is active, but this method allows you to control whether they should be presented. #### Syntax ```swift willPresentNotification(center:notification:completionHandler:) ``` #### Parameters `center`: The `UNUserNotificationCenter` responsible for delivering the notification. `notification`: The `UNNotification` object containing the notification information that was delivered. `completionHandler`: A completion handler to execute with the desired notification presentation options. #### Usage Swift ```swift @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { PushEngage.willPresentNotification(center: center, notification: notification, completionHandler: completionHandler) } ``` ### didReceiveRemoteNotification Handles the remote notification interaction for devices running iOS 10.0 and above. This method should be implemented in the application's UNUserNotificationCenterDelegate to process the response of a remote notification if swizzling is not used. When a user interacts with a notification, this method should be called to handle the response and perform appropriate actions based on the user's interaction. #### Syntax ```swift didReceiveRemoteNotification(with notification: UNNotificationResponse) ``` #### Parameters `notification`: The `UNNotificationResponse` object representing the user's response to a remote notification. - Contains information about the notification that was interacted with. - Includes details about the user's action (tap, dismiss, etc.). #### Usage Swift ```swift @available(iOS 10.0, *) func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { PushEngage.didReceiveRemoteNotification(with: response) // Handle the response and perform appropriate actions based on user's interaction // Call the completion handler after processing the notification completionHandler() } ``` ## Notification Extensions The three methods below are exposed by the **`PushEngageExtension`** module, which is the extension-safe companion to the main `PushEngage` module. Link `PushEngageExtension` to your Notification Service Extension and Notification Content Extension targets — never `PushEngage`. See the [iOS Quickstart](/api/mobile-sdk/ios/quickstart) for setup details. Migration Guide: 0.1.0 → 1.0.0 The SDK was split into two modules in 1.0.0. Extension targets that previously imported `PushEngage` must switch to `PushEngageExtension` for both the import and the class prefix on call sites — method names are unchanged. ### getCustomUIPayLoad Get Custom UI Payload for Notification. Use this method to get the custom UI payload associated with a notification request. #### Syntax ```swift getCustomUIPayLoad(for request: UNNotificationRequest) -> CustomUIModel ``` #### Parameters `request`: The UNNotificationRequest object for which you want to retrieve the custom UI payload. - This should be the notification request received from the system. - Contains the notification content and metadata needed to extract the custom UI payload. #### Returns A `CustomUIModel` object containing the custom UI payload for the given notification request. #### Usage Swift ```swift import PushEngageExtension class NotificationViewController: UIViewController, UNNotificationContentExtension { func didReceive(_ notification: UNNotification) { if #available(iOS 10.0, *) { let customUIPayload = PushEngageExtension.getCustomUIPayLoad(for: notification.request) // Process custom UI payload here } } } ``` ### didReceiveNotificationExtensionRequest Modify the notification content received from the parent application in the Notification Service Extension. #### Syntax ```swift didReceiveNotificationExtensionRequest(_ request: UNNotificationRequest, bestContentHandler: UNMutableNotificationContent) ``` #### Parameters `request`: The UNNotificationRequest received from the parent application. `bestContentHandler`: The UNMutableNotificationContent that can be modified to customize the notification. #### Usage Swift ```swift import PushEngageExtension class NotificationService: UNNotificationServiceExtension { var contentHandler: ((UNNotificationContent) -> Void)? var bestAttemptContent: UNMutableNotificationContent? var request : UNNotificationRequest? override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) { self.request = request self.contentHandler = contentHandler self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent) if let bestContent = bestAttemptContent { PushEngageExtension.didReceiveNotificationExtensionRequest(request, bestContentHandler: bestContent) contentHandler(bestContent) } } } ``` ### serviceExtensionTimeWillExpire Use this method in the notification service extension to handle notification content just before the extension gets terminated. #### Syntax ```swift serviceExtensionTimeWillExpire(_ request: UNNotificationRequest, content: UNMutableNotificationContent?) -> UNMutableNotificationContent? ``` #### Parameters `request`: The original `UNNotificationRequest` received by the extension. `content`: The mutable content for the notification. This content can be modified as needed before delivery. #### Returns The modified `UNMutableNotificationContent` that will be delivered to the user. #### Usage Swift ```swift import PushEngageExtension class NotificationService: UNNotificationServiceExtension { override func serviceExtensionTimeWillExpire() { if let contentHandler = contentHandler, let request = request ,let bestAttemptContent = bestAttemptContent { guard let content = PushEngageExtension.serviceExtensionTimeWillExpire(request, content: bestAttemptContent) else { contentHandler(bestAttemptContent) return } contentHandler(content) } } } ``` ## Utilities ### setBadgeCount Set the badge count for the application icon. This method allows you to update the numeric badge displayed on the application's icon. #### Syntax ```swift setBadgeCount(count: Int) ``` #### Parameters `count`: An integer value representing the number to be displayed as the badge. - Use any positive integer to display that number as the badge. - Use `0` to remove the badge completely. #### Usage Swift ```swift PushEngage.setBadgeCount(count: 5) ``` ### enableLogging Enables or disables verbose debug logging for the SDK. When enabled, the SDK prints detailed diagnostic output to the Xcode console. danger Set `enableLogging` to `false` before releasing to the App Store to avoid leaking internal SDK state to device logs. #### Syntax ```swift enableLogging: Bool ``` #### Usage Swift ```swift // Enable during development PushEngage.enableLogging = true // Disable for production PushEngage.enableLogging = false ``` ### getSdkVersion Retrieve the current version string of the PushEngage iOS SDK. #### Syntax ```swift getSdkVersion() -> String ``` #### Parameters None #### Returns A `String` containing the current SDK version (e.g., `"1.0.0"`). #### Usage Swift ```swift let version = PushEngage.getSdkVersion() print("PushEngage SDK version: \(version)") ``` --- Source: https://www.pushengage.com/api/mobile-sdk/react-native/expo-guide # PushEngage with Expo and Bare Workflow Which React Native setup should you use with PushEngage? This guide explains the differences, what works, what doesn't, and how to set up PushEngage in each environment. ## Quick Answer | Setup | Supported | Notes | | --- | --- | --- | | **Bare Workflow** | Yes | Full support, recommended | | **Expo Development Build** | Yes (with manual native config) | Requires `expo-dev-client` + `npx expo prebuild` | | **Expo Go** | No | Expo Go cannot run custom native modules | PushEngage React Native SDK includes native code (Swift/Kotlin) and uses React Native's New Architecture (TurboModules). It requires access to the native `ios/` and `android/` project directories — which bare workflow provides by default and Expo provides via development builds. ## Understanding the Difference ### Bare Workflow A standard React Native project with full native directories (`ios/` and `android/`). You have direct access to Xcode and Android Studio projects. **When to use:** You need full control over native configuration, you're already using bare workflow, or you want the simplest PushEngage setup. ### Expo Managed Workflow (with Development Builds) Expo manages most native configuration for you. Use `npx expo prebuild` to generate native directories, and `expo-dev-client` to build a custom development app that includes PushEngage's native code. **When to use:** Your team already uses Expo and you want to keep the Expo developer experience (EAS Build, OTA updates). ### Expo Go A pre-built app from the App Store / Play Store for quick prototyping. It contains a fixed set of native modules. **Why it doesn't work:** Expo Go does not include PushEngage's native SDK. When your JavaScript tries to call PushEngage's native module, it crashes because the native code doesn't exist in the Expo Go binary. This is true for all third-party push notification SDKs — not just PushEngage. ## Setup: Bare Workflow (Recommended) This is the standard setup. If you're using bare React Native, follow these steps. For a complete walkthrough including Firebase and APNs setup, see the [React Native Quickstart](/api/mobile-sdk/react-native/quickstart). ### 1\. Install the SDK ```bash npm install @pushengage/pushengage-react-native # or yarn add @pushengage/pushengage-react-native ``` ### 2\. iOS Setup Install pods: ```bash cd ios && pod install && cd .. ``` Then in Xcode: - Add **Push Notifications** capability to your main target - Add **Background Modes** capability and enable **Remote notifications** - Add **App Groups** capability (same group ID on main target and any extensions) - Add to your `Info.plist`: ```xml PushEngage_App_Group_Key group.com.yourcompany.yourapp ``` For rich notifications (images, action buttons), add a **Notification Service Extension** target in Xcode. ### 3\. Android Setup Add the JitPack repository to `android/settings.gradle`: ```groovy dependencyResolutionManagement { repositories { google() mavenCentral() maven { url 'https://jitpack.io' } } } ``` Add the Google Services classpath to your project-level `android/build.gradle`: ```groovy buildscript { dependencies { classpath 'com.google.gms:google-services:4.4.0' } } ``` Add the Google Services plugin to `android/app/build.gradle`: ```groovy plugins { id 'com.android.application' id 'com.google.gms.google-services' } ``` Place your `google-services.json` in `android/app/`, and add the notification permission to `android/app/src/main/AndroidManifest.xml`: ```xml ``` ### 4\. Initialize and Use Initialize PushEngage in `index.js` before `AppRegistry.registerComponent` — this is the earliest entry point and guarantees the SDK is ready before any component renders: index.js ```tsx import { AppRegistry } from 'react-native'; import PushEngage from '@pushengage/pushengage-react-native'; import App from './App'; PushEngage.setAppId('YOUR_APP_ID'); AppRegistry.registerComponent('YourAppName', () => App); ``` Then in your app, listen for deep links and request permission: App.tsx ```tsx import React, { useEffect } from 'react'; import { Button, View } from 'react-native'; import type { EventSubscription } from 'react-native'; import PushEngage from '@pushengage/pushengage-react-native'; function App() { const subscriptionRef = React.useRef(null); useEffect(() => { // Fires when a notification is tapped and contains a deep link subscriptionRef.current = PushEngage.onValueChanged( (event: { deepLink: string; data: { [key: string]: string } }) => { console.log('Deep link:', event.deepLink); console.log('Notification data:', event.data); // Navigate using your router here }, ); return () => { subscriptionRef.current?.remove(); subscriptionRef.current = null; }; }, []); const handleEnablePush = async () => { const granted = await PushEngage.requestNotificationPermission(); if (granted) { console.log('User is now subscribed to push notifications'); } }; return (