Skip to content

Android SDK (Kotlin)

Deliver sponsored push notifications in your Android app. The SDK handles device registration, notification display, click tracking and impression measurement — with the ad source fully concealed.

Current version: 0.2.0ai.dlserve:dlserve-android:0.2.0.

What you need first

  • minSdk 23 (Android 6.0+) and a Java 17 toolchain.
  • A Firebase project for your app with:
    • google-services.json in your app module (app/google-services.json), and
    • the com.google.gms.google-services Gradle plugin applied. Without the plugin the JSON is never read, the app gets no FCM token, and no device ever registers.
  • Your Firebase service-account JSON uploaded to your dlserve placement — in the dashboard, open your Android placement and upload it there. We send through your own Firebase project, so until that file is uploaded devices register fine and not a single notification is ever delivered.

The Firebase plugin

Root build.gradle.kts:

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

App module build.gradle.kts:

kotlin
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.gms.google-services")
}

The SDK depends on com.google.firebase:firebase-messaging:24.0.0 and exposes it on your compile classpath, so you do not have to declare it yourself. If you already use the Firebase BOM, keep it and pin 33.1.0 or newer — an older BOM pins an older firebase-messaging, and mixing the two is the usual cause of a duplicate-class or NoSuchMethodError build failure.

1. Add the dlserve repository

The SDK is served from https://maven.dlserve.ai. Declare it at settings level — current Android templates run FAIL_ON_PROJECT_REPOS, where a repository declared inside a module fails the build.

settings.gradle.kts:

kotlin
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        maven {
            url = uri("https://maven.dlserve.ai")
            content { includeGroup("ai.dlserve") }
        }
    }
}

Older projects that still resolve per project declare it in the root build.gradle instead:

groovy
allprojects {
    repositories {
        google()
        mavenCentral()
        maven {
            url 'https://maven.dlserve.ai'
            content { includeGroup 'ai.dlserve' }
        }
    }
}

content { includeGroup(...) } keeps every other dependency lookup away from our host, so your build stays as fast as it was.

2. Add the dependency

In your app module's build.gradle.kts:

kotlin
dependencies {
    implementation("ai.dlserve:dlserve-android:0.2.0")
}

3. Initialize

Call init once from Application.onCreate(), and register the notification listener there too — a push can wake your process with no Activity alive, and the listener is what tells you a notification was shown or tapped.

kotlin
import ai.dlserve.sdk.Dlserve
import ai.dlserve.sdk.NotificationListener
import android.app.Application

class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        Dlserve.setNotificationListener(object : NotificationListener {
            override fun onDisplayed(title: String, body: String) {
                // A sponsored notification was posted to the tray.
            }

            override fun onOpened(title: String, body: String, clickUrl: String) {
                // The user tapped it; the browser is opening.
            }
        })

        Dlserve.init(this, appId = "your-app-id")
    }
}

appId is the App ID shown on your placement in the dashboard. init does its network work on a background thread and never blocks the main thread.

4. Route FCM messages

kotlin
class MyMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(msg: RemoteMessage) {
        if (Dlserve.handleMessageIfDlserve(this, msg)) return
        // Your own message handling.
    }

    override fun onNewToken(token: String) {
        Dlserve.onNewToken(token)
        // Forward to your own backend too, if you have one.
    }
}

handleMessageIfDlserve returns true and renders the notification when the message is ours, false otherwise. It never throws into your service: a render failure is logged and swallowed.

If you have already deserialized the payload into a plain map (a game engine or a cross-platform runtime typically has), call Dlserve.handleMessageFromData(context, data) instead — same contract.

Option B — you have no messaging service

The SDK ships ai.dlserve.sdk.DlserveMessagingService disabled, so it can never collide with an app that has its own. Enable it with a manifest-merge override in your app's AndroidManifest.xml — note the xmlns:tools declaration, which tools:replace requires:

xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:tools="http://schemas.android.com/tools">
    <application>
        <service
            android:name="ai.dlserve.sdk.DlserveMessagingService"
            android:enabled="true"
            tools:replace="android:enabled" />
    </application>
</manifest>

5. Ask for the notification permission (Android 13+)

Android 13 and newer will not post a notification until the user grants POST_NOTIFICATIONS. The SDK declares the permission; your app asks for it:

kotlin
if (Dlserve.needsNotificationPermission(context)) {
    // Launch your runtime permission request for POST_NOTIFICATIONS.
}

Sponsored notifications post to their own "Sponsored offers" channel, which the user can turn off without touching your app's other notifications. While the permission is missing or that channel is off, nothing is posted — and nothing is counted as shown.

6. Choose the status-bar icon

Android masks the small icon to its alpha channel, so it must be a flat white-on-transparent silhouette. The SDK ships one (ic_dlserve_notification). To use your own:

kotlin
Dlserve.setSmallIcon(R.drawable.ic_stat_myapp)

Users and tags

kotlin
Dlserve.login("user-123")   // associate this device with your user id
Dlserve.logout()            // clear the association — local only, see below

Dlserve.setTags(mapOf(
    "plan" to "premium",
    "trial" to null,        // a null value deletes the key
))

Limits — exceeding one throws IllegalArgumentException on the calling thread, so a bad value is caught in development instead of vanishing:

FieldLimit
externalId≤ 256 characters
number of tags≤ 20
tag key≤ 64 characters
tag value≤ 128 characters

logout() is local-only. It clears the stored user id on the device and sends nothing. It does not stop notifications and is not an opt-out.

Letting users turn notifications off

kotlin
Dlserve.setPushEnabled(context, false)   // unregisters the device and stops rendering
Dlserve.setPushEnabled(context, true)    // registers again
Dlserve.isPushEnabled(context)           // current state, default true

The choice is stored on the device and survives restarts. While it is off, init, onNewToken and the message handlers do nothing at all. Give users this switch somewhere in your settings screen — the system-level "Sponsored offers" channel toggle is a fallback, not a substitute.

API reference

MethodDescription
Dlserve.init(context, appId)Initialize the SDK and register the device. Call once, from Application.onCreate().
Dlserve.login(externalId)Associate a user id (≤ 256 chars). Throws IllegalArgumentException if longer.
Dlserve.logout()Clear the user association. Local only — not an opt-out.
Dlserve.setTags(Map<String, String?>)Set tags; a null value deletes the key. Throws IllegalArgumentException over the limits.
Dlserve.setPushEnabled(context, enabled)Turn sponsored notifications on or off for this device. Persisted.
Dlserve.isPushEnabled(context)Whether they are on. Default true.
Dlserve.onNewToken(token)Forward an FCM token rotation.
Dlserve.handleMessageIfDlserve(context, msg)Route a RemoteMessage; true = handled by us.
Dlserve.handleMessageFromData(context, data)Same, for an already-deserialized data map.
Dlserve.needsNotificationPermission(context)Whether POST_NOTIFICATIONS still has to be requested.
Dlserve.setNotificationListener(listener)Observe onDisplayed / onOpened. Set it in Application.onCreate().
Dlserve.setSmallIcon(resId)Override the status-bar icon.

Troubleshooting

What you seeWhyFix
Could not find ai.dlserve:dlserve-android:0.2.0The repository is missingStep 1 — and check it is in settings.gradle.kts, not a module
Build was configured to prefer settings repositories over project repositoriesThe repository was declared inside a moduleMove it to settings.gradle.kts
Logcat: Dlserve: no FCM token — apply the com.google.gms.google-services plugin and put google-services.json in your app moduleThe google-services plugin or google-services.json is missing"What you need first"
Devices register, no notification ever arrivesThe service-account JSON is not on the placementUpload it in the dashboard
Logcat: Dlserve: config unavailable for appId '…' — dropping this pushThe App ID is wrong, or it is not an Android placement — the push was droppedCopy the App ID from the placement again, step 3
Nothing on Android 13+, logcat: Dlserve: notify blocked: POST_NOTIFICATIONS not grantedPOST_NOTIFICATIONS was never grantedStep 5
Nothing although the permission is grantedThe user turned off "Sponsored offers" in system settingsNothing to fix — that is the opt-out working
IllegalArgumentException from login / setTagsA value is over a limit"Users and tags"
Duplicate class / NoSuchMethodError around FirebaseAn old Firebase BOMPin BOM 33.1.0 or newer

What changed in 0.2.0

0.1.0 was never published to a repository — 0.2.0 is the first version you can resolve. If you were building against a local copy, note:

  • The impression pixel and onDisplayed now fire only when the notification was really shown.
  • Tapping a notification now calls onOpened before the browser opens; the callback used to be inert.
  • login and setTags now throw on invalid input instead of dropping the call silently.
  • New: setPushEnabled / isPushEnabled (opt-out) and setSmallIcon.
  • The render path can no longer throw into your FirebaseMessagingService.

Next: Flutter · React Native · Troubleshooting