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.ktsdependencyResolutionManagement {repositories {google(); mavenCentral()maven("https://jitpack.io")}}// app/build.gradle.ktsdependencies {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.
kotlinimport com.cohorly.android.Cohorlyval cohorly = Cohorly.getInstance(context, "YOUR_PROJECT_TOKEN")cohorly.track("Plan Selected", mapOf("plan" to "pro"))cohorly.identify("user-123") // links the anonymous id via /aliascohorly.people.set("plan", "pro") // profile update via /engagecohorly.registerSuperProperties(mapOf("ab_test" to "variant_b"))cohorly.flush() // also auto-flushes every 5s / 20 events
API
| Member | Notes |
|---|---|
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 / clearSuperProperties | Super properties merged into every event. |
people.set / setOnce / increment / unset / deleteUser | Profile 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:
| Policy | Behavior |
|---|---|
networkOnly | Never 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. |
networkFirst | Prefers 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. |
persistenceUntilNetworkSuccess | Serves 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.
kotlinval 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.