> ## 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.

# Android Push Notifications

> FCM token registration, delivery, and tap tracking

<Info>
  **Supported on:** Android SDK `1.0.33+`
</Info>

The AppDNA SDK provides a complete push notification module for registering device tokens via Firebase Cloud Messaging (FCM), handling notification delivery, and tracking user interactions.

## Prerequisites

Before using push notifications, configure FCM for your app:

1. Add `google-services-appdna.json` (or your project's `google-services.json`) to your `app/src/main/assets/` directory.
2. Apply the `com.google.gms.google-services` Gradle plugin in your app module.
3. Enable Firebase Cloud Messaging in the [Firebase Console](https://console.firebase.google.com).
4. Upload your FCM service account credentials (or legacy server key) to the AppDNA Console under **Settings > Push > Android Configuration**.

<Warning>
  Push notifications will not be delivered to your users without `google-services-appdna.json` in your project AND FCM credentials uploaded to the AppDNA Console.
</Warning>

## Request Permission

On Android 13+ (API 33), push notifications require the `POST_NOTIFICATIONS` runtime permission. Request it from a coroutine — `requestPermission` is a `suspend` function that uses the Activity Result API internally and returns a `Boolean` once the user responds:

```kotlin theme={null}
import androidx.lifecycle.lifecycleScope

class MainActivity : androidx.activity.ComponentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        lifecycleScope.launch {
            val granted = AppDNA.push.requestPermission(activity = this@MainActivity)
            Log.d("Push", "Permission granted: $granted")
        }
    }
}
```

The SDK handles the runtime prompt **and** updates internal permission state automatically — you do not need to override `onRequestPermissionsResult` or call `setPushPermission` afterwards. On API \< 33 the call is a no-op and returns `true`.

<Warning>
  The `activity` parameter must be a `ComponentActivity` (or subclass like `AppCompatActivity` / `FragmentActivity`). The Activity Result API the SDK uses requires `ComponentActivity`.
</Warning>

Java-friendly variant: `AppDNA.push.requestPermissionFuture(activity): CompletableFuture<Boolean>`.

## Set Token Manually

Register the device push token when it is received from FCM. This is typically done in your `FirebaseMessagingService`:

```kotlin theme={null}
import ai.appdna.sdk.AppDNA
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage

class MyFirebaseMessagingService : FirebaseMessagingService() {

    override fun onNewToken(token: String) {
        super.onNewToken(token)
        AppDNA.setPushToken(token)
    }

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)
        // Handle message
    }
}
```

You can also use the alternative method name:

```kotlin theme={null}
AppDNA.onNewPushToken(token)
```

Both `setPushToken` and `onNewPushToken` perform the same operation. The token registration payload sent to the backend includes: `token`, `platform="android"`, `device_id`, `app_version`, `sdk_version`, and `os_version`.

## Set Permission Status

Manually update the SDK with the current push permission status:

```kotlin theme={null}
AppDNA.setPushPermission(granted = true)
```

<Note>
  On Android 13+ (API 33), push notifications require the `POST_NOTIFICATIONS` runtime permission. Call `setPushPermission` after the user responds to the permission request.
</Note>

## Track Delivery and Taps

Track when a push notification is delivered to the device:

```kotlin theme={null}
AppDNA.trackPushDelivered(pushId = "push-123")
```

Track when a user taps on a push notification:

```kotlin theme={null}
AppDNA.trackPushTapped(pushId = "push-123", action = "open_workout")
```

The `action` parameter is optional. Pass `null` if no specific action is associated with the tap.

## Push Module

Access the push module directly for advanced usage:

```kotlin theme={null}
val pushModule = AppDNA.push
```

### PushModule Methods

| Method / Property         | Signature                                                                  | Description                                                                                                                                                             |
| ------------------------- | -------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`                   | `token: String?`                                                           | The currently registered FCM device token                                                                                                                               |
| `getToken`                | `getToken(): String?`                                                      | Returns the current device token                                                                                                                                        |
| `setToken`                | `setToken(token: String)`                                                  | Registers the push token with AppDNA backend                                                                                                                            |
| `requestPermission`       | `suspend fun requestPermission(activity: Activity? = null): Boolean`       | Request `POST_NOTIFICATIONS` (Android 13+) via Activity Result API. Returns `true` once granted (or on API \< 33). Requires a `ComponentActivity` to host the launcher. |
| `requestPermissionFuture` | `requestPermissionFuture(activity: Activity?): CompletableFuture<Boolean>` | Java-friendly variant of `requestPermission`                                                                                                                            |
| `trackDelivered`          | `trackDelivered(pushId: String)`                                           | Track push delivery                                                                                                                                                     |
| `trackTapped`             | `trackTapped(pushId: String, action: String?)`                             | Track push tap with optional action                                                                                                                                     |
| `setDelegate`             | `setDelegate(delegate: AppDNAPushDelegate?)`                               | Set a delegate for push lifecycle callbacks                                                                                                                             |

## AppDNAPushDelegate

All 3 methods on this delegate fire from the SDK's push pipeline:

* `onPushTokenRegistered(token)` — fires whenever the SDK registers a new FCM token (or detects a change). Token registration with the AppDNA backend continues regardless of whether you implement this method.
* `onPushReceived(notification, inForeground)` — fires when a push arrives.
* `onPushTapped(notification, actionId)` — fires when the user taps a notification or one of its action buttons.

Implement the `AppDNAPushDelegate` interface to receive push notification lifecycle callbacks:

```kotlin theme={null}
interface AppDNAPushDelegate {
    fun onPushTokenRegistered(token: String)
    fun onPushReceived(notification: PushPayload, inForeground: Boolean)
    fun onPushTapped(notification: PushPayload, actionId: String?)
}
```

### Example Implementation

```kotlin theme={null}
import ai.appdna.sdk.AppDNAPushDelegate
import ai.appdna.sdk.PushPayload

class MyPushDelegate : AppDNAPushDelegate {

    override fun onPushTokenRegistered(token: String) {
        Log.d("Push", "Token registered: $token")
    }

    override fun onPushReceived(notification: PushPayload, inForeground: Boolean) {
        if (inForeground) {
            // Show in-app notification banner
            showBanner(title = notification.title, body = notification.body)
        }
    }

    override fun onPushTapped(notification: PushPayload, actionId: String?) {
        val action = notification.action
        if (action != null) {
            // Handle deep link or custom action
            handleAction(action)
        }
    }

    private fun showBanner(title: String, body: String) { /* ... */ }
    private fun handleAction(action: Any) { /* ... */ }
}

// Register the delegate
AppDNA.push.setDelegate(MyPushDelegate())
```

## PushPayload

The `PushPayload` data class contains the notification content:

| Property   | Type                | Description                            |
| ---------- | ------------------- | -------------------------------------- |
| `pushId`   | `String`            | Unique identifier for the notification |
| `title`    | `String`            | Notification title                     |
| `body`     | `String`            | Notification body text                 |
| `imageUrl` | `String?`           | URL to a rich notification image       |
| `data`     | `Map<String, Any>?` | Custom data payload                    |
| `action`   | `PushAction?`       | Action to perform when tapped          |

## PushAction

The `PushAction` data class defines the action associated with a notification tap:

| Property | Type     | Description                                            |
| -------- | -------- | ------------------------------------------------------ |
| `type`   | `String` | Action type (e.g., `"deep_link"`, `"url"`, `"screen"`) |
| `value`  | `String` | Action value (e.g., a URL or screen identifier)        |

## 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        |

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

## Full Example

```kotlin theme={null}
import android.app.Application
import ai.appdna.sdk.AppDNA
import ai.appdna.sdk.Environment

class MyApplication : Application() {

    private val pushHandler = MyPushDelegate()

    override fun onCreate() {
        super.onCreate()
        AppDNA.configure(
            context = applicationContext,
            apiKey = "adn_live_xxx",
            environment = Environment.PRODUCTION,
        )

        AppDNA.push.setDelegate(pushHandler)
    }
}

// In MainActivity.onCreate(...):
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Request POST_NOTIFICATIONS on Android 13+
    lifecycleScope.launch {
        AppDNA.push.requestPermission(activity = this@MainActivity)
    }
}
```

<Note>
  The SDK automatically handles token refresh. When FCM issues a new token via `FirebaseMessagingService.onNewToken`, call `AppDNA.setPushToken(token)` (or `AppDNA.onNewPushToken(token)`) to update registration with the AppDNA backend.
</Note>

## Next Steps

* Set up [Billing](/sdks/android/billing) to handle in-app purchases
* Configure [Onboarding](/sdks/android/onboarding) flows
* Present [Paywalls](/sdks/android/paywall) with purchase handling
* Learn about [Offline Support](/sdks/android/offline) for event resilience
