Mobile
Android SDK
This page is assembled from
sdks/android/README.mdin the repository. See the SDK overview for the choice between native SDKs and wrappers.
Formerly the DTrackier SDK. Until 2.0 the AAR still ships deprecated forwarding aliases in
com.dtrackier.sdk(DTrackier.init(...),DTConfig, ...), so pre-rename code keeps compiling; switch imports tocom.metrixed.sdk.
Kotlin, min API 21, one AAR (com.metrixed:metrixed-android), no third-party HTTP stack (HttpURLConnection),
coroutines-free public API, Java-friendly (@JvmStatic). Only dependency: the tiny Google Play Install Referrer library.
Method names mirror the iOS SDK where idiomatic.
Install
// build.gradle.kts
dependencies {
implementation("com.metrixed:metrixed-android:1.0.0")
// optional OEM install-referrer providers (add the store's own client library too):
implementation("com.metrixed:metrixed-android-referrer-huawei:1.0.0") // also -samsung, -xiaomi, -vivo
}
Until published, make sdk-android-build writes a Maven-layout repo to sdks/android/*/build/repo.
Initialise
class App : Application() {
override fun onCreate() {
super.onCreate()
val config = DTConfig("APP_TOKEN", DTEnvironment.PRODUCTION) // or SANDBOX: flagged on every payload
.setBaseUrl("https://ingest.example.com")
.setSecret("1", "SDK_SECRET") // request signing (id is your rotation label)
Metrixed.init(this, config)
}
}
Call init from Application.onCreate (sessions are driven by activity lifecycle callbacks). The first launch reports
the install (waiting up to setReferrerTimeout, default 4 s, for the store referrer), later launches report sessions.
Events
Metrixed.trackEvent(
DTEvent("purchase") // letters, digits, _ . : - space; max 64 (invalid names are dropped with a warning)
.setRevenue(9.99, "USD") // sent as a 6-decimal string, never locale formatted
.setDedupeId(orderId) // counted once, even across retries
.addCallbackParameter("plan", "pro")
.addPartnerParameter("af_content", "x"))
Metrixed.trackAdRevenue(DTAdRevenue("applovin").setRevenue(0.012, "USD").setAdUnit("u1").setAdType("rewarded"))
Metrixed.setCustomerUserId("user-42"); Metrixed.setExternalDeviceId("my-device-id")
Google Play billing: trackPlayPurchase(DTPlayPurchase(productId, purchaseToken).setOrderId(..).setRevenue(..)) and
trackPlaySubscription(...) forward the Play tokens as purchase / subscription events with
params.verification=pending_server; verification against Google happens server side (not implemented yet, see handoff).
Attribution and deep links
Metrixed.setAttributionListener { a -> /* a.attributed, a.partnerId, a.matchType, a.clickId */ }
Metrixed.setDeeplinkHandler { link -> /* link.source = "scheme" | "deferred", link.uri, link.params, link.clickId */ }
Metrixed.setDeferredDeeplinkListener { link -> true } // true = let the SDK open the link (ACTION_VIEW within your app)
// in your Activity (onCreate and onNewIntent):
intent?.data?.let { Metrixed.processDeeplink(it) }
Deferred deep links come from the install response (deep_link_value) or, when absent, from POST /sdk/v1/deeplink
(signed; answer {found, deep_link_value, deep_link, click_id, params}, shared with the iOS SDK; 404 is ignored).
A link passed to processDeeplink before the first session rides that session; later links are re-engagement sessions.
Add <intent-filter> for your scheme / App Links to the Activity as usual.
Install referrer
Google Play is built in. For Huawei AppGallery, Samsung Galaxy Store, Xiaomi GetApps and vivo add the matching artifact
and its vendor client library, then config.addReferrerProvider(HuaweiReferrerProvider()). Providers run in the order
added after Google Play; the first non-empty referrer wins and its store is reported (google_play|huawei|samsung|xiaomi|vivo).
The OEM providers use reflection against the vendor client (override class names via the constructor).
Preinstall: the install carries preload from config.setPreloadKey(), else system property ro.metrixed.preload
(or ro.vendor.metrixed.preload), else the first line of /system/etc/metrixed_preload (/product, /vendor).
Consent and privacy
Metrixed.setConsent(DTConsent().setThirdPartySharing(false).setMeasurement(true).setAdPersonalization(false).setGdprApplies(true))
Metrixed.setThirdPartySharing(false) // shortcut
Metrixed.gdprForgetMe() // POST /v1/erase, then the SDK stays off permanently on this install
Metrixed.setEnabled(false) // pause; unsent data is discarded
thirdPartySharing=falseis honoured by the server (no raw ids in partner postbacks / analytics). Consent is stored and attached to every payload.measurement=falsestops all collection immediately: queued install/session/event data is deleted, nothing but the opt-out itself is sent (with only the pseudonymous SDK id).setConsentRequired(true)holds everything until measurement is granted; consent given before the install request rides the install.- Advertising ids: GAID (
setCollectGaid), App Set ID (setCollectAppSetId), Android ID (opt-in,setCollectAndroidId), OAID (host supplied). When the user enabled "limit ad tracking" the GAID is never sent andlimit_ad_tracking=trueis. - Kids / COPPA:
setKidsMode(true)never reads or sends any advertising id, forces third-party sharing off and marks payloadsconsent.child_directed=true. Also remove the permission from your manifest:<uses-permission android:name="com.google.android.gms.permission.AD_ID" tools:node="remove" />. - The SDK state (device id, queue) lives in
noBackupFilesDir, so Auto Backup never restores a device id onto another device.
Push token (uninstall tracking)
Metrixed.setPushToken(token) from FirebaseMessagingService.onNewToken; the token is announced once and rides later payloads.
Request signing and rotation
Every request carries X-Dt-App-Token, X-Dt-Timestamp (unix seconds) and, with a secret, X-Dt-Signature = hex(HMAC-SHA256(secret, timestamp + "." + body)). The implementation is asserted against sdks/test-vectors/signing.json
(shared with iOS, generated from the Go verifier). Rotate with config.setSecret(newId, secret) in a new app build (a different id
replaces a previously rotated secret) or at runtime with Metrixed.setSecret(id, secret); the server accepts the previous
secret while rotating. A 401 with a skewed device clock is repaired from the server Date header and resent once.
Offline queue
Everything is persisted (JSON lines, atomic rewrites) before sending: install/session/event/ad revenue/consent/erase survive
process death, keep their capture timestamps and are delivered in order. event/ad_revenue items are batched (default 50,
setEventBatchSize). Network errors, 408, 429, 5xx and per-item error results retry with exponential backoff (2 s doubling to
10 min, jittered); 413 halves the batch; other 4xx are dropped; items older than 58 days or beyond setMaxQueueSize (5000) are
discarded (oldest events first, never the install). setOfflineMode(true) keeps queueing without sending; flush() retries now.
Logging
config.setLogLevel(DTLogLevel.DEBUG); setLogSink { level, msg -> } to redirect (default logcat tag Metrixed).
Sample app, tests, CI
sdks/android/sample(buttons for events, ad revenue, consent, forget-me;-Pdt.baseUrl=... -Pdt.appToken=... -Pdt.secret=...).make sdk-android-test: JVM unit tests in the pinned Android SDK image (JDK 21, platform 34), builds the AAR, the sample and compilesandroidTest.make sdk-android-e2e: private ingest + the real engine over HTTP (signature enforced, referrer attribution, deferred link, validation)..github/workflows/sdk-android.yml:jvm(the above),emulator(runsconnectedDebugAndroidTeston an API 30 emulator),publish(tagandroid-v*).
Publishing to Maven Central
POM metadata, sources/javadoc jars and signing are configured in gradle/publish.gradle.kts. Provide
ossrhUsername, ossrhPassword, signingKey (ASCII-armored private key) and signingPassword in ~/.gradle/gradle.properties
(or ORG_GRADLE_PROJECT_* env), bump VERSION and SdkInfo.VERSION (a test checks they match), then
./gradlew publishReleasePublicationToCentralRepository. groupId com.metrixed must be verified with Sonatype first.
Not applicable on Android
GET /sdk/v1/config (conversion-value schema) and SKAdNetwork / AdAttributionKit are iOS-only; the Android SDK does not call them.