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

# Flutter Push Notifications

> Token registration, delivery and tap tracking

<Info>
  **Supported on:** iOS SDK `1.0.61+` · Android SDK `1.0.33+` · Flutter SDK `1.0.3+`
</Info>

The AppDNA Flutter SDK provides a complete push notification module for registering device tokens, tracking delivery and taps, and handling notification interactions through delegates and streams.

## Platform Setup

Before using push notifications, complete the platform-specific setup:

### 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. Set up Firebase Cloud Messaging (FCM) in your Android project.
2. Add the `google-services.json` file to your `android/app/` directory.
3. Upload the FCM server key to the AppDNA Console under **Settings > Push > Android Configuration**.

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

## Push Module

Access the push module through the `AppDNA.push` accessor, which returns an `AppDNAPushModule` instance:

```dart theme={null}
final pushModule = AppDNA.push;
```

## Request Permission

Request push notification permission from the user. This presents the system permission dialog only — it does **not** register for remote notifications. To register for remote notifications, call `AppDNA.push.registerForPush()` (see below):

```dart theme={null}
final granted = await AppDNA.push.requestPermission();

if (granted) {
  print("Push permission granted");
} else {
  print("Push permission denied");
}
```

The method returns a `bool` indicating whether the user granted permission.

## Set Token Manually

If you handle token registration yourself (e.g., using `firebase_messaging`), pass the token string to the SDK:

```dart theme={null}
await AppDNA.push.setToken("fcm-token-xxx");
```

Or via the static method:

```dart theme={null}
await AppDNA.setPushToken("fcm-token-xxx");
```

## Get Current Token

Retrieve the registered push token:

```dart theme={null}
final token = await AppDNA.push.getToken();
if (token != null) {
  print("Current token: $token");
}
```

Returns `String?` -- `null` if no token has been registered.

## Set Permission Status

Manually update the SDK with the current push permission status:

```dart theme={null}
await AppDNA.push.setPermission(true);
```

Or via the static method:

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

## Track Delivery and Taps

Track when a push notification is delivered to the device:

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

Track when a user taps on a push notification:

```dart theme={null}
await AppDNA.push.trackTapped("push-123", action: "open_workout");
```

These are also available as static methods:

```dart theme={null}
await AppDNA.trackPushDelivered("push-123");
await AppDNA.trackPushTapped("push-123", action: "open");
```

## AppDNAPushDelegate

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

* `onPushTokenRegistered(token)` — fires whenever the SDK registers a new APNs (iOS) or FCM (Android) 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. `notification` is the full payload map; `inForeground` is `true` when the app is active.
* `onPushTapped(notification, actionId)` — fires when the user taps a notification or one of its action buttons. `actionId` is the identifier of the tapped action button if the user tapped one, otherwise `null`.

Implement the `AppDNAPushDelegate` abstract class to receive push notification lifecycle callbacks:

```dart theme={null}
abstract class AppDNAPushDelegate {
  void onPushTokenRegistered(String token);
  void onPushReceived(Map<String, dynamic> notification, bool inForeground);
  void onPushTapped(Map<String, dynamic> notification, String? actionId);
}
```

### Example Implementation

```dart theme={null}
class MyPushHandler implements AppDNAPushDelegate {
  @override
  void onPushTokenRegistered(String token) {
    print("Token registered: $token");
  }

  @override
  void onPushReceived(Map<String, dynamic> notification, bool inForeground) {
    if (inForeground) {
      // Show in-app notification banner
      print("Received in foreground: ${notification['title']}");
    }
  }

  @override
  void onPushTapped(Map<String, dynamic> notification, String? actionId) {
    print("Push tapped, action: $actionId");
    // Handle deep link or custom action
  }
}

// Set the delegate
AppDNA.push.setDelegate(MyPushHandler());
```

## AppDNAPush Helper Methods

The `AppDNAPush` class exposes static helpers for permission and token/tap plumbing. Push lifecycle **callbacks** arrive through `AppDNA.push.setDelegate(...)` (above) — `AppDNAPush` has no streams.

| Method              | Signature                                   | Description                                                                                                      |
| ------------------- | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `requestPermission` | `Future<bool> requestPermission()`          | Request push notification permission.                                                                            |
| `registerForPush`   | `Future<bool> registerForPush()`            | Request permission and register for remote notifications. Real on iOS; on Android routes to `requestPermission`. |
| `handlePushTap`     | `Future<bool> handlePushTap()`              | Hand a notification tap to the SDK for attribution/routing. **Android-only** — a no-op returning `false` on iOS. |
| `onNewPushToken`    | `Future<void> onNewPushToken(String token)` | Feed a freshly-issued push token (e.g. from FCM `onNewToken`) into the SDK. **Android-only** — a no-op on iOS.   |

```dart theme={null}
final granted = await AppDNAPush.requestPermission();
if (granted) {
  await AppDNAPush.registerForPush();
}
```

## Notification Payload Shape

There is no public `PushPayload` Dart class. The `notification` argument delivered to the `AppDNAPushDelegate` callbacks is a raw `Map<String, dynamic>` with camelCase keys:

| Key        | 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, dynamic>?` | Custom data payload                                                               |
| `action`   | `Map<String, dynamic>?` | Action as `{type, value}` (e.g. `{"type": "deep_link", "value": "app://screen"}`) |

```dart theme={null}
@override
void onPushTapped(Map<String, dynamic> notification, String? actionId) {
  final action = notification['action'] as Map<String, dynamic>?;
  final value = action?['value'] as String?;
  if (value != null) {
    // Navigate to the action target
  }
}
```

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

```dart theme={null}
import 'package:appdna_sdk/appdna_sdk.dart';

class MyPushHandler implements AppDNAPushDelegate {
  @override
  void onPushTokenRegistered(String token) {
    print("Token: $token");
  }

  @override
  void onPushReceived(Map<String, dynamic> notification, bool inForeground) {
    if (inForeground) {
      showInAppBanner(notification);
    }
  }

  @override
  void onPushTapped(Map<String, dynamic> notification, String? actionId) {
    navigateToContent(notification, actionId);
  }
}

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  await AppDNA.configure(
    apiKey: "adn_live_xxx",
    env: AppDNAEnvironment.production,
  );

  // Set up push delegate
  AppDNA.push.setDelegate(MyPushHandler());

  // Request permission
  final granted = await AppDNA.push.requestPermission();
  print("Push permission: $granted");

  runApp(const MyApp());
}
```

<Note>
  Push notification behavior varies between iOS and Android. On iOS, the system permission dialog is shown on first request. On Android 13+, the `POST_NOTIFICATIONS` runtime permission is requested. The native SDKs handle these differences transparently.
</Note>
