e-volv
Docs menu

Android

Not released yet. io.e-volv:logs-android 0.1.0 is on the registry as a preview, but its conformance run on devices has not completed, so the Android SDK is not marked available. We recommend waiting for this notice to clear before shipping it. The SDKs index shows what can be installed today.

io.e-volv:logs-android is the e-volv Launch SDK for Android apps (minSdk 24). It is a client SDK: with a public key (evk_pub_…) it receives evaluated values for one context — never targeting rules — so a flag check is synchronous, never throws and never blocks on the network. A server (evk_) key is refused at init with an IllegalArgumentException: it would be readable by anyone who unpacks the APK. Values ride the existing Observer client — one init, one key, one OkHttp transport — with a poll (client SDKs never stream), a disk cache that keeps serving the last values when the control plane is unreachable, and exposure tracking on the same buffer as your logs. The behaviour is pinned by the Launch SDK contract.

Install

kotlin
// app/build.gradle.kts — pin the current release where the snippet says 0.1.0
implementation("io.e-volv:logs-android:0.1.0") // brings the JVM core transitively

io.e-volv:logs-android 0.1.0 is on Maven Central as a preview: its device conformance run has not completed yet, so it is not marked available. We recommend waiting for the release notice at the top of this page to clear before shipping it in an app. The flags logic itself lives in the shared JVM core (io.e-volv:logs-jvm), so evaluation is unit- and conformance-tested on a plain JVM; the Android module adds the application id, install id and process lifecycle.

Native API

kotlin
import io.evolve.logs.android.EvolveAndroidOptions
import io.evolve.logs.android.EvolveLogsAndroid
import io.evolve.logs.flags.client.ClientFlagsOptions

// Application.onCreate — one init serves both logs and flags
EvolveLogsAndroid.init(
    this,
    EvolveAndroidOptions.builder()
        .key("evk_pub_…") // a public client key — a server evk_… key is refused here
        .url("https://api.e-volv.io/api/public/v1/logs")
        .service("shop-android")
        .environment("production")
        .release(BuildConfig.VERSION_NAME)
        .flags(
            ClientFlagsOptions.builder()
                .initialContext(mapOf("targetingKey" to user.id, "plan" to user.plan))
                .build(),
        )
        .build(),
)

val flags = EvolveLogsAndroid.flags() // or client.flags()

// Reads are synchronous, never throw and never perform I/O
if (flags.bool("checkout.new", false)) {
    showNewCheckout()
}

// The full answer when you need the variant and the reason:
val d = flags.detail("checkout.banner", "default")
// d.value, d.variant ('on' | 'off' | 'control' | …), d.reason
// ('RULE:0' | 'ROLLOUT' | 'TARGET_MATCH' | 'DEFAULT' | 'FLAG_NOT_FOUND' | …)

// On login/logout, re-identify: values for the new context are fetched and
// cached under the same key. The future completes with whether the held
// values are for this context.
flags.identify(mapOf("targetingKey" to nextUser.id, "plan" to nextUser.plan))

Evaluation methods: bool, string, number, json and detail — all synchronous, all safe. Client SDKs hold one current context set by identify, so the per-call context argument the methods accept is ignored: it exists for protocol symmetry with the server SDKs. A flag absent from the served values returns your default with FLAG_NOT_FOUND; a value of the wrong type returns your default with TYPE_MISMATCH (typed reads only — the untyped detail path is the json read and never reports it). A typed detail with the kind applied explicitly is also available: flags.detail("boolean", "checkout.new", false, emptyMap()).

Options

Flags are configured under the flags(...) key of the existing EvolveAndroidOptions.builder() — a ClientFlagsOptions:

OptionDefaultMeaning
enabledtrueStart the flags client. False gives a defaults-only handle.
urlderivedBase URL ending in /api/public/v1/flags; defaults to the origin of the Observer url option, else the e-volv cloud.
offlinefalseNever contact the control plane: bootstrap values or defaults only.
pollIntervalSeconds60 (min 15)Poll period. Client SDKs never stream.
cacheEnabledtrueKeep the last-known values on disk, in filesDir/evolve-logs/flags beside the log queue.
bootstrapnoneA bundled values map served before the first fetch completes.
initialContext{}The context the first fetch uses, until identify replaces it.
exposuresEnabledtrueRecord an exposure on each evaluation of a flag present in the values.
exposureSampleRate1Probability an evaluation is recorded; sent as sampleRate.
exposureDedupeWindowSeconds300Suppress repeats of the same (flag, variant, contextKind, subject) within the window; suppressed counts ride the next exposure.
sendAttributesfalseSend context attributes with exposures (private attributes are always removed).
privateAttributes[]Attribute names never sent anywhere.

Install check and lifecycle

kotlin
// Install check: is this key bound, and to which environment?
// (blocking — call it off the main thread)
val ping = flags.verify()
// { environment: "production", keyKind: "client", flags: 42, etag: "3f2a…" }
// null when the control plane did not answer.

// Optional: block until values confirmed for the current context arrive.
// ready(timeoutMs) is the only call that waits — never on the main thread;
// the SDK warns once when it is.
val ok = flags.ready(2_000)

// Subscribe to the keys whose values changed; close the handle to stop.
val sub = flags.onChange { changed -> refreshUi(changed) }

// When the held values were last confirmed or swapped in — epoch millis, or null
val at = flags.lastUpdatedAtMillis

// The process lifecycle is automatic: ON_STOP pauses polling and flushes
// exposures; ON_START resumes polling with one staleness-gated refresh.
kotlin
// On sign-out or teardown: stop the poll schedule and flush pending
// exposures. Idempotent.
client.close()

Every flags request carries x-evolve-app-id (your context.packageName, overridable with appId(...)) and x-evolve-install-id, and the control plane only answers for app ids on the environment key’s allowlist — edited under the environment’s Access action.

Troubleshooting by status code

Evaluation never breaks because of the control plane — it degrades. What each response means for the SDK (contract §2.6):

ResponseSDK behaviour
401, 404Key invalid or revoked, Launch not enabled for the workspace, or the key not bound to an environment. The SDK keeps serving held values or defaults, logs once, and retries every 5 minutes.
403 — lacks the scope flags:readFlags are disabled for this key with one warning; telemetry is unaffected. No retry until re-init.
403 — app id refusedThe key’s allowlist does not include this app id. One warning naming the environment’s Access action; defaults keep serving while the SDK retries every five minutes.
429Honours Retry-After (seconds). The delivery rate limit is 3,000 requests/min per key.
5xx, timeout, network errorOne warning per outage while the last held values keep serving; the next poll retries.

Guarantees

  • A flag check is local and synchronous — bounded by CPU, not the network; ready() is the only call that waits, and only when you ask it to. Never call it on the main thread.
  • An unreachable or slow control plane never changes an answer mid-flag: the last held values (or a valid cache, or your defaults) keep serving while the SDK backs off and retries.
  • Cold start reads the on-disk cache instantly and serves it until the first fetch for the current context completes — then swaps without a flicker for unchanged flags; a cache written for a different environment is dropped and overwritten.
  • Polling stops when the app backgrounds (ON_STOP) and resumes with one staleness-gated refresh on ON_START — a backgrounded app makes no flag requests; exposures flush as it goes.
  • Exposures are de-duplicated, sampled, capped at a 10,000-entry buffer (oldest dropped first) and retried with the same backoff as your logs — telemetry you can trust as much as the flag answers themselves.
  • A server key cannot ship by accident: init refuses one loudly, and an empty key or URL yields a no-op client and one warning — never a crash.

Status

Compiled, linted and unit-tested against the Android SDK: :android:assembleRelease, :android:testDebugUnitTest and :android:lintRelease pass (compileSdk 34, minSdk 24), and the instrumented conformance runner compiles. The flags core it wraps is conformance-tested on a plain JVM, and an API-level check keeps that core free of APIs newer than Android 7.0. It has not yet run on a device or emulator — do not treat this SDK as device-conformant until the emulator run is green.

The SDKs index lists every platform, the e-volv Launch page covers concepts and targeting, and the HTTP API page documents the wire format the SDK speaks.