Push Notifications with AppsOnAir: Native Android SDK Setup

Editorial team
Dot
November 2, 2026
Push notifications with AppsOnAir native Android SDK setup – connect Firebase, add the SDK, initialize push and verify with a test notification

A complete setup guide for integrating AppsOnAir push notifications into native Android apps, covering Firebase configuration, initialization, targeting, and production edge cases.

‍

Overview

The AppsOnAir Android SDK sits on top of Firebase Cloud Messaging (FCM) and surfaces a unified API for token management, segmentation, opt-in control, and delivery analytics. Push targeting maps directly to the same distribution groups and app version segments already defined in the AppsOnAir console.

‍

Before starting: upload your Firebase service account JSON in the AppsOnAir console under App Settings → Push Notifications. The SDK will not deliver without credentials configured server-side.

‍

‍

Requirements

Requirement Minimum
Android API 24 (Android 7.0)
Kotlin 1.8+
JVM target 17
Firebase google-services.json required

‍

Basic push  (Steps 1-5) covers: text notifications with title and body, tap-to-open and deep links, images (automatic via data payload), foreground display control, user targeting, tags, segments, and opt-in/out.

Advanced features are all handled by the SDK with no separate targets: images via `big_picture` data key, notification channels (Android 8.0+), silent push, and badge count.

 

‍

AppsOnAir Push Configuration 

Before starting SDK integration, I need a one-time push notifications configuration setup for the android platform. 

Credential configuration is a one-time setup following items needed. 

  • Android Package name.
  • Firebase Google Services Account Json on file for Android

For more details you visit documentation. 

‍

‍



If you want to migrate your Existing OneSignal Push notification to AppsOnAir Push notification service you need export data from OneSignal Dashboard and received via email. Import all you existing data in AppsOnAir web portal under push notification section during first time setup as well as under devices section just tap on import devices CTA

To migrate your existing OneSignal push notification setup to AppsOnAir Push Notifications, export your existing data from the OneSignal Dashboard. You’ll receive the exported data via email.

Once you have the data, import it into the AppsOnAir web portal using either of these options:

  • During the initial setup: Import your existing data in the Push Notifications section.
  • Anytime later: Navigate to the Devices section and click the Import Devices CTA to import your existing device data.

‍

For more information visit OneSignal migration guide.

‍

‍

Step 1: Firebase Setup

‍

1. Open the Firebase Console and add your Android app.

2. Download google-services.json and place it in your app/ folder.

‍

‍

Step 2: Add the SDK Dependency

Add JitPack and the Google Services plugin, then declare the SDK dependency.

‍

Kotlin DSL:

// settings.gradle.kts
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven("https://jitpack.io")
    }
}

‍

// build.gradle.kts (root)
plugins {
    id("com.google.gms.google-services") version "4.4.2" apply false
}

‍

// app/build.gradle.kts
plugins {
    id("com.google.gms.google-services")
}
android {
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions { jvmTarget = "17" }
}
dependencies {
    implementation("com.github.apps-on-air:appsonair-android-push-notification:1.0.6-beta")
}

‍

Groovy DSL:

// settings.gradle
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven { url 'https://jitpack.io' }
    }
}

‍

// build.gradle (root)
plugins {
    id 'com.google.gms.google-services' version '4.4.2' apply false
}

‍

// app/build.gradle
plugins {
    id 'com.google.gms.google-services'
}
android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_17
        targetCompatibility JavaVersion.VERSION_17
    }
}
dependencies {
    implementation 'com.github.apps-on-air:appsonair-android-push-notification:1.0.6-beta'
}

Sync Gradle in Android Studio after editing these files.

‍

‍

Step 3: Notification Icon and Color

Android draws the status-bar icon from its alpha channel only. Every non-transparent pixel becomes one solid color. Without a proper icon, the SDK falls back to your launcher icon, which renders as a white square.

‍

Icon requirements: white silhouette on a transparent background, stored in res/drawable (not mipmap), as a vector drawable or PNGs per density.

‍

PNG sizes for a 24dp icon

Folder Size
drawable-mdpi 24 × 24 px
drawable-hdpi 36 × 36 px
drawable-xhdpi 48 × 48 px
drawable-xxhdpi 72 × 72 px
drawable-xxxhdpi 96 × 96 px

‍

Use Android Studio's File → New → Image Asset → Icon type: Notification Icons to generate all densities from one source image.

‍

Vector drawable example:

<!-- app/src/main/res/drawable/ic_stat_appsonair_default.xml -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24"
    android:tint="#FFFFFF">
    <path
        android:fillColor="#FFFFFF"
        android:pathData="M12,22c1.1,0 2,-0.9 2,-2h-4c0,1.1 0.9,2 2,2z..." />
</vector>

‍

Accent color (must be a color resource, not a raw hex in the manifest):

<!-- res/values/colors.xml -->
<resources>
    <color name="appsonair_notification_accent_color">#2E6BE6</color>
</resources>

‍

Declare both AppsOnAir and Firebase meta-data entries in AndroidManifest.xml. Two pairs are required: one for notifications the SDK renders, one for notification-block messages Firebase renders itself when the app is backgrounded.

<application ...>
    <!-- AppsOnAir SDK: data-only and foreground notifications -->
    <meta-data
        android:name="com.appsonair.apppush.default_notification_icon"
        android:resource="@drawable/ic_stat_appsonair_default" />
    <meta-data
        android:name="com.appsonair.apppush.default_notification_color"
        android:resource="@color/appsonair_notification_accent_color" />
    <!-- Firebase: notification-block messages rendered in background -->
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_icon"
        android:resource="@drawable/ic_stat_appsonair_default" />
    <meta-data
        android:name="com.google.firebase.messaging.default_notification_color"
        android:resource="@color/appsonair_notification_accent_color" />
</application>

‍

Icon precedence for SDK-rendered notifications:

Property Precedence
Small icon small_icon data key → manifest default → launcher icon (white square + WARN log)
Accent color bg_color data key (hex) → manifest default → none
Large icon large_icon data key (HTTPS URL or drawable name) → image_url → none

‍

‍

Step 4: Initialize the SDK

Initialize in your Application class so it starts before any Activity exists. Silent pushes can arrive while no Activity is running, so register setSilentPushListener here too, before initialize().

// MyApp.kt
import android.app.Application
import com.appsonair.apppush.AppPushService
import com.appsonair.apppush.LogLevel
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        AppPushService.Debug.logLevel = LogLevel.VERBOSE  // dev only
        // Register BEFORE initialize() so silent pushes are not missed
        AppPushService.setSilentPushListener { data ->
            // Runs on a background thread
            Log.d("MyApp", "Silent push: $data")
        }
        AppPushService.initialize(this)
    }
}

‍

Register MyApp in AndroidManifest.xml:

<application android:name=".MyApp" ...>

‍

‍

Step 5: Wire Your Activity

Implement PushListener in your main Activity to receive notification events and handle taps:

// MainActivity.kt
class MainActivity : AppCompatActivity(), PushListener {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        AppPushService.setListener(this)
        AppPushService.requestNotificationPermission(this)
        AppPushService.handleNotificationTapIntent(intent)  // cold-start taps
    }
    override fun onNewIntent(intent: Intent?) {
        super.onNewIntent(intent)
        intent?.let { AppPushService.handleNotificationTapIntent(it) }
    }
    override fun onDestroy() {
        super.onDestroy()
        AppPushService.setListener(null)
    }
    override fun onTokenUpdated(token: String) { }
    override fun onNotificationReceived(notification: PushNotification) { }
    override fun onNotificationOpened(notification: PushNotification) { }
    override fun onError(error: PushError) { }
}

Always call handleNotificationTapIntent in both onCreate and onNewIntent. When the app is killed and the user taps a notification, Android relaunches via an Intent. Missing the `onCreate` call means cold-start taps are lost.

‍

‍

Feature Guide

‍

Subscription Observer

Observe token and opt-in state changes without polling. Register after initialize().

AppPushService.User.pushSubscription.addObserver(object : IPushSubscriptionObserver {
    override fun onPushSubscriptionDidChange(state: PushSubscriptionChangedState) {
        println("optedIn: ${state.current.optedIn}")
        println("token: ${state.current.token}")
    }
})

‍

Permission

val granted    = AppPushService.Notifications.permission(context)
val canRequest = AppPushService.Notifications.canRequestPermission(context)
AppPushService.Notifications.requestPermission(activity)
// Permanently denied on Android 13+? Send to Settings:
AppPushService.Notifications.requestPermission(activity, fallbackToSettings = true)
AppPushService.Notifications.addPermissionObserver(object : INotificationPermissionObserver {
    override fun onNotificationPermissionDidChange(permission: Boolean) { }
})

On Android 13+, two denials permanently suppress the system dialog. Request at a meaningful point in onboarding, not at app launch.

‍

User Identity

AppPushService.login("user_12345")   // associate after login
AppPushService.logout()              // disassociate on sign-out; clears tags and aliases

‍

Tags and Segmentation

AppPushService.User.addTag("plan", "premium")
AppPushService.User.addTags(mapOf("plan" to "premium", "region" to "us-east"))
AppPushService.User.removeTag("plan")
AppPushService.User.getTags { tags -> /* callback on main thread */ }

‍

Language

AppPushService.User.setLanguage("fr")   // ISO 639-1
val current = AppPushService.User.language

‍

Aliases and Email

AppPushService.User.addAlias("crm_id", "CRM-9876")
AppPushService.User.addAliases(mapOf("crm_id" to "CRM-9876"))
AppPushService.User.removeAlias("crm_id")
AppPushService.User.addEmail("user@example.com")
AppPushService.User.removeEmail("user@example.com")

‍

Opt-in and Opt-out

AppPushService.User.pushSubscription.optOut()   // stop delivery, preserve OS permission
AppPushService.User.pushSubscription.optIn()
val optedIn = AppPushService.User.pushSubscription.optedIn
// Fetch server's copy
AppPushService.User.pushSubscription.getOptedIn { optedIn -> }

‍

Foreground Display Control

AppPushService.Notifications.addForegroundLifecycleListener(object : INotificationLifecycleListener {
    override fun onWillDisplay(event: NotificationWillDisplayEvent) {
        // Do nothing: SDK displays it.
        // Call preventDefault(): suppressed entirely, handle display yourself.
        event.preventDefault()
    }
})

‍

Handling Taps

AppPushService.Notifications.addClickListener(object : INotificationClickListener {
    override fun onClick(event: NotificationClickEvent) {
        val actionId = event.result.actionId   // null = body tap, else action button ID
        val url      = event.result.url
    }
})

‍

Notification Channels (Android 8.0+)

Separate channels let users control notification types independently in Settings.

AppPushService.Notifications.createNotificationChannel(
    context     = applicationContext,
    id          = "transactional",
    name        = "Transactional Notifications",
    importance  = NotificationManager.IMPORTANCE_HIGH,
    description = "Order updates and alerts"
)
AppPushService.Notifications.deleteNotificationChannel(context, "transactional")

A channel's sound is fixed at creation. To change it, use a new channel ID.

‍

Dismissing Notifications

AppPushService.Notifications.clearAllNotifications(context)
AppPushService.Notifications.removeNotification(context, "order-4821")         // by notification_id
AppPushService.Notifications.removeGroupedNotifications(context, groupKey = "orders")

‍

Badge Count

Supported on Samsung, Huawei, Xiaomi, Sony, and ASUS launchers.

AppPushService.setBadgeCount(context, 5)
AppPushService.clearBadgeCount(context)

‍

Silent Push

A silent push delivers data to your app without showing a notification. The SDK treats a push as silent when its data payload contains "silent": "true". Only the exact string "true" counts: "1" or "TRUE" will show as a normal notification.

‍

Register the listener in Application.onCreate(). A silent push can arrive while no Activity exists, so a listener registered in an Activity would miss it.

AppPushService.setSilentPushListener { data ->
    // Runs on a background thread
    Log.d("MyApp", "Silent push received: $data")
}

‍

FCM payload for a silent push:

{
  "message": {
    "token": "device-fcm-token",
    "data": {
      "notification_id": "sync-001",
      "silent":          "true",
      "sync_type":       "orders"
    }
  }
}

The payload must be data-only. If a `notification` block is present, Firebase renders the notification itself and the listener never fires.

‍

Rich Media (Images)

Include big_picture in a data-only payload for an expanded image with BigPictureStyle. large_icon sets the collapsed thumbnail independently.

{
  "message": {
    "token": "device-fcm-token",
    "data": {
      "notification_id": "notif-123",
      "title":           "New offer",
      "body":            "Limited time deal",
      "big_picture":     "https://cdn.example.com/offer.jpg",
      "large_icon":      "https://cdn.example.com/brand-thumb.png"
    }
  }
}

JPEG, PNG, WebP, and SVG are supported. Images must respond within 10 seconds and be under 10 MB. Big pictures are scaled to 1024x1024 px; large icons to 256x256 px.

‍

‍

REST API

In addition to the SDK, AppsOnAir supports triggering push notifications via REST API, useful for server-side sends, CI/CD workflows, and backend automation. For more details check this documentation. 

‍

Payload Reference

Key Meaning
notification_id Unique ID for the notification. Required for programmatic dismissal.
title / body Notification text. Falls back to the notification block if absent.
big_picture HTTPS image for the expanded BigPictureStyle view.
large_icon HTTPS image or drawable name for the collapsed thumbnail.
image_url Legacy key. Used for big_picture or large_icon when those are absent.
small_icon Drawable name for the status-bar icon. Overrides the manifest default.
bg_color Hex accent color (e.g., "#2E6BE6"). Overrides the manifest default.
channel_id Target notification channel. Defaults to appsonair_push_channel.
collapse_key Replaces a previous notification with the same key instead of stacking.
sound File in res/raw without the extension.
actions Up to 3 action buttons as a JSON string. Fires the click listener with result.actionId set.
silent "true" routes to the silent push listener without displaying a notification.

‍

‍

When Notifications Won't Be Delivered

Push delivery depends on OS state, manufacturer battery managers, and user settings, not just your code.

Condition Effect
Doze mode (Android 6+) Device is stationary, screen is off, and unplugged. Normal-priority and silent pushes may be deferred; high-priority FCM messages can break through.
App Standby buckets (Android 9+) Rarely used apps receive progressively restricted background processing. Silent pushes may be deferred or dropped.
Battery optimization enabled Background wakeups may be delayed or skipped. Ask users to set battery usage to Unrestricted for more reliable silent push delivery.
Background data restricted FCM delivery may be restricted while the app is in the background on mobile data.
Force-stopped by user Android will not wake the app for push processing until the user manually relaunches it. This cannot be worked around in code.
OEM battery savers (Xiaomi/MIUI, Huawei/EMUI, Samsung/One UI, OnePlus, OPPO/ColorOS) Proprietary battery managers may terminate background processes. Users may need to add the app to Autostart or Protected Apps allowlists.
No network FCM may hold messages until the device reconnects, subject to the time_to_live window (default: 4 weeks).

‍

‍

Troubleshooting

Problem Fix
Token never arrives Ensure google-services.json is placed in the app/ directory. Confirm the file placement and re-sync Gradle.
FIS_AUTH_ERROR Firebase API key rejected. Add the package name and signing SHA-1 in Google Cloud Console → API Credentials. Include all SHA-1 fingerprints: debug, release, and Play App Signing.
SERVICE_NOT_AVAILABLE in Logcat The device cannot reach Firebase. Check the network, disable VPN/Private DNS, update Google Play Services, and allow autostart on OEM devices. Force-stop and relaunch the app after fixing the issue.
JVM-target mismatch Set both compileOptions and kotlinOptions.jvmTarget to VERSION_17.
White square icon Add com.appsonair.apppush.default_notification_icon metadata pointing to a white-on-transparent drawable in res/drawable.
Icon correct in foreground, wrong in background Add com.google.firebase.messaging.default_notification_icon metadata as well.
Accent color ignored Use android:resource="@color/..." in metadata, not android:value.
No push in background Send a data-only FCM payload without a notification block.
Silent push shown as a notification Add "silent": "true" (exact string) to the data payload.
Silent push listener never fires Register the listener in Application.onCreate() instead of an Activity, and ensure the payload does not contain a notification block.
Cold-start tap ignored Call handleNotificationTapIntent in both onCreate() and onNewIntent().
Image missing Ensure the URL uses HTTPS and the download completes within 10 seconds. The image must be under 10 MB.

‍

‍

Setup Checklist

  • Setup google-services.json in app/
  • Google Services plugin applied in both build.gradle.kts files
  • JitPack added and SDK dependency declared, Gradle synced
  • AppsonairAppId in AndroidManifest.xml
  • White-on-transparent icon in res/drawable*
  • Accent color in res/values/colors.xml
  • Both AppsOnAir and Firebase icon/color meta-data in manifest
  • setSilentPushListener registered before initialize() in Application.onCreate()
  • AppPushService.initialize(this) called in Application.onCreate()
  • handleNotificationTapIntent called in both onCreate and onNewIntent
  •  Notification permission requested (Android 13+)

‍

‍

Log Levels

Set before initialize():

Level Use
NONE Silent logging (default for production)
FATAL Fatal errors only
ERROR Errors
WARN Warnings
INFO General information
DEBUG Debug information
VERBOSE All messages (use during development)

‍

Conclusion

The AppsOnAir Android Push Notification SDK handles the FCM integration surface behind a clean API. Basic setup is five steps: Firebase, Gradle, notification icon, Application initialization, and Activity wiring. Everything beyond that, segmentation, silent push, rich media, channels, is opt-in. For teams already using AppsOnAir for OTA distribution, push targeting plugs directly into existing build segments and distribution groups without additional configuration.

FAQ’s

No items found.

Actionable Insights,
Straight to Your Inbox

Subscribe to our newsletter to get useful tutorials , webinars,use cases, and step-by-step guides from industry experts

Start Pushing Real-Time App Updates Today
Try AppsOnAir for Free
Stay Uptodate