Kotlin
The Shipeasy Kotlin server SDK — coroutine init, JVM/Android, local evaluation, configs, experiments, and tracking.
Server-side flags, configs, experiments, and tracking for Kotlin (JVM/Android-compatible). Evaluates locally against a background-polled blob — see the shared evaluation model.
Install
implementation("ai.shipeasy:shipeasy-kotlin:0.3.0")Configure
Call configure() once at startup with the server key, plus an optional attributes transform that maps your user object onto the Shipeasy attribute map.
import ai.shipeasy.configure
import ai.shipeasy.Client
configure(
System.getenv("SHIPEASY_SERVER_KEY"),
attributes = { u -> mapOf("user_id" to u.id, "plan" to u.plan) },
)Bind a client to the user
Construct a lightweight Client from your user object — the attributes transform turns it into the evaluation context, so the getters take no user argument:
val flags = Client(currentUser)Evaluate a feature flag
flags.getFlag("new_checkout")An optional default is returned only when the gate can't be evaluated (client not ready or gate absent) — a gate that evaluates false returns false:
flags.getFlag("new_checkout", default = true)For the value plus the reason, use getFlagDetail — see Evaluation reasons:
val d: FlagDetail = flags.getFlagDetail("new_checkout")
d.value // Boolean
d.reason // one of the Reason constantsRead a dynamic config
flags.getConfig("billing_copy")
flags.getConfig("billing_copy", default = "Pay now")Resolve an experiment
val r = flags.getExperiment("checkout_button", mapOf("color" to "blue"))
if (r.inExperiment) {
r.group // "treatment"
r.params // {color=...}
}Track an event
flags.track("purchase", mapOf("amount" to 49))Anonymous visitors
AnonIdFilter is a servlet Filter that mints the shared __se_anon_id cookie so logged-out traffic buckets like the browser:
@Bean
fun shipeasyAnonId() = FilterRegistrationBean(AnonIdFilter())
Client(anonUser).getFlag("new_checkout") // buckets on the cookiejakarta.servlet-api is compileOnly. Non-servlet stacks (Ktor, http4k) use the AnonId primitives directly.
Testing
Engine.forTesting() does zero network — no key, init/track are no-ops. Seed with override* and reset with clearOverrides. Engine is AutoCloseable — wrap it in use { }. See Testing.
Engine.forTesting().use { engine ->
engine.overrideFlag("new_checkout", true)
engine.getFlag("new_checkout", emptyMap()) // true
engine.clearOverrides()
}Errors & feedback
Report a handled exception with see() so it folds into the errors primitive with a one-sentence consequence — what feature broke and how it degraded. see() rides the same configure() boot (server key); there is no separate error SDK or second key.
try {
chargeCard(order, prices)
} catch (e: Exception) {
Shipeasy.see(e).causesThe("checkout").to("use cached prices")
.extras(mapOf("order_id" to order.id))
}Use Shipeasy.seeViolation("large query") for a non-exception problem. Full grammar — consequence phrasing, control-flow exceptions, anti-patterns — is in Error reporting with see().
The in-app bug & feature report overlay is a standalone <script> tag you drop into your frontend — platform-agnostic, no server SDK required. See The devtools overlay.