Android SDK quickstart
From a fresh Crossdeck account to your first Android event landing in the dashboard in under ten minutes. One Gradle dependency, two values to paste, one Crossdeck.start(...) call inside your Application.
If you just signed up, the dashboard's Onboarding flow generates the same snippet you'll build here. This page exists for developers who closed the wizard, came back later, or want to read the steps before they paste anything into Android Studio.
Before you start
You'll need:
- A Crossdeck account with a project created (if you don't have one, start free — no credit card).
- Android Studio Hedgehog (2023.1) or later. Check via Android Studio menu → About Android Studio.
- An Android app project with minSdk 21 or higher (Android 5.0 Lollipop, ~99% of active devices). Check via app/build.gradle.kts →
android { defaultConfig { minSdk = 21 } }. The SDK uses no API-version-conditional code; one binary serves every supported device. - Kotlin 1.9+ and Java 17 toolchains. Check Kotlin via top-level build.gradle.kts → plugins block →
id("org.jetbrains.kotlin.android") version "…". AGP 8.x ships both as defaults. - About 10 minutes.
You don't need a Google Play Console account or a published listing to send your first event — the emulator or a USB-connected device works fine. Google Play rail connection comes later, in rail-webhooks.
Step 1 · Find your appId and publishable key
Crossdeck identifies your Android app with two values:
| Value | What it looks like | What it does |
|---|---|---|
appId |
app_android_92b2d6a5728a4d |
Identifies which app inside your project the events belong to. Stable, never rotates. |
publicKey |
cd_pub_test_… or cd_pub_live_… |
Authenticates the SDK to the ingest endpoint. Safe to ship inside a Play Store APK/AAB. |
A cd_pub_test_… key routes into the sandbox environment — events here never affect production analytics or your customer-facing dashboards. Pair test keys with Environment.SANDBOX in debug builds and live keys with Environment.PRODUCTION in release — the SDK validates the pairing at start.
Where to find them
- Open the Crossdeck dashboard and select your project from the workspace switcher (top-left).
- From the left sidebar, click Developers, then API keys. The direct path is
/dashboard/developers/api-keys/. - The page lists your apps. If you haven't added an Android app yet, click + Add app and pick Android. Each app card shows the
appIdat the top and two publishable keys below — one test, one live. Click the copy icon next to either to copy it to your clipboard.
Step 2 · Find your Android applicationId
When you add an Android app in the Crossdeck dashboard, you'll be asked for its applicationId — the globally-unique reverse-DNS name that identifies your app on Google Play (com.acme.notes, com.spotify.music). Crossdeck uses it to attribute Google Play purchases and developer notifications to the right app inside your project.
How to find it in Android Studio
- Open your project in Android Studio.
- In the Project view (left sidebar, switch the dropdown to Android if it isn't already), open Gradle Scripts → build.gradle.kts (Module: app). If you're on Groovy DSL, it's build.gradle (Module: app).
- Inside the
android { defaultConfig { } }block, find theapplicationIdline — usually shaped likeapplicationId = "com.yourcompany.yourapp".
applicationId is the Google Play identity, not the package name.
You'll see two similar-looking strings in an Android project: applicationId (in build.gradle) and package (in AndroidManifest.xml or the namespace in build.gradle). They often match, but they're allowed to diverge — and Google Play, Play Billing, and Play Console all key on applicationId. Use applicationId for Crossdeck.
If you have product flavors or build variants (debug, release, paid, free)
Flavors and the debug suffix can produce different applicationIds at install time — e.g. com.acme.notes.debug for debug builds, com.acme.notes for release. Use the release applicationId (no suffix) for the Crossdeck app entry. If you want telemetry from debug and release routed to separate Crossdeck apps, create two Crossdeck app entries and pick the right key in your build script with BuildConfig.DEBUG.
If you can't find Android Studio (or want the answer from the command line)
grep applicationId app/build.gradle.kts
# or, on Groovy DSL:
grep applicationId app/build.gradle
Step 3 · Add the Gradle dependency
The SDK ships from Maven Central as com.crossdeck:crossdeck:1.+ — a single AAR with one import.
Kotlin DSL (build.gradle.kts)
// settings.gradle.kts — confirm mavenCentral is in the resolution list
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}
// app/build.gradle.kts
dependencies {
implementation("com.crossdeck:crossdeck:1.+")
}
Groovy DSL (build.gradle)
// app/build.gradle
dependencies {
implementation 'com.crossdeck:crossdeck:1.+'
}
Sync Gradle (Android Studio offers a "Sync Now" banner the moment you save). After sync, import com.crossdeck.Crossdeck resolves and brings the entire public API into scope.
Step 4 · Create the Application class
The SDK should start exactly once per process, before any UI code runs. The standard Android idiom is a subclass of android.app.Application overriding onCreate.
If you already have an Application subclass, skip to Step 5 — you just need to add the Crossdeck.start(...) call. If you don't, create one now:
- In Project → app → java → com.yourcompany.yourapp (or wherever your package root is), right-click → New → Kotlin Class/File.
- Name it
MyApplicationand pick Class. - Make it extend
Application:package com.yourcompany.yourapp import android.app.Application class MyApplication : Application() { override fun onCreate() { super.onCreate() // Crossdeck.start goes here — Step 5. } } - Wire it into your
AndroidManifest.xmlby adding theandroid:nameattribute to the<application>tag:<!-- app/src/main/AndroidManifest.xml --> <application android:name=".MyApplication" android:label="@string/app_name" …> … </application>
Step 5 · Initialise the SDK
Call Crossdeck.start(context, options) exactly once inside Application.onCreate. Wrap the call in try/catch and store the client as nullable — a typo'd key should log + degrade telemetry, never crash a customer's launch.
package com.yourcompany.yourapp
import android.app.Application
import android.util.Log
import com.crossdeck.Crossdeck
import com.crossdeck.CrossdeckError
import com.crossdeck.CrossdeckOptions
import com.crossdeck.Environment
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
// Build-configuration switch — Debug never embeds a live key,
// Release never routes events into the sandbox.
val publicKey = if (BuildConfig.DEBUG) "cd_pub_test_…" else "cd_pub_live_…"
val env = if (BuildConfig.DEBUG) Environment.SANDBOX else Environment.PRODUCTION
crossdeck = try {
Crossdeck.start(
this,
CrossdeckOptions(
appId = "app_android_92b2d6a5728a4d",
publicKey = publicKey,
environment = env,
),
)
} catch (e: CrossdeckError) {
// invalid_secret_key / env_mismatch / missing_app_id —
// recoverable misconfig. Log + carry on without telemetry.
Log.e("Crossdeck", "start failed (${e.code}): ${e.message}", e)
null
} catch (e: Throwable) {
Log.e("Crossdeck", "start failed", e)
null
}
}
companion object {
// Process-singleton accessor. Reach from anywhere as
// \`MyApplication.crossdeck?.…\` — no
// \`(applicationContext as MyApplication).cd\` boilerplate.
var crossdeck: Crossdeck? = null
private set
}
}
lateinit?
If Crossdeck.start throws (typo'd key, env mismatch), lateinit var cd: Crossdeck stays uninitialised. The FIRST access from anywhere in your app then crashes with UninitializedPropertyAccessException — long after onCreate, in code that has nothing to do with the original misconfig. Nullable + safe-call (cd?.) means the host app keeps working with telemetry silently disabled.
Reach the client from anywhere as MyApplication.crossdeck?.…:
// Track an event
MyApplication.crossdeck?.track("paywall_seen", mapOf("variant" to "annual"))
// Synchronous paywall gate (safe from the UI thread)
if (MyApplication.crossdeck?.isEntitled("pro") == true) {
showProUI()
} else {
showPaywall()
}
environment enum must match the publishable-key prefix.
cd_pub_test_… pairs with Environment.SANDBOX; cd_pub_live_… pairs with Environment.PRODUCTION. Mismatched values throw CrossdeckError.envMismatch at start rather than silently routing events into the wrong warehouse. Use BuildConfig.DEBUG to swap both together (the snippet above does this).
BuildConfig is off by default.
If your build fails with Unresolved reference: BuildConfig, your Android Gradle Plugin 8.x project hasn't enabled the BuildConfig class. Add it explicitly to app/build.gradle.kts:
android {
buildFeatures {
buildConfig = true
}
}
Sync Gradle and BuildConfig.DEBUG resolves. (AGP 7 generated it automatically — 8 made it opt-in to shave a few hundred ms off cold builds.)
Step 6 · Identify your user
Until you call identify(...), every event carries an anonymous ID minted on first launch and persisted via SharedPreferences. The moment you know who the user is — after sign-in — call identify with your app's user ID. The SDK rotates the anonymous events onto the identified person automatically.
MyApplication.crossdeck?.identify("user_847")
// When the user signs out
MyApplication.crossdeck?.reset()
The userId is whatever your app uses — the database primary key, the Firebase Auth UID, the Google account ID. Stable for the lifetime of the account.
Want the People feed to show real names instead of anon_xxx? identify also takes optional email and traits — see Identify users for the full signature.
Step 7 · Verify the heartbeat
Build and run your app on an emulator or USB-connected device. The SDK fires its boot heartbeat the moment start returns. Open Developers → Heartbeat in the dashboard to confirm.
The page shows a per-app SDK signal audit:
- Last seen — should show "a few seconds ago" after you launch the app.
- SDK version — the version string the SDK stamped on its events. Confirms which build is live.
- Recent events — live tail of the last events received. You should see
session.startedwithin a few seconds, thenscreen.viewedevents as you navigate through Activities and Fragments.
The SDK batches events (default flush at 20 events or 5 seconds) → ingest writes to Firestore → the dashboard's onSnapshot listener fans the update out. If you don't see anything after 15 seconds, jump to Troubleshooting.
If nothing shows up after 30 seconds
| Symptom | Most likely cause | Fix |
|---|---|---|
Logcat: Crossdeck start failed (env_mismatch) |
environment doesn't match the key (test key + PRODUCTION, or live key + SANDBOX). |
Use BuildConfig.DEBUG to swap both together — the snippet in Step 5 does this. |
Logcat: Crossdeck start failed (invalid_app_id) |
The publishable key is for a different project than the appId. | Re-copy both values from the same app card on API keys. |
Build fails with Could not find com.crossdeck:crossdeck:1.+ |
Your settings.gradle doesn't include mavenCentral() in the dependency-resolution list. |
Add mavenCentral() to the repositories { } block inside dependencyResolutionManagement in settings.gradle.kts. |
Build fails with Unresolved reference: BuildConfig |
AGP 8.x ships with BuildConfig generation disabled by default. Your Application class references BuildConfig.DEBUG. |
Enable it explicitly in app/build.gradle.kts: android { buildFeatures { buildConfig = true } }. Sync Gradle. |
App launches but nothing fires from Application.onCreate |
Your AndroidManifest.xml doesn't reference .MyApplication on the <application> tag — Android is using the default Application. |
Add android:name=".MyApplication" to the <application> element. Step 4 covers the exact line. |
| No logcat errors, but Heartbeat shows nothing | Emulator or device has no network; or a firewall is blocking api.cross-deck.com. |
From the device's browser, open https://api.cross-deck.com — you should see a 404 page (the root has no route, but DNS resolves). If it doesn't load, network isn't reachable. |
What's next
Heartbeat green? The SDK is live. From here:
- Identify users — pass traits (email, plan, signup date) so the People feed renders real names instead of
anon_xxx. - Track custom events —
cd.track("paywall_seen", mapOf("variant" to "annual"))for the conversion events that matter to you. - Entitlements & gating — gate pro features with one line:
if (cd.isEntitled("pro")) { … }. - Connect Google Play — start verifying Play Billing purchases and computing MRR from Google Play subscriptions.
- Full Android SDK reference — every method, every option, every event the SDK fires automatically.