Android SDK
A lightweight Android SDK for tracking analytics events.
Requirements
- Android SDK 21+ (Android 5.0 Lollipop)
- Kotlin 1.9+
Installation
Use Android SDK 0.7.1 or later for the background failure containment and bounded event storage described below.
Gradle (Kotlin DSL)
dependencies {
implementation("com.github.Mostly-Good-Metrics:mostly-good-metrics-android-sdk:0.7.1")
}
Add JitPack repository to your settings.gradle.kts:
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
Quick Start
Initialize
Initialize once in your Application class:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
MostlyGoodMetrics.configure(this, "mgm_proj_your_api_key")
}
}
Track Events
// Simple event
MostlyGoodMetrics.track("button_clicked")
// Event with properties
MostlyGoodMetrics.track("purchase_completed", mapOf(
"product_id" to "SKU123",
"price" to 29.99,
"currency" to "USD"
))
Identify Users
// Set user identity
MostlyGoodMetrics.identify("user_123")
// Reset identity (e.g., on logout)
MostlyGoodMetrics.resetIdentity()
Configuration Options
val config = MGMConfiguration.Builder("mgm_proj_your_api_key")
.baseUrl("https://ingest.mostlygoodmetrics.com")
.environment("production")
.maxBatchSize(100)
.flushIntervalSeconds(30)
.maxStoredEvents(10000)
.enableDebugLogging(BuildConfig.DEBUG)
.trackAppLifecycleEvents(true)
.build()
MostlyGoodMetrics.configure(this, config)
| Option | Default | Description |
|---|---|---|
apiKey | Required | Your API key |
baseUrl | https://ingest.mostlygoodmetrics.com | API endpoint |
environment | "production" | Environment name |
maxBatchSize | 100 | Events per batch (1-1000) |
flushIntervalSeconds | 30 | Auto-flush interval |
maxStoredEvents | 10000 | Max cached events |
enableDebugLogging | false | Enable logcat output |
trackAppLifecycleEvents | true | Auto-track lifecycle events |
existingInstallation | false | Baseline lifecycle state on a provider migration without emitting $app_installed |
contextProvider | null | Dynamic properties evaluated for every event |
optedOutByDefault | false | Start opted out until optIn() is called (Privacy) |
collectDeviceProperties | true | Collect device model/type, manufacturer, locale, timezone |
Automatic Events
| Event | When | Properties |
|---|---|---|
$app_installed | First launch after install | $version |
$app_updated | First launch after version change | $version, $previous_version |
$app_opened | App became active (foreground) | - |
$app_backgrounded | App resigned active (background) | - |
Migration and dynamic context
For an existing app moving from another provider, derive existingInstallation
from that provider's persisted installation marker. Do not enable it for
every user in a migration release, which would suppress genuine new installs.
val config = MGMConfiguration.Builder("mgm_proj_your_api_key")
.existingInstallation(legacyInstallMarkerExists())
.contextProvider { mapOf("active_workspace_id" to currentWorkspaceId()) }
.build()
Dynamic context overrides super properties; explicit event properties and MGM
system properties take precedence. DEBUG builds report invalid event names and
reserved custom $ property keys.
Automatic Context
Every event automatically includes:
| Field | Example | Description |
|---|---|---|
platform | "android" | Platform |
os_version | "14" | Android version |
app_version | "1.0.0 (42)" | App version with build number |
session_id | "uuid..." | Unique session ID |
user_id | "user_123" | User ID (if set) |
$device_type | "phone" | Device type (phone, tablet, tv, watch) |
$device_model | "Pixel 8" | Device model |
Callback threads and failure handling
contextProvider runs synchronously on the thread calling track(). It can run
on background threads and concurrently. Return an immutable snapshot or use a
synchronized store; do not read Android views directly from this callback.
MostlyGoodMetrics.flush { result ->
// This optional completion runs on the main thread.
result.onFailure { error -> /* report the delivery failure */ }
}
Event delivery stays in the background. MGM contains ordinary exceptions from the provider, property conversion, background work, and the flush completion. Recursive property containers are bounded rather than recursively exhausting the stack. Cancellation of SDK coroutine work and fatal JVM errors retain their normal behavior. A completion that throws is contained.
The built-in event stores also enforce a 1 MiB estimated event-data ceiling in
addition to maxStoredEvents. Oldest events are dropped when either limit is
reached; oversized events and damaged or oversized persisted queues may be
discarded. Automatic flush requests are coalesced during bursts. These limits
protect the host while offline or when storage is slow, so analytics delivery
remains best effort.
Privacy
The SDK never accesses the Advertising ID (AAID), location, contacts, or any other personal data from the device. identify() is entirely optional — without it, users are tracked only under a random, resettable anonymous ID ($anon_...).
Opt-out
MostlyGoodMetrics.optOut() // stops tracking immediately, purges queued events
MostlyGoodMetrics.optIn() // re-enables tracking
MostlyGoodMetrics.isOptedOut // current state
While opted out, track(), identify(), and flush() are no-ops, any queued (unsent) events are deleted, and the choice is persisted across app restarts.
For consent-first apps (e.g. GDPR), start opted out and only begin tracking after consent:
val config = MGMConfiguration.Builder("mgm_proj_your_api_key")
.optedOutByDefault(true)
.build()
MostlyGoodMetrics.configure(this, config)
// Later, once the user consents:
MostlyGoodMetrics.optIn()
A persisted opt-in/opt-out choice always wins over optedOutByDefault on subsequent launches.
Rotating the anonymous ID
Rotate the anonymous ID at any time so future events can't be linked to prior activity:
MostlyGoodMetrics.resetAnonymousId()
Forget me
resetIdentity() clears the user ID. Pass clearAnonymousId = true for a full reset — for example, when a user asks to be forgotten:
MostlyGoodMetrics.resetIdentity(clearAnonymousId = true)
This clears the user ID, rotates the anonymous ID, purges all pending (unsent) events and super properties, and starts a new session.
Limiting device properties
val config = MGMConfiguration.Builder("mgm_proj_your_api_key")
.collectDeviceProperties(false)
.build()
When disabled, events omit $device_model, $device_type, device_manufacturer, locale, and timezone. Platform, OS version, and app version are still included.
Java Interop
// Initialize
MostlyGoodMetrics.configure(context, "mgm_proj_your_api_key");
// Track
Map<String, Object> props = new HashMap<>();
props.put("button_name", "submit");
MostlyGoodMetrics.track("button_clicked", props);
// Identify
MostlyGoodMetrics.identify("user_123");
ProGuard / R8
The SDK includes consumer ProGuard rules. No additional configuration needed.