Skip to content

Push Notifications

Android (FCM)

AppAmbit provides a Push Notifications SDK that integrates push notifications into your applications using Firebase Cloud Messaging (FCM). The SDK manages device tokens, handles notification payloads, and covers all application lifecycle states.

Prerequisites

If you haven't yet configured your environment on the AppAmbit platform to start sending Push Notifications, go to the next page.

Requirements

  • AppAmbit Core SDK (com.AppAmbit.Maui)
  • .NET 10 targeting net10.0-android
  • Android API level 21 or newer
  • A Firebase project with google-services.json
  • AppAmbit Core SDK (com.appambit:appambit)
  • Android API level 21 (Lollipop) or newer
  • A Firebase project with google-services.json
  • AppAmbit Core SDK (appambit_sdk_flutter)
  • Android API level 24 (Nougat) or newer
  • A Firebase project with google-services.json
  • AppAmbit Core SDK (appambit)
  • Android API level 21 (Lollipop) or newer
  • A Firebase project with google-services.json
  • AppAmbit Core SDK (com.AppAmbit.Avalonia)
  • .NET 10 targeting net10.0-android
  • Android API level 21 or newer
  • A Firebase project with google-services.json

Install dependencies

Install both packages via NuGet:

dotnet add package com.AppAmbit.Maui --version 4.0.1
dotnet add package com.AppAmbit.PushNotifications --version 4.0.1

Place google-services.json under Platforms/Android/ and add the following to your .csproj:

<GoogleServicesJson Include="Platforms/Android/google-services.json" />

Add the following to your app-level build.gradle:

dependencies {
    implementation("com.appambit:appambit:1.0.2")
    implementation("com.appambit:appambit-push-notifications:1.0.2")

    // Required to align Firebase library versions.
    implementation(platform("com.google.firebase:firebase-bom:33.1.2"))
}
dependencies {
    implementation 'com.appambit:appambit:1.0.2'
    implementation 'com.appambit:appambit-push-notifications:1.0.2'

    // Required to align Firebase library versions.
    implementation platform('com.google.firebase:firebase-bom:33.1.2')
}

Ensure the Google Services plugin is applied in your project-level build.gradle.

Install both packages:

flutter pub add appambit_sdk_flutter
flutter pub add appambit_sdk_push_notifications

Add the Google Services plugin to your Gradle files:

android/app/build.gradle.kts

apply(plugin = "com.google.gms.google-services")

dependencies {
    implementation(platform("com.google.firebase:firebase-bom:33.1.2"))
    implementation("com.google.firebase:firebase-messaging:23.4.0")
}

android/build.gradle.kts

buildscript {
    repositories { google(); mavenCentral() }
    dependencies {
        classpath("com.google.gms:google-services:4.3.15")
    }
}

android/app/build.gradle

apply plugin: "com.google.gms.google-services"

dependencies {
    implementation platform('com.google.firebase:firebase-bom:33.1.2')
    implementation 'com.google.firebase:firebase-messaging:23.4.0'
}

android/build.gradle

buildscript {
    repositories { google(); mavenCentral() }
    dependencies {
        classpath "com.google.gms:google-services:4.3.15"
    }
}

Ensure minSdk = 24 in android/app/build.gradle — the plugin requires it.

Install both packages:

npm install appambit
npm install appambit-push-notifications

Add the Google Services plugin to your Gradle files:

android/app/build.gradle.kts

apply(plugin = "com.google.gms.google-services")

dependencies {
    implementation(platform("com.google.firebase:firebase-bom:33.1.2"))
    implementation("com.google.firebase:firebase-messaging:23.4.0")
}

android/build.gradle.kts

buildscript {
    dependencies {
        classpath("com.google.gms:google-services:4.3.15")
    }
}

android/app/build.gradle

apply plugin: "com.google.gms.google-services"

dependencies {
    implementation platform('com.google.firebase:firebase-bom:33.1.2')
    implementation 'com.google.firebase:firebase-messaging:23.4.0'
}

android/build.gradle

dependencies {
    classpath "com.google.gms:google-services:4.3.15"
}

Install both packages via NuGet:

dotnet add package com.AppAmbit.Avalonia --version 4.0.1
dotnet add package com.AppAmbit.PushNotifications --version 4.0.1

Place google-services.json under Platforms/Android/ and add the following to your .csproj:

<GoogleServicesJson Include="Platforms/Android/google-services.json" />

Setup

MauiProgram.cs

builder.UseMauiApp<App>().UseAppAmbit("<YOUR-APPKEY>");

Platforms/Android/MainActivity.cs

Register a notification customizer (optional) and call Start in OnCreate. Override OnNewIntent to handle background-to-foreground taps.

using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.OS;
using AppAmbit.PushNotifications;

[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true,
    LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ...)]
public class MainActivity : MauiAppCompatActivity
{
    protected override void OnCreate(Bundle? savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        PushNotifications.Start(this);
    }

    protected override void OnNewIntent(Intent? intent)
    {
        base.OnNewIntent(intent);
        Intent = intent;
        // Required for background-to-foreground taps.
        PushNotifications.Android.HandleNotificationOpened(intent);
    }
}

Register listeners from a shared page (e.g., MainPage.xaml.cs), after the MAUI Shell is ready. Do not register listeners in MainActivity.

public MainPage()
{
    InitializeComponent();

    PushNotifications.RequestNotificationPermission(callback: granted =>
    {
        if (granted) PushNotifications.SetNotificationsEnabled(true);
    });
}

Initialize both SDKs in your Application class or MainActivity. PushNotifications.start() must be called after AppAmbit.start().

import com.appambit.sdk.AppAmbit
import com.appambit.sdk.PushNotifications

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    AppAmbit.start(applicationContext, "<YOUR-APPKEY>")
    PushNotifications.start(applicationContext)

    // Required for cold-start and foreground taps.
    PushNotifications.handleNotificationOpened(this, intent)
}

override fun onNewIntent(intent: Intent) {
    super.onNewIntent(intent)
    setIntent(intent)
    // Required for background-to-foreground taps.
    PushNotifications.handleNotificationOpened(this, intent)
}
import com.appambit.sdk.AppAmbit;
import com.appambit.sdk.PushNotifications;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    AppAmbit.start(getApplicationContext(), "<YOUR-APPKEY>");
    PushNotifications.start(getApplicationContext());

    // Required for cold-start and foreground taps.
    PushNotifications.handleNotificationOpened(this, getIntent());
}

@Override
protected void onNewIntent(Intent intent) {
    super.onNewIntent(intent);
    setIntent(intent);
    // Required for background-to-foreground taps.
    PushNotifications.handleNotificationOpened(this, intent);
}

Note

If the app was killed and the user taps a notification, the SDK caches the payload internally. As soon as you register the opened listener via setOpenedListener, the pending notification is dispatched immediately.

Initialize both SDKs in main(), before calling runApp().

import 'package:appambit_sdk_flutter/appambit_sdk_flutter.dart';
import 'package:appambit_sdk_push_notifications/appambit_sdk_push_notifications.dart';

void main() {
    AppAmbitSdk.start(appKey: '<YOUR-APPKEY>');
    PushNotificationsSdk.start();
    PushNotificationsSdk.requestNotificationPermission();
    runApp(const MyApp());
}

Initialize both SDKs at your application entry point (App.tsx):

import * as AppAmbit from "appambit";
import * as PushNotifications from "appambit-push-notifications";

AppAmbit.start("<YOUR-APPKEY>");
PushNotifications.start();
PushNotifications.requestNotificationPermission();

Platforms/Android/MainActivity.cs

using Android.App;
using Android.Content.PM;
using Avalonia;
using Avalonia.Android;
using AppAmbit;
using AppAmbit.PushNotifications;

[Activity(MainLauncher = true, LaunchMode = LaunchMode.SingleTop,
    ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize | ConfigChanges.UiMode)]
public class MainActivity : AvaloniaMainActivity<App>
{
    protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
    {
        AppAmbitSdk.Start("<YOUR-APPKEY>");
        PushNotifications.Start(this);
        return base.CustomizeAppBuilder(builder);
    }

    protected override void OnNewIntent(Android.Content.Intent? intent)
    {
        base.OnNewIntent(intent);
        Intent = intent;
        PushNotifications.Android.HandleNotificationOpened(intent);
    }
}

Register listeners from your main view, after PushNotifications.Start(...) has run.


Android Manifest

The SDK automatically merges all required components into your app's AndroidManifest.xml via manifest merging. No manual entries are needed.

Component Purpose
POST_NOTIFICATIONS permission Declared automatically for Android 13+.

No service or receiver entries are required — the SDK handles all FCM routing internally.

Component Class Purpose
<service> MessagingService FCM message receiver. Handles all incoming push events.
<activity> PermissionRequestActivity Transparent helper for requesting POST_NOTIFICATIONS at runtime.
<receiver> NotificationOpenedReceiver Handles the notification tap intent and launches the app.
<uses-permission> POST_NOTIFICATIONS Declared automatically (API 33+).
Component Purpose
INTERNET Required for FCM communication.
POST_NOTIFICATIONS Runtime permission for Android 13+.
Firebase Messaging service Handles all incoming FCM messages.
Component Purpose
POST_NOTIFICATIONS Runtime permission for Android 13+.
RECEIVE_BOOT_COMPLETED Allows the SDK to recover state after device reboot.
AppAmbitInitProvider ContentProvider for automatic SDK initialization on app start.
AppAmbitHeadlessService Background service for killed-state notification handling (Headless JS).
Component Purpose
POST_NOTIFICATIONS permission Declared automatically for Android 13+.

No service or receiver entries are required — the SDK handles all FCM routing internally.


Event Listeners

The SDK provides separate callbacks for three notification lifecycle events.

Event When it fires
Foreground A notification arrives while the app is open.
Opened The user taps a notification banner (any app state).
Background A notification arrives while the app is backgrounded or killed.

Register all listeners from a shared page (e.g., MainPage.xaml.cs), after the MAUI Shell is ready.

// Fires when a notification arrives while the app is open.
PushNotifications.SetForegroundListener(data =>
    Debug.WriteLine($"[Foreground] {data.Title}"));

// Fires when the user taps a notification (any app state).
PushNotifications.SetOpenedListener(data =>
{
    // Shell is ready — safe to navigate.
    Shell.Current.GoToAsync("//TargetPage");
});

// Fires when a notification arrives while the app is backgrounded or killed.
PushNotifications.Android.SetBackgroundListener(data =>
    Debug.WriteLine($"[Background] {data.Title}"));

Notification tap delivery scenarios (Android):

App state Delivery mechanism
Foreground SetForegroundListener fires immediately.
Background OnNewIntentHandleNotificationOpenedSetOpenedListener fires.
Killed OnCreatePushNotifications.Start(this)SetOpenedListener fires.

Register listeners in onCreate, before calling handleNotificationOpened.

// Fires when the user taps a notification (any app state).
PushNotifications.setOpenedListener { notification ->
    Log.d(TAG, "Opened: ${notification.title}")
}

// Fires when a notification arrives while the app is in the foreground.
PushNotifications.setForegroundListener { notification ->
    Log.d(TAG, "Foreground: ${notification.title}")
}

// Fires when a notification arrives while the app is backgrounded or killed.
PushNotifications.setBackgroundListener { notification ->
    Log.d(TAG, "Background: ${notification.title}")
}
// Fires when the user taps a notification (any app state).
PushNotifications.setOpenedListener(notification -> {
    Log.d(TAG, "Opened: " + notification.getTitle());
});

// Fires when a notification arrives while the app is in the foreground.
PushNotifications.setForegroundListener(notification -> {
    Log.d(TAG, "Foreground: " + notification.getTitle());
});

// Fires when a notification arrives while the app is backgrounded or killed.
PushNotifications.setBackgroundListener(notification -> {
    Log.d(TAG, "Background: " + notification.getTitle());
});

Note

The background listener is called synchronously during FirebaseMessagingService.handleIntent(). Do not perform long-running work here. For heavy processing, use a WorkManager task from this callback.

Notification tap delivery scenarios:

App state Delivery mechanism
Foreground setForegroundListener fires immediately.
Background onNewIntenthandleNotificationOpenedsetOpenedListener fires.
Killed onCreatePushNotifications.start(applicationContext)setOpenedListener fires.

Listeners are singleton-scoped — registering a new listener replaces the previous one. Each returns a cleanup function to call in dispose().

@override
void initState() {
    super.initState();

    // Fires when a notification arrives while the app is open.
    _unsubscribeForeground = PushNotificationsSdk.setForegroundListener((payload) {
        print('Foreground: ${payload.title}');
    });

    // Fires when the user taps a notification (any app state).
    _unsubscribeOpened = PushNotificationsSdk.setOpenedListener((payload) {
        print('Tapped: ${payload.title}');
    });

    // Fires when a notification arrives while the app is backgrounded (Android only).
    // Runs in a separate isolate — no access to app state or UI.
    _unsubscribeBackground = PushNotificationsSdk.Android.setBackgroundListener((payload) async {
        print('Background: ${payload.title}');
    });
}

@override
void dispose() {
    _unsubscribeForeground?.call();
    _unsubscribeOpened?.call();
    _unsubscribeBackground?.call();
    super.dispose();
}

Background handler for killed app state

To receive notifications when the app is completely killed, register a top-level background handler before runApp():

@pragma('vm:entry-point')
void _backgroundHandler(PushNotificationData payload) {
    print('Killed state: ${payload.title}');
    // Must be a top-level or static function — not a closure or class method.
    // The @pragma annotation prevents tree-shaking by the Dart AOT compiler.
}

void main() {
    AppAmbitSdk.start(appKey: '<YOUR-APPKEY>');
    PushNotificationsSdk.start();
    PushNotificationsSdk.Android.setBackgroundHandler(_backgroundHandler);
    runApp(const MyApp());
}

Listeners hold a single subscription — registering again replaces the previous one. Each returns a cleanup function for use in useEffect.

useEffect(() => {
    // Fires when a notification arrives while the app is open.
    const unsubForeground = PushNotifications.setForegroundListener((payload) => {
        console.log("Foreground:", payload.title);
    });

    // Fires when the user taps a notification (any app state).
    const unsubOpened = PushNotifications.setOpenedListener((payload) => {
        console.log("Tapped:", payload.title);
    });

    // Fires when a notification arrives while the app is backgrounded (Android only).
    const unsubBackground = PushNotifications.Android.setBackgroundListener(async (payload) => {
        console.log("Background:", payload.title);
    });

    return () => {
        unsubForeground();
        unsubOpened();
        unsubBackground();
    };
}, []);

Headless JS task for killed app state

To handle notifications when the app is completely killed, register a Headless JS task in index.js:

import { AppRegistry, Platform } from 'react-native';
import * as PushNotifications from 'appambit-push-notifications';
import App from './src/App';
import { name as appName } from './app.json';

if (Platform.OS === 'android') {
    AppRegistry.registerHeadlessTask(
        PushNotifications.BACKGROUND_NOTIFICATION_TASK,
        () => async (payload) => {
            console.log('Killed state:', payload);
        }
    );
}

AppRegistry.registerComponent(appName, () => App);

Register all listeners from your main view, after PushNotifications.Start(...) has run in CustomizeAppBuilder.

// Fires when a notification arrives while the app is open.
PushNotifications.SetForegroundListener(data =>
    Debug.WriteLine($"[Foreground] {data.Title}"));

// Fires when the user taps a notification (any app state).
PushNotifications.SetOpenedListener(data =>
    Debug.WriteLine($"[Opened] {data.Title}"));

// Fires when a notification arrives while the app is backgrounded or killed.
PushNotifications.Android.SetBackgroundListener(data =>
    Debug.WriteLine($"[Background] {data.Title}"));

Notification Payload

Every listener callback receives a notification data object. The fields available depend on what was sent in the FCM payload.

Listeners receive a PushNotificationData object:

Property Type Platform Description
Title string? Both Notification title.
Body string? Both Notification body text.
ImageUrl string? Both URL of the attached image.
Data IDictionary<string, string>? Both Custom key-value pairs from the FCM data object.
Android AndroidPushData? Android only Android-specific extras. null on iOS.
Ios IosPushData? iOS only iOS APNs extras. null on Android.

AndroidPushData fields: Color, SmallIconName, ChannelId, Priority, Sound, ClickAction, Ticker, Visibility, Tag, Sticky.

PushNotifications.SetOpenedListener(data =>
{
    var title   = data.Title;
    var channel = data.Android?.ChannelId;
    var custom  = data.Data?["your_key"];
});

Listeners receive an AppAmbitNotification object:

Method Returns Description
getTitle() String? Notification title.
getBody() String? Notification body text.
getImageUrl() String? URL of the large notification image.
getData() Map<String, String> Custom key-value pairs from the FCM data object.
getColor() String? Accent color (e.g., #FF5722).
getSmallIconName() String? Drawable resource name for the small icon.
getChannelId() String? Notification channel ID.
getSound() String? Custom sound resource name or "default".
getTag() String? Tag for grouping or replacing notifications.
getClickAction() String? Intent action triggered on tap.
getPriority() String? Raw display priority string from the payload.
getTicker() String? Accessibility text shown in the status bar.
getVisibility() String? Lock-screen visibility value.
getSticky() Boolean? Whether the notification persists after being tapped.
PushNotifications.setOpenedListener { notification ->
    val title   = notification.title
    val data    = notification.data
    val channel = notification.channelId
    val custom  = data["your_key"]
}
PushNotifications.setOpenedListener(notification -> {
    String title   = notification.getTitle();
    Map<String, String> data = notification.getData();
    String custom  = data.get("your_key");
});

Listeners receive a PushNotificationData object:

class PushNotificationData {
    final String? title;
    final String? body;
    final String? imageUrl;
    final Map<String, String>? data;   // Custom key-value pairs
    final AndroidPushData? android;    // null on iOS
    final IosPushData? ios;            // null on Android
}

class AndroidPushData {
    final String? color;
    final String? smallIconName;
    final String? channelId;
    final String? sound;
    final String? clickAction;
    final String? tag;
    final String? ticker;
    final bool? sticky;
    final String? visibility;
}
_unsubscribeOpened = PushNotificationsSdk.setOpenedListener((payload) {
    print(payload.title);
    print(payload.android?.channelId);
    print(payload.data?['your_key']);
});

Listeners receive a NotificationPayload object:

interface NotificationPayload {
    title: string | null;
    body: string | null;
    imageUrl: string | null;
    data: Record<string, string>;   // Custom key-value pairs
    android: {
        color: string | null;
        smallIconName: string | null;
        channelId: string | null;
        sound: string | null;
        clickAction: string | null;
        tag: string | null;
        ticker: string | null;
        sticky: boolean | null;
        visibility: string | null;
    } | null;
    ios: {
        badge: number | null;
        sound: string | null;
        category: string | null;
        threadId: string | null;
    } | null;
}
const unsubscribe = PushNotifications.setOpenedListener((payload) => {
    console.log(payload.title);
    console.log(payload.android?.channelId);
    console.log(payload.data['your_key']);
});

Same PushNotificationData model as .NET MAUI (see the .NET MAUI tab above).


Permissions

On Android 13 (API 33) and higher, POST_NOTIFICATIONS must be requested at runtime. The SDK declares the permission automatically — no AndroidManifest.xml changes needed.

// Without a callback
PushNotifications.RequestNotificationPermission(this);

// With a callback
PushNotifications.RequestNotificationPermission(callback: granted =>
{
    if (granted) PushNotifications.SetNotificationsEnabled(true);
});
// Without a callback
PushNotifications.requestNotificationPermission(this)

// With a callback
PushNotifications.requestNotificationPermission(this) { isGranted ->
    if (isGranted) {
        Log.d(TAG, "Permission granted.")
    } else {
        Log.w(TAG, "Permission denied.")
    }
}
// Without a callback
PushNotifications.requestNotificationPermission(getApplicationContext());

// With a callback
PushNotifications.requestNotificationPermission(getApplicationContext(), isGranted -> {
    if (isGranted) {
        Log.d(TAG, "Permission granted.");
    } else {
        Log.w(TAG, "Permission denied.");
    }
});

Note

On Android 12 (API 32) and below, the dialog is not shown. The callback fires immediately with isGranted = true.

// Show the system dialog (fire-and-forget)
PushNotificationsSdk.requestNotificationPermission();

// Show the dialog and get the result
final granted = await PushNotificationsSdk.requestNotificationPermissionWithResult();
if (granted) {
    print('Permission granted.');
}

// Check without prompting
final hasPermission = await PushNotificationsSdk.hasNotificationPermission();
// Show the system dialog (fire-and-forget)
PushNotifications.requestNotificationPermission();

// Show the dialog and get the result
const granted = await PushNotifications.requestNotificationPermissionWithResult();

// Check without prompting
const hasPermission = await PushNotifications.hasNotificationPermission();
PushNotifications.RequestNotificationPermission(new PermissionListener(granted =>
{
    if (granted) PushNotifications.SetNotificationsEnabled(true);
}));

class PermissionListener : PushNotifications.IPermissionListener
{
    private readonly Action<bool> _onResult;
    public PermissionListener(Action<bool> onResult) => _onResult = onResult;
    public void OnPermissionResult(bool isGranted) => _onResult(isGranted);
}

Enable and Disable Notifications

By default, notifications are enabled when you first call start(). Use this toggle to manage user preferences independently of the OS permission. A device shows notifications only when both the OS permission is granted and the SDK toggle is enabled.

Disabling notifications deletes the FCM token from the device and updates the opt-in status on the AppAmbit backend. Re-enabling fetches a new token and re-syncs.

// Disable (removes FCM token + updates backend)
PushNotifications.SetNotificationsEnabled(false);

// Enable again
PushNotifications.SetNotificationsEnabled(true);

// Check current state
bool isEnabled = PushNotifications.IsNotificationsEnabled();
PushNotifications.setNotificationsEnabled(context, false)
PushNotifications.setNotificationsEnabled(context, true)

val isEnabled = PushNotifications.isNotificationsEnabled(context)
PushNotifications.setNotificationsEnabled(context, false);
PushNotifications.setNotificationsEnabled(context, true);

boolean isEnabled = PushNotifications.isNotificationsEnabled(getApplicationContext());
PushNotificationsSdk.setNotificationsEnabled(false);
PushNotificationsSdk.setNotificationsEnabled(true);

final isEnabled = await PushNotificationsSdk.isNotificationsEnabled();
PushNotifications.setNotificationsEnabled(false);
PushNotifications.setNotificationsEnabled(true);

const isEnabled = await PushNotifications.isNotificationsEnabled();
PushNotifications.SetNotificationsEnabled(false);
PushNotifications.SetNotificationsEnabled(true);

bool isEnabled = PushNotifications.IsNotificationsEnabled();

Customization

The SDK automatically configures the notification by reading standard fields from the FCM payload. For most use cases no custom code is needed.

Supported FCM payload fields (applied automatically):

Field Description
title / body Notification title and text.
icon Drawable resource name for the small icon.
color Accent color in hex (e.g., #FF5722).
image / image_url URL for the BigPicture expanded image.
channel_id / android_channel_id Notification channel (Android 8+).
sound Custom sound resource name or "default".
notification_priority Display priority: integer (-22) or string ("high", "low", etc.).
click_action Intent action for notification tap.
ticker Accessibility text shown in the status bar.
visibility Lock-screen visibility ("public", "private", "secret").
tag Notifications with the same tag replace each other.
sticky When "true", the notification is not dismissed when tapped.

The data object is a free-form container for any custom key-value pairs. They are available in every listener callback and in the NotificationCustomizer via notification.getData().

Example FCM payload:

{
    "notification": {
        "title": "New Message",
        "body": "You have a new message from a friend."
    },
    "data": {
        "action_label": "Mark as Read",
        "action_filter": "MARK_AS_READ_ACTION",
        "screen": "chat"
    }
}

For advanced modifications, register a NotificationCustomizer. It is called after all built-in fields have been applied, giving you full control over the NotificationCompat.Builder.

class MyNotificationCustomizer : PushNotifications.INotificationCustomizer
{
    public void Customize(object context, object builder, PushNotificationData notification)
    {
        // Use dynamic to avoid iOS/Windows compile errors in cross-targeted projects.
        dynamic b = builder;
        b.SetColor(unchecked((int)0xFF0066FF));

        // Example: add an action button from custom data keys.
        if (notification.Data != null &&
            notification.Data.TryGetValue("action_label", out var label) &&
            notification.Data.TryGetValue("action_filter", out var action))
        {
            // ... build and add PendingIntent action
        }
    }
}

// Register in Platforms/Android/MainActivity.cs OnCreate, before base.OnCreate:
PushNotifications.Android.SetNotificationCustomizer(new MyNotificationCustomizer());
PushNotifications.setNotificationCustomizer { context, builder, notification ->
    val data = notification.data

    val actionLabel  = data["action_label"]
    val actionFilter = data["action_filter"]

    if (actionLabel != null && actionFilter != null) {
        val intent = Intent(actionFilter)
        val pending = PendingIntent.getBroadcast(
            context, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )
        builder.addAction(0, actionLabel, pending)
    }
}
PushNotifications.setNotificationCustomizer((context, builder, notification) -> {
    Map<String, String> data = notification.getData();

    String actionLabel  = data.get("action_label");
    String actionFilter = data.get("action_filter");

    if (actionLabel != null && actionFilter != null) {
        Intent intent = new Intent(actionFilter);
        PendingIntent pending = PendingIntent.getBroadcast(
            context, 0, intent,
            PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE
        );
        builder.addAction(0, actionLabel, pending);
    }
});

Flutter does not expose a notification customizer for Android. Use the background listener to react to incoming notifications and the standard FCM payload fields for appearance control.

React Native does not expose a notification customizer for Android. Use the background listener to react to incoming notifications and the standard FCM payload fields for appearance control.

class MyNotificationCustomizer : PushNotifications.INotificationCustomizer
{
    public void Customize(object context, object builder, PushNotificationData notification)
    {
        dynamic b = builder;
        b.SetColor(unchecked((int)0xFF0066FF));
    }
}

// Register in Platforms/Android/MainActivity.cs CustomizeAppBuilder, before base.CustomizeAppBuilder:
PushNotifications.Android.SetNotificationCustomizer(new MyNotificationCustomizer());

iOS (APNs)

AppAmbit provides a comprehensive iOS push notifications SDK that integrates seamlessly with the AppAmbit Core SDK. It uses Apple Push Notification service (APNs) for token management and supports both SwiftUI and UIKit apps.

Prerequisites

If you haven't yet configured your environment on the AppAmbit platform to start sending Push Notifications, go to the next page.

Requirements

  • AppAmbit Core SDK (com.AppAmbit.Maui)
  • .NET 10 targeting net10.0-ios
  • iOS 12.0 or newer
  • Push Notifications capability enabled on your app target

Note

The native iOS frameworks (AppAmbit.framework and AppAmbitPushNotifications.framework) are bundled inside the NuGet package. No CocoaPods step is required.

  • AppAmbit Core SDK (AppAmbitSdk)
  • iOS 12.0 or newer
  • Xcode 15 or newer
  • Swift 5.7 or newer
  • Push Notifications capability enabled on your app target
  • AppAmbit Core SDK (appambit_sdk_flutter)
  • iOS 13.0 or newer
  • Push Notifications capability enabled on the Runner target
  • AppAmbit Core SDK (appambit)
  • iOS 13.0 or newer
  • Push Notifications capability enabled on the Runner target
  • AppAmbit Core SDK (com.AppAmbit.Avalonia)
  • .NET 10 targeting net10.0-ios
  • iOS 12.0 or newer
  • Push Notifications capability enabled on your app target

Note

The native iOS frameworks are bundled inside the NuGet package. No CocoaPods step is required.


Install

Same NuGet packages as Android — no separate iOS install step needed:

dotnet add package com.AppAmbit.Maui --version 4.0.1
dotnet add package com.AppAmbit.PushNotifications --version 4.0.1

The native iOS frameworks are bundled inside the NuGet package. No CocoaPods step is required.

Swift Package Manager

In Xcode, go to File → Add Packages… and add both packages to your app target:

  • AppAmbitSdk — core SDK
  • AppAmbitPushNotifications — push notifications

CocoaPods

Add both pods to your Podfile:

pod 'AppAmbitSdk'
pod 'AppAmbitPushNotifications', '~> 1.0.2'

Then run:

pod install

Note

If you see Unable to find a specification for 'AppAmbitPushNotifications', run pod repo update and then pod install again.

Install both packages:

flutter pub add appambit_sdk_flutter
flutter pub add appambit_sdk_push_notifications

Then install the iOS pods:

cd ios && pod install

No manual pod entries needed — the SDK resolves automatically as a dependency of the plugin.

Install both packages:

npm install appambit
npm install appambit-push-notifications

Then install the iOS pods:

cd ios && pod install

Same NuGet packages as Android — no separate iOS install step needed:

dotnet add package com.AppAmbit.Avalonia --version 4.0.1
dotnet add package com.AppAmbit.PushNotifications --version 4.0.1

The native iOS frameworks are bundled inside the NuGet package. No CocoaPods step is required.


Setup

Enable Push Notifications Capability

The aps-environment entitlement is required for APNs device registration. Without it, no token is delivered to the app.

In Xcode: select your app targetSigning & Capabilities+ CapabilityPush Notifications.

If you manage the entitlements file manually (e.g., in CI or version control), ensure the following key is present in Runner.entitlements:

<key>aps-environment</key>
<string>development</string>  <!-- use "production" for App Store / TestFlight builds -->

Also verify that Code Signing Entitlements in Build Settings points to Runner/Runner.entitlements for both Debug and Release configurations.

Platform initialization

Platforms/iOS/AppDelegate.cs

Call Start and HandleLaunchOptions here. Do not register listeners in AppDelegate — register them from a shared page after the MAUI Shell is ready.

using Foundation;
using UIKit;
using AppAmbit.PushNotifications;

[Register("AppDelegate")]
public class AppDelegate : MauiUIApplicationDelegate
{
    protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp();

    public override bool FinishedLaunching(UIApplication app, NSDictionary options)
    {
        var result = base.FinishedLaunching(app, options);
        PushNotifications.Start();
        // Required for cold-start taps: buffers the payload and dispatches
        // it to SetOpenedListener as soon as the listener is registered.
        PushNotifications.HandleLaunchOptions(options);
        return result;
    }
}

Register listeners from a shared page (e.g., MainPage.xaml.cs):

public MainPage()
{
    InitializeComponent();

    PushNotifications.SetForegroundListener(data => { /* ... */ });
    PushNotifications.SetOpenedListener(data =>
    {
        Shell.Current.GoToAsync("//TargetPage");
    });

    PushNotifications.RequestNotificationPermission(callback: granted =>
    {
        if (granted) PushNotifications.SetNotificationsEnabled(true);
    });
}

SwiftUI

Use AppAmbitAppDelegate so the SDK can intercept APNs delegate calls automatically via method swizzling:

import SwiftUI
import AppAmbit
import AppAmbitPushNotifications

@main
struct MyApp: App {
    @UIApplicationDelegateAdaptor(AppAmbitAppDelegate.self) var appDelegate

    init() {
        PushNotifications.start()
        AppAmbit.start(appKey: "<YOUR-APPKEY>")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Then request permission:

PushNotifications.requestNotificationPermission { granted in
    if granted {
        PushNotifications.setNotificationsEnabled(true)
    }
}

UIKit

For UIKit apps, no AppDelegate changes are required — the SDK wires itself up via method swizzling.

import AppAmbit
import AppAmbitPushNotifications

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    AppAmbit.start(appKey: "<YOUR-APPKEY>")
    PushNotifications.start()
    return true
}

Objective-C

@import AppAmbit;
@import AppAmbitPushNotifications;

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [AppAmbit start:@"<YOUR-APPKEY>"];
    [PushNotifications start];
    return YES;
}

Initialize both SDKs in main(), before calling runApp().

import 'package:appambit_sdk_flutter/appambit_sdk_flutter.dart';
import 'package:appambit_sdk_push_notifications/appambit_sdk_push_notifications.dart';

void main() {
    AppAmbitSdk.start(appKey: '<YOUR-APPKEY>');
    PushNotificationsSdk.start();
    PushNotificationsSdk.requestNotificationPermission();
    runApp(const MyApp());
}

Initialize both SDKs at your application entry point (App.tsx):

import * as AppAmbit from "appambit";
import * as PushNotifications from "appambit-push-notifications";

AppAmbit.start("<YOUR-APPKEY>");
PushNotifications.start();
PushNotifications.requestNotificationPermission();

Platforms/iOS/AppDelegate.cs

Cold-start limitation

CustomizeAppBuilder does not receive launch options, so cold-start taps (app fully terminated) are not supported via HandleLaunchOptions in Avalonia iOS. Foreground and background-tap scenarios work normally.

using Avalonia;
using Avalonia.iOS;
using AppAmbit;
using AppAmbit.PushNotifications;

[Register("AppDelegate")]
public partial class AppDelegate : AvaloniaAppDelegate<App>
{
    protected override AppBuilder CustomizeAppBuilder(AppBuilder builder)
    {
        AppAmbitSdk.Start("<YOUR-APPKEY>");
        PushNotifications.Start();
        return base.CustomizeAppBuilder(builder).WithInterFont();
    }
}

Event Listeners

Listeners are registered from a shared page, after the MAUI Shell is ready (see Setup above).

// Fires when a notification arrives while the app is open.
PushNotifications.SetForegroundListener(data =>
    Debug.WriteLine($"[Foreground] {data.Title}"));

// Fires when the user taps a notification (any app state).
PushNotifications.SetOpenedListener(data =>
{
    // Shell is ready — safe to navigate.
    Shell.Current.GoToAsync("//TargetPage");
});

Note

PushNotifications.Android.SetBackgroundListener is not available on iOS. Use the Notification Service Extension to intercept and process notifications before they are displayed.

Notification tap delivery scenarios (iOS):

App state Delivery mechanism
Foreground SetForegroundListener fires immediately.
Background Notification delegate → SetOpenedListener fires.
Killed HandleLaunchOptions buffers payload → SetOpenedListener fires once registered.

iOS uses a single setNotificationListener callback instead of separate foreground/opened listeners. The state parameter tells you how the notification was received.

State When it fires Typical use
.foreground Notification arrives while the app is open Show in-app banner, update UI
.opened User taps the notification banner Navigate to the relevant screen

Swift

PushNotifications.setNotificationListener { userInfo, state in
    switch state {
    case .foreground:
        // App was open when the notification arrived.
        // Show an in-app banner or update the UI.
        print("Foreground: \(userInfo)")

    case .opened:
        // User tapped the notification.
        // Navigate to the relevant screen.
        print("Opened: \(userInfo)")

    @unknown default:
        break
    }
}

Objective-C

[PushNotifications setNotificationListener:^(NSDictionary *userInfo,
                                              PushNotificationState state) {
    switch (state) {
        case PushNotificationStateForeground:
            NSLog(@"Foreground: %@", userInfo);
            break;
        case PushNotificationStateOpened:
            NSLog(@"Opened: %@", userInfo);
            break;
    }
}];

Note

There is no background listener on iOS. Use the Notification Service Extension to intercept and process notifications before they are displayed — it runs in a separate process regardless of app state.

Notification tap delivery scenarios (iOS):

App state Delivery mechanism
Foreground Listener fires with .foreground state immediately.
Background Notification delegate → listener fires with .opened state when tapped.
Killed SDK captures the tap on launch → listener fires with .opened state once registered.

Listeners are singleton-scoped — registering a new listener replaces the previous one. Each returns a cleanup function to call in dispose().

@override
void initState() {
    super.initState();

    // Fires when a notification arrives while the app is open.
    _unsubscribeForeground = PushNotificationsSdk.setForegroundListener((payload) {
        print('Foreground: ${payload.title}');
    });

    // Fires when the user taps a notification (any app state).
    _unsubscribeOpened = PushNotificationsSdk.setOpenedListener((payload) {
        print('Tapped: ${payload.title}');
    });
}

@override
void dispose() {
    _unsubscribeForeground?.call();
    _unsubscribeOpened?.call();
    super.dispose();
}

Note

There is no background Dart callback on iOS. Use the Notification Service Extension to intercept and process notifications before they are displayed.

Notification tap delivery scenarios (iOS):

App state Delivery mechanism
Foreground setForegroundListener fires immediately.
Background Notification delegate → setOpenedListener fires.
Killed SDK buffers payload → setOpenedListener fires once registered.

Listeners hold a single subscription — registering again replaces the previous one. Each returns a cleanup function for use in useEffect.

useEffect(() => {
    // Fires when a notification arrives while the app is open.
    const unsubForeground = PushNotifications.setForegroundListener((payload) => {
        console.log("Foreground:", payload.title);
    });

    // Fires when the user taps a notification (any app state).
    const unsubOpened = PushNotifications.setOpenedListener((payload) => {
        console.log("Tapped:", payload.title);
    });

    return () => {
        unsubForeground();
        unsubOpened();
    };
}, []);

Note

There is no background JS callback on iOS. Use the Notification Service Extension to intercept and process notifications before they are displayed.

Notification tap delivery scenarios (iOS):

App state Delivery mechanism
Foreground setForegroundListener fires immediately.
Background Notification delegate → setOpenedListener fires.
Killed SDK buffers payload → setOpenedListener fires once registered.

Listeners are registered from the main view, after PushNotifications.Start() has run in CustomizeAppBuilder.

// Fires when a notification arrives while the app is open.
PushNotifications.SetForegroundListener(data =>
    Debug.WriteLine($"[Foreground] {data.Title}"));

// Fires when the user taps a notification (any app state).
PushNotifications.SetOpenedListener(data =>
    Debug.WriteLine($"[Opened] {data.Title}"));

Note

PushNotifications.Android.SetBackgroundListener is not available on iOS. Use the Notification Service Extension to intercept and process notifications before they are displayed.

Notification tap delivery scenarios (iOS):

App state Delivery mechanism
Foreground SetForegroundListener fires immediately.
Background Notification delegate → SetOpenedListener fires.
Killed Not supported — CustomizeAppBuilder does not receive launch options. See the cold-start limitation warning in Setup.

Enable and Disable Notifications

PushNotifications.SetNotificationsEnabled(false);
PushNotifications.SetNotificationsEnabled(true);

bool isEnabled = PushNotifications.IsNotificationsEnabled();
// Disable (unregisters from APNs and syncs with backend)
PushNotifications.setNotificationsEnabled(false)

// Enable (registers with APNs and syncs with backend)
PushNotifications.setNotificationsEnabled(true)

// Check current state
let isEnabled = PushNotifications.isNotificationsEnabled()
[PushNotifications setNotificationsEnabled:NO];
[PushNotifications setNotificationsEnabled:YES];
BOOL isEnabled = [PushNotifications isNotificationsEnabled];
PushNotificationsSdk.setNotificationsEnabled(false);
PushNotificationsSdk.setNotificationsEnabled(true);

final isEnabled = await PushNotificationsSdk.isNotificationsEnabled();
PushNotifications.setNotificationsEnabled(false);
PushNotifications.setNotificationsEnabled(true);

const isEnabled = await PushNotifications.isNotificationsEnabled();
PushNotifications.SetNotificationsEnabled(false);
PushNotifications.SetNotificationsEnabled(true);

bool isEnabled = PushNotifications.IsNotificationsEnabled();

Notification Service Extension

A Notification Service Extension (NSE) intercepts every notification before it is displayed, running in a separate process regardless of whether the app is in the foreground, background, or force-killed. Use it to:

  • Receive notification data — the handlePayload hook gives you typed access to the full payload, including any custom keys you sent from the server.
  • Process the payload — run analytics, data sync, or local state updates the moment a notification arrives, even when the app is not running.
  • Mutate the content — change the title, body, or category before the banner appears.

The SDK's base class handles rich image download and attachment automatically — you do not need to write any code for image support.

NSE process constraints imposed by iOS:

  • ~30-second time limit
  • ~24 MB memory limit
  • No access to your app's state, UI, or data

The minimum implementation is a single class. The base class handles payload parsing and image download automatically. Override handlePayload only if you need to customize behavior.

using AppAmbit.PushNotifications;

[Register("NotificationService")]
public class NotificationService : AppAmbitNotificationServiceExtension { }

To access and mutate the notification before display:

[Register("NotificationService")]
public class NotificationService : AppAmbitNotificationServiceExtension
{
    protected override void HandlePayload(
        AppAmbitNotificationData notification,
        UNMutableNotificationContent content)
    {
        // notification.Title, notification.Body, notification.ImageUrl, notification.Data
        content.Title = $"[{notification.Title}]";
    }
}

For project setup (extension target, Info.plist, project reference), see Full Setup below.

import AppAmbitPushNotifications

final class NotificationService: AppAmbitNotificationService {}

To access and mutate the notification before display:

import AppAmbitPushNotifications
import UserNotifications

final class NotificationService: AppAmbitNotificationService {

    override func handlePayload(_ notification: AppAmbitNotification,
                                content: UNMutableNotificationContent) {
        // notification.title, notification.body, notification.imageUrl, notification.data
        content.title = "[\(content.title)]"
    }
}

For target setup in Xcode, see Full Setup below.

import AppAmbitPushNotificationsExtension

class NotificationService: AppAmbitNotificationService {}

To access and mutate the notification before display:

import AppAmbitPushNotificationsExtension
import UserNotifications

class NotificationService: AppAmbitNotificationService {
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        // Always call super — it handles payload parsing and image download.
        super.didReceive(request, withContentHandler: contentHandler)
    }
}

For target setup in Xcode and Podfile configuration, see Full Setup below.

import AppAmbitPushNotificationsExtension

class NotificationService: AppAmbitNotificationService {}

To access and mutate the notification before display:

import AppAmbitPushNotificationsExtension
import UserNotifications

class NotificationService: AppAmbitNotificationService {
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        super.didReceive(request, withContentHandler: contentHandler)
    }
}

For target setup in Xcode and Podfile configuration, see Full Setup below.

using AppAmbit.PushNotifications;

[Register("NotificationService")]
public class NotificationService : AppAmbitNotificationServiceExtension { }

To access and mutate the notification before display:

[Register("NotificationService")]
public class NotificationService : AppAmbitNotificationServiceExtension
{
    protected override void HandlePayload(
        AppAmbitNotificationData notification,
        UNMutableNotificationContent content)
    {
        content.Title = $"[{notification.Title}]";
    }
}

Follow the same project setup as .NET MAUI. See Full Setup below.


Full Setup

1. Create the NSE project

Add a net10.0-ios app-extension project and reference com.AppAmbit.PushNotifications:

YourApp.NotificationExtension.csproj

<Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFramework>net10.0-ios</TargetFramework>
        <IsAppExtension>true</IsAppExtension>
        <SupportedOSPlatformVersion>12.0</SupportedOSPlatformVersion>
        <ApplicationId>com.yourapp.NotificationExtension</ApplicationId>
    </PropertyGroup>
    <ItemGroup>
        <PackageReference Include="com.AppAmbit.PushNotifications" Version="*" />
    </ItemGroup>
</Project>

Info.plist

<key>NSExtension</key>
<dict>
    <key>NSExtensionPointIdentifier</key>
    <string>com.apple.usernotifications.service</string>
    <key>NSExtensionPrincipalClass</key>
    <string>NotificationService</string>
</dict>

2. Reference the NSE from your iOS app project

<ItemGroup Condition="'$(TargetFramework)' == 'net10.0-ios'">
    <ProjectReference Include="..\YourApp.NotificationExtension\YourApp.NotificationExtension.csproj">
        <IsAppExtension>true</IsAppExtension>
    </ProjectReference>
</ItemGroup>

3. Implement the service class

Minimal:

using AppAmbit.PushNotifications;

[Register("NotificationService")]
public class NotificationService : AppAmbitNotificationServiceExtension { }

With payload mutation:

using AppAmbit.PushNotifications;
using UserNotifications;

[Register("NotificationService")]
public class NotificationService : AppAmbitNotificationServiceExtension
{
    protected override void HandlePayload(
        AppAmbitNotificationData notification,
        UNMutableNotificationContent content)
    {
        content.Title = $"{notification.Title} ✦";
        // notification.Body, notification.ImageUrl, notification.Data also available.
    }

    // Called when iOS is about to terminate the extension (~30 s limit).
    protected override void OnTimeExpiring() { }
}

1. Add the NSE target in Xcode

Go to File > New > Target > Notification Service Extension. Name it (e.g., NotificationService) and activate the scheme when prompted.

2. Add the package to the NSE target

Add AppAmbitPushNotifications to the extension target (not just the app target). In Xcode: App target → General → Frameworks, Libraries, and Embedded Content → + and add the .appex.

3. Implement the service

Swift — Minimal:

import AppAmbitPushNotifications

final class NotificationService: AppAmbitNotificationService {}

Swift — With payload mutation:

import AppAmbitPushNotifications
import UserNotifications

final class NotificationService: AppAmbitNotificationService {

    override func handlePayload(_ notification: AppAmbitNotification,
                                content: UNMutableNotificationContent) {
        content.title = "[\(content.title)]"
        // notification.title, notification.body, notification.imageUrl available
    }

    override func serviceExtensionTimeWillExpire() {
        super.serviceExtensionTimeWillExpire()
    }
}

Objective-C:

Swift classes from an SPM dynamic library cannot be subclassed from Objective-C. Use AppAmbitNotificationProcessor instead:

#import <UserNotifications/UserNotifications.h>
@import AppAmbitPushNotifications;

@interface NotificationService : UNNotificationServiceExtension
@end

@implementation NotificationService

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request
                   withContentHandler:(void (^)(UNNotificationContent *))contentHandler {
    [AppAmbitNotificationProcessor processRequest:request
                                   contentHandler:contentHandler
                                    handlePayload:^(AppAmbitNotification *notification,
                                                    UNMutableNotificationContent *content) {
        content.title = [content.title stringByAppendingString:@" [Custom]"];
    }];
}

- (void)serviceExtensionTimeWillExpire {
    // AppAmbitNotificationProcessor handles timeout internally.
}

@end

1. Add the NSE target in Xcode

Open ios/Runner.xcworkspace, then go to File > New > Target > Notification Service Extension. Name it NotificationService.

2. Add the pod to the NSE target

In your Podfile, add the extension target outside the main Runner target block:

target 'NotificationService' do
    pod 'AppAmbitPushNotificationsExtension', '~> 1.0.0'
end

Then run:

cd ios && pod install

3. Implement NotificationService.swift

Minimal:

import UserNotifications
import AppAmbitPushNotificationsExtension

class NotificationService: AppAmbitNotificationService {}

With payload mutation:

import UserNotifications
import AppAmbitPushNotificationsExtension

class NotificationService: AppAmbitNotificationService {
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        // Always call super — it handles image download and attachment.
        super.didReceive(request, withContentHandler: contentHandler)
    }

    override func serviceExtensionTimeWillExpire() {
        super.serviceExtensionTimeWillExpire()
    }
}

1. Add the NSE target in Xcode

Open ios/Runner.xcworkspace, then go to File > New > Target > Notification Service Extension. Name it NotificationService.

2. Add the pod to the NSE target

In your Podfile, add the extension target outside the main target block:

target 'NotificationService' do
    pod 'AppAmbitPushNotificationsExtension', '~> 1.0.0'
end

Then run:

cd ios && pod install

3. Implement NotificationService.swift

import UserNotifications
import AppAmbitPushNotificationsExtension

class NotificationService: AppAmbitNotificationService {
    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        super.didReceive(request, withContentHandler: contentHandler)
    }

    override func serviceExtensionTimeWillExpire() {
        super.serviceExtensionTimeWillExpire()
    }
}

Follow the same steps as .NET MAUI (see the .NET MAUI tab above). The NSE project setup and C# implementation are identical.


Notification Payload

APNs payload fields

These fields are sent from the AppAmbit platform and parsed by the SDK automatically.

Field APNs key Description
Title aps.alert.title Notification title.
Body aps.alert.body Notification body text.
Sound aps.sound Play sound ("default" or custom filename).
Badge aps.badge App icon badge number.
Category aps.category Action buttons (requires app-side UNNotificationCategory registration).
Thread ID aps.thread-id Groups multiple notifications together.
Image URL Custom key (e.g., image) Requires the Notification Service Extension and mutable-content: 1.
Custom data Any key outside aps Available in listener callbacks and AppAmbitNotification.data.

Example APNs payload:

{
    "aps": {
        "alert": {
            "title": "New Message",
            "body": "You have a new message."
        },
        "mutable-content": 1,
        "sound": "default",
        "badge": 1,
        "thread-id": "messages"
    },
    "image": "https://example.com/image.jpg",
    "screen": "chat",
    "user_id": "123"
}

Received data model

The data your listener callback receives depends on the platform.

Listeners receive a PushNotificationData object. The Ios sub-object maps APNs aps fields.

Property Type Description
Title string? Notification title (aps.alert.title).
Body string? Notification body (aps.alert.body).
ImageUrl string? URL of the attached image.
Data IDictionary<string, string>? All custom keys outside aps.
Ios IosPushData? APNs-specific fields. null on Android.

IosPushData fields: Badge, Sound, ThreadId, Category.

PushNotifications.SetOpenedListener(data =>
{
    var title    = data.Title;
    var badge    = data.Ios?.Badge;
    var threadId = data.Ios?.ThreadId;
    var custom   = data.Data?["your_key"];
});

The listener receives the raw APNs userInfo dictionary ([AnyHashable: Any]).

PushNotifications.setNotificationListener { userInfo, state in
    // Access standard APNs fields
    let aps     = userInfo["aps"] as? [String: Any]
    let alert   = aps?["alert"] as? [String: Any]
    let title   = alert?["title"] as? String
    let badge   = aps?["badge"] as? Int

    // Access custom fields
    let screen  = userInfo["screen"] as? String
    let userId  = userInfo["user_id"] as? String
}

Inside the Notification Service Extension, the AppAmbitNotification model provides typed access:

Property Type Description
title String? Notification title.
body String? Notification body.
imageUrl String? Image URL from the custom payload key.
data [String: Any] All custom keys outside aps.

Listeners receive a PushNotificationData object. The ios sub-object maps APNs aps fields.

class PushNotificationData {
    final String? title;
    final String? body;
    final String? imageUrl;
    final Map<String, String>? data;   // Custom keys outside aps
    final IosPushData? ios;            // null on Android
    final AndroidPushData? android;    // null on iOS
}

class IosPushData {
    final int? badge;
    final String? sound;
    final String? category;
    final String? threadId;
}
_unsubscribeOpened = PushNotificationsSdk.setOpenedListener((payload) {
    print(payload.title);
    print(payload.ios?.badge);
    print(payload.ios?.threadId);
    print(payload.data?['your_key']);
});

Listeners receive a NotificationPayload object. The ios field maps APNs aps fields.

interface NotificationPayload {
    title: string | null;
    body: string | null;
    imageUrl: string | null;
    data: Record<string, string>;   // Custom keys outside aps
    ios: {
        badge: number | null;
        sound: string | null;
        category: string | null;
        threadId: string | null;
    } | null;
    android: { ... } | null;        // null on iOS
}
const unsubscribe = PushNotifications.setOpenedListener((payload) => {
    console.log(payload.title);
    console.log(payload.ios?.badge);
    console.log(payload.ios?.threadId);
    console.log(payload.data['your_key']);
});

Listeners receive a PushNotificationData object. The Ios sub-object maps APNs aps fields.

Property Type Description
Title string? Notification title (aps.alert.title).
Body string? Notification body (aps.alert.body).
ImageUrl string? URL of the attached image.
Data IDictionary<string, string>? All custom keys outside aps.
Ios IosPushData? APNs-specific fields. null on Android.

IosPushData fields: Badge, Sound, ThreadId, Category.

PushNotifications.SetOpenedListener(data =>
{
    var title    = data.Title;
    var badge    = data.Ios?.Badge;
    var threadId = data.Ios?.ThreadId;
    var custom   = data.Data?["your_key"];
});

Differences from Android

Feature Android iOS
Token type FCM Token APNs Device Token
Permissions POST_NOTIFICATIONS (Android 13+) User Notifications authorization
Background Dart/JS callback Supported Not supported
State storage SharedPreferences UserDefaults