> ## Documentation Index
> Fetch the complete documentation index at: https://docs.appdna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# React Native Push Notifications

> Token registration, delivery and tap tracking

<Info>
  **Supported on:** iOS SDK `1.0.70+` · Android SDK `1.0.42+` · React Native SDK `1.0.7+` (New Architecture only)
</Info>

The AppDNA React Native SDK provides a complete push notification module for registering device tokens, tracking delivery and taps, and handling notification interactions through delegates and event listeners.

## Platform Setup

Before using push notifications, configure the native push services for each platform:

### iOS

1. In Xcode, enable the **Push Notifications** capability under your target's **Signing & Capabilities** tab.
2. Generate an APNs authentication key (.p8 file) in the [Apple Developer Portal](https://developer.apple.com/account/resources/authkeys/list).
3. Upload the .p8 key to the AppDNA Console under **Settings > Push > iOS Configuration**.

### Android

1. Add your `google-services.json` file to the `android/app` directory.
2. Upload the Firebase Server Key to the AppDNA Console under **Settings > Push > Android Configuration**.

<Warning>
  Without the platform-specific push credentials uploaded to the Console, push notifications will not be delivered to your users.
</Warning>

## Static Methods

The `AppDNA` class exposes top-level static methods for common push operations:

### Set Token

Pass a device push token to the SDK manually:

```typescript theme={null}
await AppDNA.setPushToken("device-token-string");
```

### Set Permission Status

Update the SDK with the current push permission status:

```typescript theme={null}
await AppDNA.setPushPermission(true);
```

### Track Delivery

Track when a push notification is delivered to the device:

```typescript theme={null}
await AppDNA.trackPushDelivered("push-123");
```

### Track Tap

Track when a user taps on a push notification, with an optional action identifier:

```typescript theme={null}
await AppDNA.trackPushTapped("push-123", "open_workout");
```

## Push Module

Access the push module through the `AppDNA.push` property for advanced usage:

```typescript theme={null}
const push = AppDNA.push;
```

### Module Methods

| Method              | Signature                                                          | Description                                                           |
| ------------------- | ------------------------------------------------------------------ | --------------------------------------------------------------------- |
| `setToken`          | `setToken(token: string): Promise<void>`                           | Register a device push token                                          |
| `setPermission`     | `setPermission(granted: boolean): Promise<void>`                   | Update the push permission status                                     |
| `trackDelivered`    | `trackDelivered(pushId: string): Promise<void>`                    | Track notification delivery                                           |
| `trackTapped`       | `trackTapped(pushId: string, action?: string): Promise<void>`      | Track notification tap with optional action                           |
| `requestPermission` | `requestPermission(): Promise<boolean>`                            | Request push permission from the user                                 |
| `getToken`          | `getToken(): Promise<string \| null>`                              | Returns the current device token, or null                             |
| `setDelegate`       | `setDelegate(delegate: Partial<AppDNAPushDelegate> \| null): void` | Set a delegate for push notification callbacks. Pass `null` to clear. |

### Request Permission

Request push notification permission from the user. This presents the system permission dialog:

```typescript theme={null}
const granted = await AppDNA.push.requestPermission();

if (granted) {
  console.log("Push permission granted");
} else {
  console.log("Push permission denied");
}
```

### Get Current Token

Retrieve the current device push token:

```typescript theme={null}
const token = await AppDNA.push.getToken();
if (token) {
  console.log("Current token:", token);
}
```

## AppDNAPushDelegate

Set a delegate to receive push notification lifecycle callbacks:

```typescript theme={null}
interface AppDNAPushDelegate {
  onPushTokenRegistered(token: string): void;
  onPushReceived(notification: Record<string, unknown>, inForeground: boolean): void;
  /** `actionId` is `undefined` when the user tapped the body rather than an action button. */
  onPushTapped(notification: Record<string, unknown>, actionId: string | undefined): void;
}
```

### Example Implementation

```typescript theme={null}
declare function showBanner(title: unknown, body: unknown): void;
declare function handleAction(actionId: string): void;

AppDNA.push.setDelegate({
  onPushTokenRegistered(token: string) {
    console.log('Token registered:', token);
  },

  onPushReceived(notification: Record<string, unknown>, inForeground: boolean) {
    if (inForeground) {
      // The notification map uses the snake_case keys native sends.
      showBanner(notification.title, notification.body);
    }
  },

  onPushTapped(notification: Record<string, unknown>, actionId: string | undefined) {
    if (actionId) {
      // Handle custom action
      handleAction(actionId);
    }
  },
});
```

## Standalone AppDNAPush Class

For a more React-idiomatic approach, use the standalone `AppDNAPush` class with callback-based listeners:

```typescript theme={null}
import { AppDNAPush } from '@appdna-ai/react-native-sdk';
```

### Methods

| Method              | Signature                                                                               | Description                           |
| ------------------- | --------------------------------------------------------------------------------------- | ------------------------------------- |
| `requestPermission` | `requestPermission(): Promise<boolean>`                                                 | Request push permission from the user |
| `onPushReceived`    | `onPushReceived(cb: (payload: PushPayload, inForeground: boolean) => void): () => void` | Listen for received notifications     |
| `onPushTapped`      | `onPushTapped(cb: (payload: PushPayload, actionId?: string) => void): () => void`       | Listen for notification taps          |

### Example with Listeners

```typescript theme={null}
import { AppDNAPush } from '@appdna-ai/react-native-sdk';

declare function navigateTo(target: string): void;

// Request permission
const granted = await AppDNAPush.requestPermission();

// Listen for received push notifications
const unsubReceived = AppDNAPush.onPushReceived((payload, inForeground) => {
  console.log('Push received:', payload.title, inForeground);
});

// Listen for push notification taps. The payload keys are snake_case.
const unsubTapped = AppDNAPush.onPushTapped((payload, actionId) => {
  console.log('Push tapped:', payload.push_id, actionId);
  if (payload.action_value) {
    navigateTo(payload.action_value);
  }
});

// Clean up listeners when component unmounts
unsubReceived();
unsubTapped();
```

<Warning>
  Always call the unsubscribe function returned by `onPushReceived` and `onPushTapped` when the component unmounts to prevent memory leaks.
</Warning>

## PushPayload

The `PushPayload` type contains the notification content. **The keys are snake\_case** — they are the
keys native puts on the wire, not camelCase:

| Property       | Type                                  | Description                                                        |
| -------------- | ------------------------------------- | ------------------------------------------------------------------ |
| `push_id`      | `string`                              | Unique identifier for the notification                             |
| `title`        | `string`                              | Notification title                                                 |
| `body`         | `string`                              | Notification body text                                             |
| `image_url`    | `string \| undefined`                 | URL to a rich notification image                                   |
| `data`         | `Record<string, string> \| undefined` | Custom data payload                                                |
| `action_type`  | `string \| undefined`                 | Action type (e.g., "deep\_link", "url")                            |
| `action_value` | `string \| undefined`                 | Action value (e.g., a URL or screen ID)                            |
| `actions`      | `PushAction[] \| undefined`           | The registered action buttons — look up the tapped `actionId` here |

Each entry in `actions` is a `PushAction`:

| Property       | Type                  | Description                                              |
| -------------- | --------------------- | -------------------------------------------------------- |
| `id`           | `string`              | Button identifier — matches `actionId` in `onPushTapped` |
| `label`        | `string`              | Button label as displayed                                |
| `action_type`  | `string`              | Action type (e.g., "deep\_link", "url")                  |
| `action_value` | `string \| undefined` | Action value (e.g., a URL or screen ID)                  |

## Native events

Native emits the following to the push delegate:

| Event                   | Payload                                           | Triggered When                          |
| ----------------------- | ------------------------------------------------- | --------------------------------------- |
| `onPushTokenRegistered` | `{ token: string }`                               | Device token is successfully registered |
| `onPushReceived`        | `{ payload: PushPayload, inForeground: boolean }` | A push notification is received         |
| `onPushTapped`          | `{ payload: PushPayload, actionId?: string }`     | User taps on a push notification        |

<Info>
  Use `AppDNAPush` or `AppDNA.push.setDelegate(...)`. There is no supported way to subscribe to these
  events directly, and a `NativeEventEmitter` subscription would never fire.
</Info>

## Auto-Tracked Events

The SDK automatically tracks the following push-related events:

| Event                     | Triggered When                          |
| ------------------------- | --------------------------------------- |
| `push_token_registered`   | Device token is successfully registered |
| `push_permission_granted` | User grants push permission             |
| `push_permission_denied`  | User denies push permission             |
| `push_delivered`          | A push notification is delivered        |
| `push_tapped`             | User taps on a push notification        |

<Note>
  Auto-tracked events are sent alongside any manually tracked events. You do not need to track these events yourself.
</Note>

## Full Example

```tsx theme={null}
import React, { useEffect } from 'react';
import { AppDNA, AppDNAPush } from '@appdna-ai/react-native-sdk';

declare function showInAppBanner(notification: Record<string, unknown>): void;
declare function handleDeepLink(
  notification: Record<string, unknown>,
  actionId: string | undefined,
): void;
declare function MainNavigator(): React.ReactElement;

function App() {
  useEffect(() => {
    async function setupPush() {
      // Configure the SDK
      await AppDNA.configure('adn_live_xxx', 'production');

      // Set delegate for lifecycle callbacks
      AppDNA.push.setDelegate({
        onPushTokenRegistered(token) {
          console.log("Token:", token);
        },
        onPushReceived(notification, inForeground) {
          if (inForeground) {
            showInAppBanner(notification);
          }
        },
        onPushTapped(notification, actionId) {
          handleDeepLink(notification, actionId);
        },
      });

      // Request permission
      const granted = await AppDNA.push.requestPermission();
      console.log("Push permission:", granted);
    }

    void setupPush();
  }, []);

  // Listener-based approach for component-scoped handling
  useEffect(() => {
    const unsubReceived = AppDNAPush.onPushReceived((payload) => {
      console.log('Received:', payload.title);
    });

    const unsubTapped = AppDNAPush.onPushTapped((payload) => {
      console.log('Tapped:', payload.push_id);
    });

    return () => {
      unsubReceived();
      unsubTapped();
    };
  }, []);

  return <MainNavigator />;
}
```
