Skip to main content

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)
OptionDefaultDescription
apiKeyRequiredYour API key
baseUrlhttps://ingest.mostlygoodmetrics.comAPI endpoint
environment"production"Environment name
maxBatchSize100Events per batch (1-1000)
flushIntervalSeconds30Auto-flush interval
maxStoredEvents10000Max cached events
enableDebugLoggingfalseEnable logcat output
trackAppLifecycleEventstrueAuto-track lifecycle events
existingInstallationfalseBaseline lifecycle state on a provider migration without emitting $app_installed
contextProvidernullDynamic properties evaluated for every event
optedOutByDefaultfalseStart opted out until optIn() is called (Privacy)
collectDevicePropertiestrueCollect device model/type, manufacturer, locale, timezone

Automatic Events​

EventWhenProperties
$app_installedFirst launch after install$version
$app_updatedFirst launch after version change$version, $previous_version
$app_openedApp became active (foreground)-
$app_backgroundedApp 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:

FieldExampleDescription
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.