Skip to content
Pushix Pushix
Features Developers Guides Pricing Questions
Sign in Start free
Menu
Features Developers Guides Pricing Questions
Sign in Create a free account
  1. Home
  2. Guides
  3. Android

Android push notifications

Push notifications for native Android apps in Kotlin or Java with Firebase Cloud Messaging: add the Pushix SDK, ask permission, handle taps, channels and data.

Updated 31 October 2026 12 min read Mobile apps

Mobile apps

  • Flutter
  • Android
  • iOS
  • React Native

Websites

  • Web push
  • WordPress

Your backend

  • REST API
  • PHP, Node.js, Python

Switching

  • Switching to Pushix
  • All guides
On this page 18 sections
  1. Requirements
  2. 1. Add Firebase to the app
  3. 2. Add the SDK
  4. 3. Android setup
  5. 4. Initialize
  6. API overview
  7. Permission
  8. User, login and tags
  9. Handling taps
  10. While the app is open
  11. Notification icon and channel
  12. In-app messages
  13. Data-only messages
  14. Using your own FirebaseMessagingService
  15. Privacy consent
  16. How notifications are drawn
  17. Testing
  18. Troubleshooting

The Pushix Android SDK (com.pushix:pushix-android) is for native Android apps written in Kotlin or Java. It registers the phone with Pushix, shows notifications, tells your app when one is tapped, and shows in-app messages.

For Flutter apps use the Flutter SDK instead (see Flutter SDK).

Requirements#

  • Android Studio with Android Gradle Plugin 8.x, Kotlin 1.9 or newer (Java apps work too)
  • minSdk 21 and compileSdk 34 or newer. If your app uses Firebase BoM 34 or newer, minSdk must be 23.
  • A Firebase project with your Android app in it. Use the project whose service account you upload to Pushix.

1. Add Firebase to the app#

In the Firebase console open your project → Project settings → Your apps, add the Android app if it isn't there (same package name as your app), and download google-services.json into the app/ folder.

Add the Google services plugin.

settings.gradle.kts or the root build.gradle.kts:

Kotlin
plugins {
    id("com.google.gms.google-services") version "4.4.2" apply false
}

app/build.gradle.kts:

Kotlin
plugins {
    id("com.android.application")
    id("com.google.gms.google-services")
}

Use the Firebase project whose service account you upload to Pushix. A different project makes every send fail with SENDER_ID_MISMATCH.

2. Add the SDK#

The SDK is in sdks/android/pushix in the Pushix download. Pick one of these.

As a module (simplest). Copy the pushix folder into your project, next to app:

Kotlin
// settings.gradle.kts
include(":pushix")

// app/build.gradle.kts
dependencies {
    implementation(project(":pushix"))
}

From a local Maven repository. In sdks/android run ./gradlew :pushix:publishToMavenLocal once (or ./gradlew :pushix:publishReleasePublicationToFolderRepository, which writes a repository to pushix/build/repo you can copy to a shared folder or web server). Then:

Kotlin
// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        mavenLocal() // or: maven { url = uri("/path/to/repo") }
    }
}

// app/build.gradle.kts
dependencies {
    implementation("com.pushix:pushix-android:1.0.0")
}

Use the Maven artifact if your project is on Android Gradle Plugin 9.

The SDK brings firebase-messaging with it, so you don't need to add it yourself. If the app already uses another push SDK, remove it (and its Gradle plugin, if it has one): two push SDKs in one app fight over Firebase messages.

3. Android setup#

AndroidManifest.xml — inside <application>:

XML
<!-- Channel and icon for notifications Android shows while the app is closed -->
<meta-data android:name="com.google.firebase.messaging.default_notification_channel_id"
    android:value="pushix_default" />
<meta-data android:name="com.google.firebase.messaging.default_notification_icon"
    android:resource="@drawable/ic_stat_notification" />

The SDK's own manifest already adds the INTERNET and POST_NOTIFICATIONS permissions, its FirebaseMessagingService and two invisible activities. You don't need to declare them.

Notification icon — Android draws the small icon as a white silhouette, so a full-colour launcher icon shows up as a white square. Create a white-on-transparent icon (Android Studio → New → Image Asset → Notification Icons) and name it ic_stat_notification. The SDK uses it automatically. You can also set an icon for all sends in the dashboard (App settings → Android defaults) or per message with small_icon.

Keep icons and sounds in release builds — icons and sounds named only in messages (small_icon, large_icon, android_sound, button icons) look unused to the build and get removed by resource shrinking. Create app/src/main/res/raw/keep.xml:

XML
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@drawable/ic_stat_*,@raw/*" />

If a message names an icon or sound that doesn't exist, the SDK falls back to the default icon and sound instead of dropping the notification.

You don't need ProGuard or R8 rules. The SDK ships its own.

4. Initialize#

Call initWithContext once, in your Application class. If you don't have one, create it and add android:name=".App" to <application> in the manifest.

Kotlin
import android.app.Application
import com.pushix.sdk.LogLevel
import com.pushix.sdk.Pushix

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        Pushix.Debug.logLevel = LogLevel.WARN // VERBOSE while you set things up

        Pushix.initWithContext(this, "YOUR_APP_ID", "https://pushix.in")

        Pushix.Notifications.addClickListener { event ->
            val data = event.notification.additionalData // your "data" object, or null
            if (data?.optString("screen") == "orders") {
                startActivity(Intent(this, OrdersActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
            }
        }
    }
}

The same in Java:

Java
public class App extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Pushix.initWithContext(this, "YOUR_APP_ID", "https://pushix.in");
        Pushix.getNotifications().addClickListener(event -> {
            JSONObject data = event.getNotification().getAdditionalData();
        });
    }
}

The App ID is in the dashboard under App settings. The server URL is https://pushix.in. A malformed App ID or URL throws IllegalArgumentException at start-up, so you notice it right away.

Then ask for permission at a sensible moment, while an Activity is visible (Android 13+ shows the system dialog):

Kotlin
Pushix.Notifications.requestPermission(true) { granted ->
    // runs on the main thread
}

When the user logs in to your app, link the device to your user id so your backend can target them:

Kotlin
Pushix.login(user.id.toString())   // on login
Pushix.logout()                    // on logout (also clears tags)

That's it. Open the app once and the device appears in the dashboard under Subscribers, marked Android.

API overview#

CallWhat it does
Pushix.initWithContext(context, appId, serverUrl)Starts the SDK. Call it once, in Application.onCreate()
Pushix.Debug.logLevel = LogLevel.VERBOSEHow much the SDK writes to Logcat (NONE … VERBOSE)
Pushix.login(id) / Pushix.logout()Links the device to your user id / unlinks it (also clears tags)
Pushix.consentRequired = true / Pushix.consentGiven = trueRegisters only after the user agrees
Pushix.Notifications.requestPermission(true) { granted -> } / .permission / addPermissionObserverNotification permission
Pushix.Notifications.addClickListener / addForegroundLifecycleListener / addSilentDataListener (and remove…)Taps, notifications arriving while the app is open, data-only messages
Pushix.Notifications.clearAllNotifications() / removeNotification(id)Removes notifications from the shade
Pushix.User.addTag(k, v) / addTags / removeTag / removeTags / getTags()Tags
Pushix.User.setLanguage("hi") / externalIdMessage language; the id passed to login
Pushix.User.pushSubscription.id / .token / .optedIn / optIn() / optOut() / addObserverPlayer id, FCM token and opt-in state of this device
Pushix.InAppMessages.addTrigger / addTriggers / removeTrigger / removeTriggers / clearTriggers / pausedIn-app message triggers, and holding messages back
Pushix.InAppMessages.addClickListener / addLifecycleListenerIn-app message events
Pushix.handleMessage(context, message) / Pushix.onNewToken(context, token) / Pushix.isPushixMessage(message)For apps with their own FirebaseMessagingService

From Java, use the getters: Pushix.getNotifications(), getUser(), getInAppMessages(), getDebug().

The listener types (PushixNotificationClickListener, PushixNotificationLifecycleListener, PushixPermissionObserver, PushixSilentDataListener, PushixPushSubscriptionObserver, PushixInAppMessageClickListener) are fun interfaces, so Kotlin lambdas work as well as object : implementations. PushixInAppMessageLifecycleListener has four optional methods (onWillDisplay, onDidDisplay, onWillDismiss, onDidDismiss).

Permission#

Kotlin
Pushix.Notifications.permission                      // true if notifications can be shown now
Pushix.Notifications.requestPermission(true) { granted -> }
Pushix.Notifications.addPermissionObserver { granted -> }
  • Android 13+ shows the system dialog. Older Android versions have no dialog; notifications are on unless the user turned them off.
  • With true (fallback to settings), when Android won't show the dialog any more (the user said no twice, or switched notifications off in Settings), the SDK asks "Notifications are off. Open Settings?" and opens the app's notification settings. With false, nothing is shown and the callback gets false.
  • Call it while one of your Activities is visible. From the background the callback gets the current value and nothing is shown.
  • The dialog texts are string resources (pushix_permission_title, pushix_permission_message, pushix_permission_open_settings, pushix_permission_not_now). Override them in your res/values*/strings.xml to change or translate them.

Pushix.User.pushSubscription.optOut() stops push to this device without removing the permission (the server marks it unsubscribed); optIn() turns it back on and asks for permission if needed.

User, login and tags#

Tags are key–value strings used for segments, filters and {{ personalisation }}.

Kotlin
Pushix.User.addTags(mapOf("level" to 7, "plan" to "gold", "last_bonus" to System.currentTimeMillis() / 1000))
Pushix.User.removeTag("plan")
val tags = Pushix.User.getTags()   // tags set from this device; tags your backend set are not included
Pushix.User.setLanguage("hi")   // pick the Hindi translation of messages

Numbers are sent as text. Your backend can also set tags by user id without the app: PUT /api/v1/apps/{app_id}/users/{external_user_id}.

Changes made while offline are saved on the phone and sent when the server can be reached, with retries.

Kotlin
Pushix.User.pushSubscription.id        // player id, "" until registered
Pushix.User.pushSubscription.token     // FCM token
Pushix.User.pushSubscription.addObserver { state ->
    // state.previous / state.current: id, token, optedIn
}

Calling login, logout or the User methods before initWithContext throws IllegalStateException.

Handling taps#

Kotlin
Pushix.Notifications.addClickListener { event ->
    val n = event.notification
    n.notificationId      // id from the REST API
    n.additionalData      // your "data" object (JSONObject), or null
    n.launchURL           // your "url"
    event.result.actionId // button id, or null for the notification itself
    event.result.url      // the button's url, else the notification's url
}
  • Listeners run on the main thread.
  • Taps that open the app from closed are delivered as soon as you add the first click listener, so add it in Application.onCreate().
  • Each tap is reported to the server and shows up as "Clicked" in the dashboard. A tap on the very first launch is reported once the device has registered.
  • A button tap dismisses its notification.

What opens. When the notification (or button) has a URL, the SDK opens it: http:// and https:// links in the browser, anything else (myshop://orders) as a deep link, which opens the screen in your app that declares that scheme in an <intent-filter>. Without a URL, tapping opens your app's launcher Activity, like tapping the app icon. Change this with manifest meta-data inside <application>:

XML
<!-- Don't open URLs. Open the app instead; your click listener gets event.result.url. -->
<meta-data android:name="com.pushix.suppressLaunchURLs" android:value="true" />

<!-- Open nothing at all, not even the app. Your click listener decides what to start. -->
<meta-data android:name="com.pushix.NotificationOpened.DEFAULT" android:value="DISABLE" />

If your click listener navigates to a screen itself, set suppressLaunchURLs so the deep link isn't opened twice. Note that the Flutter SDK never opens deep links by itself; the Android SDK does.

Notifications Android draws itself. Plain messages are shown by Android while your app is closed (see How notifications are drawn). Tapping one starts your launcher Activity, and the SDK finds the tap in that Activity's intent. This works without any code as long as your Activities extend ComponentActivity (AppCompatActivity, FragmentActivity). With a plain android.app.Activity that uses launchMode="singleTop" or singleTask, call setIntent(intent) in onNewIntent so the SDK sees the tap.

While the app is open#

Notifications are shown even when the app is in the foreground. To hide or delay one:

Kotlin
Pushix.Notifications.addForegroundLifecycleListener { event ->
    if (currentScreen == "orders") {
        event.preventDefault()            // don't show it
        // event.notification.display()   // or show it later
    }
}

The listener runs on the main thread. Keep it short; the notification waits for it.

Notification icon and channel#

  • Small icon. The SDK uses, in order: the message's small_icon (a drawable name in your app), the com.google.firebase.messaging.default_notification_icon meta-data, a drawable called ic_stat_notification, and finally a white bell built into the SDK.
  • Channel. The SDK creates the pushix_default channel ("Notifications", high importance) on start-up, so notifications pop up on screen. To rename it, override the pushix_default_channel_name and pushix_default_channel_description string resources.
  • Your own channels. Create them in your app (for example one with a custom sound) and send their id as android_channel_id. If a message names a channel the app hasn't created, the SDK uses pushix_default.
  • Sound. On Android 8+ the channel decides the sound; android_sound only applies to Android 7 and older. Put sound files in res/raw.
  • Accent colour, large icon, big picture, group, lock-screen visibility, collapse id all work as described in the REST API. Messages in the same android_group are bundled with a summary on Android 7+.

In-app messages#

Create them in the dashboard under In-app messages: a pop-up, card, top or bottom banner, or full screen, with your colours, picture and up to three buttons. Tick the Android platform. The SDK downloads the ones meant for this device each time a session starts and shows them by their trigger:

TriggerShows
When the app opensOn each new session (launch, or back after 30+ seconds)
From the Nth sessionWhen the app opens and the device has had at least N sessions
After some time in the appN seconds into the session
When my code sets a triggerWhen your code calls addTrigger with a matching key and value
Kotlin
Pushix.InAppMessages.addTrigger("cart_value", 1200)
Pushix.InAppMessages.paused = true    // e.g. during a game round
Pushix.InAppMessages.paused = false

Pushix.InAppMessages.addClickListener { event ->
    // event.result.actionId is the button id, event.result.url its link, event.result.action its type
    if (event.result.action == "deep_link" && event.result.url == "myshop://account") {
        startActivity(Intent(this, KycActivity::class.java))
    }
}

Pushix.InAppMessages.addLifecycleListener(object : PushixInAppMessageLifecycleListener {
    override fun onDidDisplay(event: PushixInAppMessageDidDisplayEvent) { /* pause a video */ }
    override fun onDidDismiss(event: PushixInAppMessageDidDismissEvent) { /* resume it */ }
})

Messages are drawn on top of the Activity that is in front, so no layout changes are needed. Pop-ups and full-screen messages close with the back button when they are dismissible. Frequency rules (show at most N times, wait between showings) are kept on the device. "Open a link" buttons open the browser; "Deep link" buttons are handed to your click listener, or opened by Android if you have none. Seen, clicked and closed counts appear in the dashboard and in Analytics.

Data-only messages#

Messages sent with content_available: true and no contents don't show anything. You get them here, on the main thread, whenever your app's process receives them:

Kotlin
Pushix.Notifications.addSilentDataListener { n ->
    if (n.additionalData?.optString("refresh") == "balance") reloadBalance()
}

Android may delay them while the phone is idle, and doesn't deliver them at all after the user force-stops the app.

Using your own FirebaseMessagingService#

The SDK declares PushixMessagingService in its manifest, so most apps need nothing. If your app already has its own FirebaseMessagingService (for another push provider or for your own data messages), Android delivers messages to yours instead. Forward them to Pushix:

Kotlin
class MyMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        if (Pushix.handleMessage(this, message)) return // a Pushix message, handled
        // ...your own messages
    }

    override fun onNewToken(token: String) {
        Pushix.onNewToken(this, token)
        // ...your own code
    }
}

Or extend PushixMessagingService and call super.onMessageReceived(message). Pushix.isPushixMessage(message) tells you whether a message came from Pushix without handling it.

To be sure only your service is used, remove the SDK's from your merged manifest:

XML
<service android:name="com.pushix.sdk.PushixMessagingService" tools:node="remove" />

(add xmlns:tools="http://schemas.android.com/tools" to the <manifest> tag).

Privacy consent#

To register only after the user agrees:

Kotlin
Pushix.consentRequired = true          // before initWithContext
Pushix.initWithContext(this, appId, serverUrl)
// later, when the user agrees:
Pushix.consentGiven = true

Both values are remembered across launches. Until consent is given the SDK sends nothing to Pushix.

How notifications are drawn#

  • Plain messages (title, text, picture) are sent as normal notifications. Android shows them even if the app was swiped away. While the app is open the SDK draws them, so your foreground listener can hide them.
  • Messages with buttons, a large icon or android_group are sent as data messages and drawn by the SDK. These need the app process to start, which some phone makers (Xiaomi, Oppo, Vivo, Realme) block when battery saving is aggressive. Prefer plain messages for anything important.

Testing#

  1. Run the app on a phone or an emulator with Google Play, and allow notifications.
  2. In the dashboard, open Subscribers, click your device and tick Test device.
  3. New message → Send to test devices. Try once with the app open, once in the background and once swiped away.

The example app in sdks/android shows initialization, login, tags, permission, the click listener and an in-app trigger. Put your google-services.json in sdks/android/example/ and your App ID and server in sdks/android/gradle.properties to run it.

Troubleshooting#

ProblemFix
Device never appears in SubscribersCheck the server URL (must be reachable from the phone over HTTPS) and the App ID. Set Pushix.Debug.logLevel = LogLevel.VERBOSE and filter Logcat by Pushix.
Log says "Firebase is not set up" or "Could not get an FCM token"Add google-services.json and the com.google.gms.google-services plugin. Test on a device or emulator with Google Play services.
Every send fails with SENDER_ID_MISMATCHThe service account in the dashboard is from a different Firebase project than google-services.json.
IllegalStateException: Call Pushix.initWithContext()Call initWithContext in Application.onCreate(), before any other Pushix call.
Notification shows as a white squareUse a white-on-transparent small icon named ic_stat_notification (see Android setup).
No pop-up, only an icon in the status barThe channel isn't high importance. Use the default pushix_default channel, or create your own with IMPORTANCE_HIGH. If the user lowered it in system settings, only they can raise it again.
Permission dialog never appearsIt needs Android 13+, targetSdk 33+, and a visible Activity. After two refusals Android stops showing it; pass true to requestPermission to offer Settings instead.
Works when open, nothing when closed (Xiaomi etc.)Ask users to allow Autostart / disable battery optimisation for the app, and send plain messages (no buttons or large icon).
Nothing at all after adding Pushix to an app with its own FirebaseMessagingServiceForward messages with Pushix.handleMessage (see Using your own FirebaseMessagingService).
Tap opens the browser and the app's screen twiceYour click listener and the SDK both open the URL. Add the com.pushix.suppressLaunchURLs meta-data.
Taps on plain notifications aren't reportedUse ComponentActivity/AppCompatActivity, or call setIntent(intent) in onNewIntent.
Custom sound doesn't playOn Android 8+ the channel decides the sound. Create a channel with that sound in your app and send its id as android_channel_id.
Icon or sound works in debug but not releaseAdd the keep.xml from Android setup so resource shrinking keeps them.
In-app message never showsCheck its status is Live, Android is ticked, and its frequency hasn't been used up on this device. Messages are fetched when a session starts, and wait while paused is true.
Previous guideFlutter push notifications Next guideiOS push notifications

Send your first notification

Create an app in Pushix to get your App ID and REST API key, then follow this guide. The free plan needs no card.

Create a free account See pricing

On this page

  1. Requirements
  2. 1. Add Firebase to the app
  3. 2. Add the SDK
  4. 3. Android setup
  5. 4. Initialize
  6. API overview
  7. Permission
  8. User, login and tags
  9. Handling taps
  10. While the app is open
  11. Notification icon and channel
  12. In-app messages
  13. Data-only messages
  14. Using your own FirebaseMessagingService
  15. Privacy consent
  16. How notifications are drawn
  17. Testing
  18. Troubleshooting
Back to top
Pushix Pushix

Push notification service for Android, iPhone and websites, from one dashboard or one API call. Built in West Bengal, India.

info@pushix.in

Product

Features Developers Pricing Questions Sign in Create an account

Guides

Flutter guide Android guide iOS guide React Native guide Web push guide WordPress guide REST API guide PHP, Node.js, Python guide Switching to Pushix guide All guides

Legal

Terms of Service Privacy Policy Cookie Policy Acceptable Use Policy Data Processing Addendum Refund & Cancellation Policy Service Delivery Policy

Company

Contact us All legal pages Support

© 2026 Pushix. All rights reserved.

Pushix · Powered by Pynix