Android SDK
openpush-android is a receive-only Android library. It registers a device with OpenPush, carries the user's identity and tags, and posts delivery receipts back. It has no send surface at all — messages are created through the REST API or the console, never from the device.
When to use
Add this SDK to any Android app that should receive push from OpenPush. If your app already owns a FirebaseMessagingService, keep it and forward payloads into the SDK. If it has no messaging service yet, the optional FCM module can own delivery and rendering for you.
Requirements
| Requirement | Value |
|---|---|
| SDK version | 1.1.5 |
| Minimum Android | 7.0 (API 24) |
| Compile SDK | 36 |
| Java / Kotlin target | 17 |
| Runtime dependency | kotlinx-coroutines-android only — the core artifact does not depend on Firebase |
| Credentials | Your app id and the app's SDK key (X-OP-SDK-Key) |
| Server | Nothing to choose — the API lives at https://app.openpush.ai. You do need a working FCM credential uploaded under Android — see platform-setup-fcm.md |
The SDK key is public by design and is meant to ship inside the app binary. The REST API key must never appear in an app. See security-and-limits.md.
Step 1 — install
The intended Maven coordinate is:
Code
The Maven artifact is not published yet. Until it is, add the library as a composite build from a source checkout — this is the distribution channel that actually works today:
Code
Optional modules, each versioned in step with the core:
| Module | What it adds |
|---|---|
openpush-android-fcm | A FirebaseMessagingService, a default notification renderer, and automatic receipts |
openpush-android-live-updates | The Android ongoing-notification renderer (Java-only, no Firebase dependency) |
openpush-android-in-app-messages | The in-app message presenter, trigger state and receipt path — see in-app-messages.md |
| The compatibility facade module | A compile-time alias layer for migrating hosts — see Compatibility facade |
Compatibility facade
The compatibility facade is an opt-in module for migrating hosts. It accepts the API spelling of the previous provider's SDK, so existing call sites compile against OpenPush unchanged. It is a source-compatibility layer only. It pulls openpush-android-in-app-messages in as an api dependency, because the facade's InAppMessages surface resolves onto it. See the migration guide.
Do not add openpush-android-fcm if your app already declares a FirebaseMessagingService. Two services filtering the same FCM intent means delivery to either one is unspecified. If it arrives transitively, remove its service during manifest merge:
Code
Step 2 — initialize
Put the SDK key in the manifest:
Code
Then initialize once, from Application.onCreate:
Code
serverURL already defaults to https://app.openpush.ai, so the argument above is optional and shown only to make the target explicit. To keep the key out of the manifest, use the overload that takes sdkKey explicitly — an explicit argument always beats the manifest value. Recognised manifest keys are ai.openpush.sdk_key and ai.openpush.app_id.
With no key in either place, initialize refuses rather than half-configuring: it reports through onLog and onRegistrationFailed and does nothing else.
OpenPush.attach(context, OpenPushConfiguration(appId, sdkKey), credential) is the lower-level form; initialize wraps it.
Verify: OpenPush.isInitialized is true and OpenPush.onLog reports no refusal.
Initialize from the Application, not an Activity
The SDK reads foregroundedness from the ActivityLifecycleCallbacks it registers at initialize. Initializing with an Application context installs that observation automatically. If you pass some other context, drive it yourself with OpenPush.onAppForegrounded() and OpenPush.onAppBackgrounded().
There is one sharp edge: attaching from an Activity adopts that Activity as visible, because the lifecycle callbacks register too late to have seen its onStart. If that Activity had already stopped, Android offers no way to discover it, so the process looks foregrounded until the Activity is destroyed. Initialize from Application.onCreate, or pass applicationContext.
Step 3 — request notification permission
Code
There is also a suspend overload. The overload without an Activity uses the currently resumed activity; with none resumed it logs the limitation and reports the cached permission immediately.
Forward the OS result from your Activity:
Code
The request code is OpenPush.Notifications.PERMISSION_REQUEST_CODE (9110). fallbackToSettings opens the system notification settings once the OS stops showing the prompt, which is the only path a denied user has left.
OpenPush.Notifications.permission is a cache, refreshed at initialization, on foregrounding, around permission calls, and while processing a foreground message. Watch changes with addPermissionObserver / removePermissionObserver.
Android has no provisional (quiet) authorization — that is an iOS-only concept, and the Android SDK deliberately offers no analogue.
Every registration reports the resolved status: 1 with permission, 0 without, -2 while explicitly opted out. The SDK recomputes it at launch and on foreground and re-registers only when the accepted value changed.
Step 4 — hand over the push token
OpenPush never asks FCM for a token. Your app owns provider integration and forwards the result:
Code
Call it from onNewToken too, so a rotated token reaches the server.
Transient registration failures retry at +2 s and +8 s, three attempts total. A 4xx is final. onRegistrationFailed fires only after the last attempt, so it can arrive roughly ten seconds after the call. A newer registration cancels an older pending retry.
Verify: OpenPush.onRegistered fires, and the device appears in GET /v1/apps/{app_id}/subscriptions.
Step 5 — identity
Code
login before a token exists stores the identity and applies it to every later registration, including token refreshes. logout re-registers with a present-and-empty external_id, which is how the server is told to detach.
A rejected identity is reported as IdentityRejected, never as success — the device is registered anonymously in that case. Compute authHash on your server; the SDK keeps it in memory only and never persists it, so call login again at launch. The full model is in users-and-subscriptions.md.
Step 6 — tags and aliases
Code
Aliases use the same shape: addAlias, addAliases, removeAlias, removeAliases.
Rules that matter:
- Tag values are strings only. There are no numeric, boolean or date overloads on any OpenPush SDK.
- Deleting is an empty-string value. A removed tag keeps riding as
""for the rest of the process session so a token-refresh re-registration cannot resurrect it. - Writes join a fixed 300 ms window measured from the first pending write; the window does not restart.
register,login,logout,optInandoptOutabsorb pending writes; backgrounding flushes immediately. - Without a token, writes simply accumulate and ride the first registration.
- The server caps aliases at 10 per user and refuses reserved labels such as
external_id. It names refused labels on a successful response rather than failing the request, and the SDK logs them. - With identity verification enabled, a tag or alias write without a valid in-memory auth hash comes back rejected and is logged.
Step 7 — notification handlers and receipts
Code
Forward every message through processForegroundMessage, backgrounded or not. onMessageReceived fires with the app backgrounded or killed too, and the receipt has to be posted either way. The foreground listeners run — and preventDefault() can return true — only while the app actually has a visible Activity. That is what stops a preventDefault() from suppressing a notification the user never saw: a background delivery still posts its receipt, still returns false, and still renders.
Keep the returned ListenerHandle to remove a listener. Listeners are not cleared by initialize.
Receipts
processForegroundMessage posts received; processClick posts clicked. Post confirmed yourself, at the moment you actually display the notification:
Code
The three stages are deliberately distinct and are not interchangeable:
| Stage | Meaning |
|---|---|
| Provider Accepted | FCM took the message from OpenPush |
| Device Received | the SDK got the data payload |
| Confirmed Receipt | a notification was actually displayed |
| Clicked | the user opened it |
Failed receipt requests stay retryable; only successful receipts are deduplicated, and only for the current process session.
Action buttons
The bundled FCM renderer displays buttons from the message's top-level actions
field and passes the selected ID to the click callback. If your app supplies its
own renderer, parse the delivered button data with:
Code
OpenPush.actions parses the internal op_actions payload key, capped at 3.
Attach the parsed buttons to your own notification builder. The public send API
uses actions; see Sending messages.
Android rendering options in the payload
An OpenPush Android push is a data-only FCM message. There is no android.notification block, so FCM never draws anything — your app does. The sender therefore ships the composer's Android options as plain keys inside the data map:
| Data key | Composer option | Typical use |
|---|---|---|
op_android_channel | Channel | the NotificationChannel id to post into |
op_android_accent | Accent | Notification.Builder.setColor |
op_android_large_icon | Large icon | image URL for setLargeIcon |
op_android_big_picture | Big picture | image URL for BigPictureStyle |
op_android_group | Group | setGroup key for bundling |
The bundled FCM renderer uses these options: it selects the requested channel, applies the accent and group, and loads big-picture and large-icon HTTPS URLs. It preserves a channel your app already created; if the channel ID is unknown, it creates one with default importance and the ID as its name. A custom renderer can read the same payload keys and apply its own display policy.
Other keys that arrive in the same map: op_message_id (the id you pass to receipt), title, body, image_url and deep_link when set, plus any custom data from the message. op_render_truncated is "1" when the server had to trim the payload to fit FCM's 4 KB data envelope.
Images must be https:// URLs, or a media path served by OpenPush — see sending-messages.md.
The optional FCM module
If you add openpush-android-fcm, it registers refreshed tokens, renders payloads that carry a title or body, creates a notification channel when one is missing, posts received and clicked receipts, and forwards notification extras to your launch activity. Configure its display defaults in the manifest:
Code
Without the channel meta-data it posts to a channel named openpush_default.
Custom events
Code
Names are 1–128 characters matching [a-zA-Z0-9_\-. ]+. Properties must be JSON-safe and at most 2048 bytes. Events are validated locally, persisted while offline, and sent in batches of up to 50 after the same 300 ms window. Switching workspaces clears the queue so one account's activity is never reported under another. See events.md.
Subscription state and opt-out
Code
optOut() does not delete the subscription or the provider token; it is durable across process death and reversible. clearToken() wipes the local token but leaves state.id in place, because the server-side subscription still exists.
The Android subscription state carries id, token and optedIn — there is deliberately no status field on Android. Only the iOS SDK exposes the raw status integer.
App sessions
When you initialize with an Application context, cold launches and foreground transitions send a throttled app-session ping, at most one per 30 minutes, persisted across process restarts. It powers session counts and the per-user activity histogram behind best-hour delivery. With any other context, call OpenPush.onAppForegrounded() yourself.
Limits
- No send surface, and none is planned for the SDK. Sending is REST or console only.
- No swizzling and no delegate installation — every callback is forwarded by your app explicitly.
- No badge API, no notification-category API, and no consent gate. The compatibility facade's
consentRequired/consentGivenare compile-time errors, not silent no-ops. - No notification inbox: no inbox API exists on the SDK and the compatibility facade declares none.
- In-app messages are supported.
openpush-android-in-app-messagescarries the presenter, the trigger controller and the impression/click receipt path, and the compatibility facade'sInAppMessagesresolves ontoOpenPushInAppMessagesrather than failing to compile. See in-app-messages.md and the in-app messages API. - No ActivityKit-style Live Activities — Android has none. The equivalent is Live Updates, an ongoing notification driven by a
live_notificationdata value. See live-activities.md. - Tag values are strings, always.
DeviceCredentialStoreis not constructible from a host app on Android; its constructor is internal.- Not on Maven Central yet — see Step 1.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
initialize logs a refusal and nothing registers | No SDK key in the manifest and none passed in code | Add ai.openpush.sdk_key meta-data or pass sdkKey explicitly |
onRegistrationFailed about ten seconds after a call | Transport or 5xx retries exhausted (+2 s, +8 s) | Check the device's network reachability to app.openpush.ai; a 4xx is final and reported immediately |
| Device registered but never receives anything | FCM service account uploaded under Web instead of Android | Upload it under Android — see platform-setup-fcm.md |
| Notifications arrive twice | Both your own FirebaseMessagingService and openpush-android-fcm are present | Remove the module, or strip its service with tools:node="remove" |
| Receipts never appear in message stats | processForegroundMessage only called on foreground deliveries, or no op_message_id in the map | Forward every delivery; confirm OpenPush.messageId(data) is non-null |
preventDefault() has no effect | The delivery arrived with no visible Activity | Expected — background deliveries always render |
| Identity shows as anonymous in the console | Identity verification is on and the auth hash was missing or wrong | Compute HMAC-SHA256(external_id) with an active REST key on your server and pass it as authHash |
| Permission prompt never appears | No resumed Activity, or the OS has stopped offering the prompt | Pass an Activity, or use fallbackToSettings = true |
FAQ
Do I need Firebase for the core library?
No. The core artifact has one runtime dependency and it is not Firebase. Only openpush-android-fcm pulls in firebase-messaging.
Can the SDK send a message? No. It registers, identifies, tags, and reports receipts. Sending lives behind the REST API key, which does not belong in an app binary.
Where does the notification actually get drawn? In your app. OpenPush sends data-only FCM messages so that channel, colour, style and grouping stay under your control.
What happens to tags if the FCM token rotates?
Nothing is lost. The whole tag map rides every registration, and deleted tags keep riding as "" for the session.
Which version is current? 1.1.5.
Related
- quickstart.md
- platform-setup-fcm.md
- users-and-subscriptions.md
- sending-messages.md
- events.md
- Migration guide
- in-app-messages.md
- ../api-handbook/03-subscriptions-users.md
- ../api-handbook/06-events-ingest.md
- ../api-handbook/10-in-app-messages.md