Android SDK

Official Android client SDK - Kotlin, minSdk 21, zero third-party runtime dependencies. The API mirrors the Mixpanel Android SDK, so migrating is mostly a find-and-replace of MixpanelAPI to Cohorly.

Install

Distributed via JitPack from the public cohorly-io/cohorly-android repository:

kotlin
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
google(); mavenCentral()
maven("https://jitpack.io")
}
}
​
// app/build.gradle.kts
dependencies {
implementation("com.github.cohorly-io.cohorly-android:cohorly-android:v0.1.0")
}

The host app must declare the INTERNET permission.

Initialize

Call Cohorly.getInstance once, e.g. in Application.onCreate(). One instance per project token.

kotlin
import com.cohorly.android.Cohorly
​
val cohorly = Cohorly.getInstance(context, "YOUR_PROJECT_TOKEN")
​
cohorly.track("Plan Selected", mapOf("plan" to "pro"))
​
cohorly.identify("user-123") // links the anonymous id via /alias
cohorly.people.set("plan", "pro") // profile update via /engage
​
cohorly.registerSuperProperties(mapOf("ab_test" to "variant_b"))
​
cohorly.flush() // also auto-flushes every 5s / 20 events

API

MemberNotes
Cohorly.getInstance(context, token, apiHost[, config])Singleton per token. CohorlyConfig accepts flushIntervalMs, batchSize, maxQueueSize, trackAutomaticEvents.
track(name[, JSONObject | Map])Queue an event (persisted to SharedPreferences).
identify(id) / reset()Set the distinct id (sends /alias while anonymous) / new anonymous id on logout.
registerSuperProperties[Once] / unregisterSuperProperty / clearSuperPropertiesSuper properties merged into every event.
people.set / setOnce / increment / unset / deleteUserProfile updates sent to /engage.
optOutTracking() / optInTracking()GDPR opt-out; drops queued events while opted out.
flush()Send now, bypassing any retry backoff window.
getFeatureFlag(key[, fallback]) / isFeatureEnabled(key[, fallback])Read a flag's variant/enabled value; never hits the network.
getFeatureFlagDetails(key[, fallback])The value plus source (network / persistence / fallback) and cache metadata.
reloadFeatureFlags() / onFeatureFlags(listener)Re-evaluate flags on the worker thread / subscribe to updates.

Feature flags

Flags are evaluated on getInstance (opt out with CohorlyConfig(loadFeatureFlags = false)), on identify() and on reset(), always on the worker thread, and persisted to storage scoped by distinct id; reads never hit the network.

CohorlyConfig.flagPersistence (default FlagPersistenceMode.persistenceUntilNetworkSuccess) controls how reads resolve against the persisted cache:

PolicyBehavior
networkOnlyNever reads nor writes the cache on the serving path (identity changes still remove it - that is hygiene, not serving). Flags come only from network responses this session; a failed evaluation serves the fallback.
networkFirstPrefers the network, but reads are synchronous, so a valid cache (inside the TTL) is served immediately like persistenceUntilNetworkSuccess. Strict about freshness: a cache past the TTL is dropped, never served.
persistenceUntilNetworkSuccessServes a valid cache immediately, so startup doesn't flicker; the first network success takes over and re-persists. A failed evaluation keeps the cache serving. Trades freshness for offline availability: an expired cache still serves while inside CohorlyCore.FLAG_CACHE_MAX_STALE_MS (7 days).

The cache carries a stored_at timestamp and expires after CohorlyConfig.flagCacheTtlMs (default 24 hours - the Mixpanel reference default shared by every Cohorly SDK). A legacy cache with no timestamp cannot be dated, so it is served indefinitely rather than expired - the graceful-upgrade path preserving the pre-TTL behavior - until the next successful reload rewrites it in the stamped format. identify() and reset() clear the previous identity's flags before fetching the new identity's, so values never leak across users - onFeatureFlags listeners are notified of the clear with an empty map, then again when the new identity's flags land.

kotlin
val variant = cohorly.getFeatureFlag("checkout-copy", fallback = "control")
val details = cohorly.getFeatureFlagDetails("checkout-copy")
// details.source: FlagSource.network | .persistence | .fallback
// details.enabled / details.payload; a fallback result reports
// enabled = truthiness of the fallback and payload = null
// details.persistedAt / details.ttlMs when source == FlagSource.persistence

A read that resolves to the fallback reports no exposure. Reads of known flags emit $feature_flag_called once per (key, value) per identity, stamped with $feature_flag_source ("network" or "persistence") plus $feature_flag_persisted_at and $feature_flag_ttl_ms for persistence reads; the same value served first from persistence and then from the network is reported once.

Auto-captured properties

Every event includes $lib = "android", $lib_version, $os, $os_version, $manufacturer, $brand, $model, $screen_dpi, $screen_height, $screen_width, $app_version_string, $app_build_number, $carrier, $has_nfc, $has_telephone, $bluetooth_version and $google_play_services. $wifi and $bluetooth_enabled are only collected when the app holds the corresponding permission. Geo properties are derived server-side from the client IP.

Automatic events

Opt-in via trackAutomaticEvents = true (default off, matching the iOS and React Native SDKs): $ae_first_open (once ever), $ae_updated (on version change) and $ae_session with $ae_session_length when the app backgrounds after a session of 10s or more.

The offline queue survives process death (SharedPreferences, capped at 1000 events, oldest dropped) and follows the shared Cohorly retry contract: exponential backoff on 429/5xx/network errors honoring Retry-After, batch halving on 413, permanent drop on 400.

PreviousiOS
NextNode.js