
Client/server SDK mirroring Amplitude Experiment API — remote fetch, local evaluation, caching, exposure tracking, shared evaluation engine reuse, and analytics bridging for identity/exposure sync.
Kotlin Multiplatform SDK for Amplitude Experiment (feature flags & A/B testing).
It mirrors the public API of the official experiment-android-client / experiment-jvm-server
SDKs — same com.amplitude.experiment package and type names — so migrating an existing Android
or JVM project is (mostly) a matter of swapping the dependency. All evaluation logic is provided by
the shared, platform-agnostic evaluation engine; this SDK contains only the Experiment client/server
code, not a reimplementation of the engine.
| Module | Android | JVM | iOS | Linux x64 |
|---|---|---|---|---|
core |
✅ | ✅ | ✅ | ✅ |
client |
✅ | ✅ | ✅ | — |
server |
— | ✅ | — | ✅ |
ExperimentUser, Variant, ServerZone.dependencies {
// Client SDK (Android / JVM / iOS)
implementation("me.nathanfallet.amplitude:experiment-kmp-client:<version>")
// Server SDK (JVM / Linux)
implementation("me.nathanfallet.amplitude:experiment-kmp-server:<version>")
}The evaluation engine is pulled in transitively from
me.nathanfallet.amplitude:evaluation-core
— a fork of Amplitude's evaluation-core with iOS targets added.
The API is coroutine-based (suspend), which is idiomatic for Kotlin Multiplatform. Note the Android
SDK's Future-based async API is JVM-only and therefore cannot be reproduced verbatim in common code.
import com.amplitude.experiment.Experiment
import com.amplitude.experiment.ExperimentConfig
import com.amplitude.experiment.ExperimentUser
import com.amplitude.experiment.ServerZone
val config = ExperimentConfig.builder()
.debug(true)
.serverZone(ServerZone.US)
.build()
// suspend
val client = Experiment.initialize(apiKey = "your-deployment-key", config = config)
val user = ExperimentUser.builder()
.userId("user-id")
.deviceId("device-id")
.userProperty("premium", true)
.build()
// Fetch variants from the server (suspend)
client.fetch(user)
// Read a variant (with optional fallback)
val variant = client.variant("flag-key", Variant(value = "control"))
if (variant.value == "treatment") {
// ...
}
// All variants
val all = client.all()On Android, provide the platform Context via the config so variants are cached in
SharedPreferences:
val config = ExperimentConfig.builder()
.androidContext(applicationContext)
.build()Storage per platform: Android → SharedPreferences, iOS → NSUserDefaults, JVM → Preferences.
import com.amplitude.experiment.LocalEvaluation
import com.amplitude.experiment.LocalEvaluationConfig
import com.amplitude.experiment.ExperimentUser
val config = LocalEvaluationConfig.builder()
.debug(true)
.flagConfigPollerIntervalMillis(30_000)
.build()
// suspend
val client = LocalEvaluation.initialize(apiKey = "your-server-key", config = config)
// Fetch flag configs and start polling
client.start()
val user = ExperimentUser.builder().userId("user-id").build()
// Evaluate locally — targeting, bucketing and flag dependencies are all applied
val variant = client.evaluate(user, "flag-key")
val all = client.evaluateAll(user)
client.stop()EvaluationFlag type and evaluated by evaluation-core. The server maps ExperimentUser into the
engine's nested EvaluationContext and converts results back into the public Variant.expect/actual — only storage and the HTTP engine are platform-specific../gradlew build # build + test everything
./gradlew :server:jvmTest
./gradlew :client:testDebugUnitTestRequirements: JDK 17+, Kotlin 2.3, Android SDK (for Android targets), a macOS host (for iOS targets).
experiment-android-client (flat com.amplitude.experiment package, config options,
logging via logLevel/loggerProvider, customRequestHeaders). The async API is suspend (KMP-idiomatic)
rather than Android's JVM-only Future.initializeWithAmplitudeAnalytics — full Amplitude Analytics bridge on Android and iOS (since 1.0.1):
identity sync + exposure forwarding through the shared native analytics connector (the same one the
Amplitude Analytics SDK / amplitude-kmp writes to). In 1.0.0 it delegated to initialize.
On iOS this pulls in the AnalyticsConnector CocoaPod — see the note below.The iOS analytics bridge integrates the @objc AnalyticsConnector pod via Kotlin/Native cinterop.
The published artifact ships Kotlin Multiplatform klibs (consumed from another KMP project — no
XCFramework/SPM/podspec is published), and the iOS klib carries a cinterop dependency on the
AnalyticsConnector pod. So an iOS consumer must provide that pod at link time — regardless of
which code path runs (the connector code is inert at runtime if you only call Experiment.initialize,
but it is still a link input). The pod already ships with AmplitudeSwift / amplitude-kmp, so if you
use Amplitude Analytics on iOS you already have it.
Building/publishing the iOS targets therefore requires a macOS host with the CocoaPods CLI installed.
MIT License — this project ports/wraps Amplitude's MIT-licensed SDKs. Copyright (c) Amplitude Inc. and contributors.
Kotlin Multiplatform SDK for Amplitude Experiment (feature flags & A/B testing).
It mirrors the public API of the official experiment-android-client / experiment-jvm-server
SDKs — same com.amplitude.experiment package and type names — so migrating an existing Android
or JVM project is (mostly) a matter of swapping the dependency. All evaluation logic is provided by
the shared, platform-agnostic evaluation engine; this SDK contains only the Experiment client/server
code, not a reimplementation of the engine.
| Module | Android | JVM | iOS | Linux x64 |
|---|---|---|---|---|
core |
✅ | ✅ | ✅ | ✅ |
client |
✅ | ✅ | ✅ | — |
server |
— | ✅ | — | ✅ |
ExperimentUser, Variant, ServerZone.dependencies {
// Client SDK (Android / JVM / iOS)
implementation("me.nathanfallet.amplitude:experiment-kmp-client:<version>")
// Server SDK (JVM / Linux)
implementation("me.nathanfallet.amplitude:experiment-kmp-server:<version>")
}The evaluation engine is pulled in transitively from
me.nathanfallet.amplitude:evaluation-core
— a fork of Amplitude's evaluation-core with iOS targets added.
The API is coroutine-based (suspend), which is idiomatic for Kotlin Multiplatform. Note the Android
SDK's Future-based async API is JVM-only and therefore cannot be reproduced verbatim in common code.
import com.amplitude.experiment.Experiment
import com.amplitude.experiment.ExperimentConfig
import com.amplitude.experiment.ExperimentUser
import com.amplitude.experiment.ServerZone
val config = ExperimentConfig.builder()
.debug(true)
.serverZone(ServerZone.US)
.build()
// suspend
val client = Experiment.initialize(apiKey = "your-deployment-key", config = config)
val user = ExperimentUser.builder()
.userId("user-id")
.deviceId("device-id")
.userProperty("premium", true)
.build()
// Fetch variants from the server (suspend)
client.fetch(user)
// Read a variant (with optional fallback)
val variant = client.variant("flag-key", Variant(value = "control"))
if (variant.value == "treatment") {
// ...
}
// All variants
val all = client.all()On Android, provide the platform Context via the config so variants are cached in
SharedPreferences:
val config = ExperimentConfig.builder()
.androidContext(applicationContext)
.build()Storage per platform: Android → SharedPreferences, iOS → NSUserDefaults, JVM → Preferences.
import com.amplitude.experiment.LocalEvaluation
import com.amplitude.experiment.LocalEvaluationConfig
import com.amplitude.experiment.ExperimentUser
val config = LocalEvaluationConfig.builder()
.debug(true)
.flagConfigPollerIntervalMillis(30_000)
.build()
// suspend
val client = LocalEvaluation.initialize(apiKey = "your-server-key", config = config)
// Fetch flag configs and start polling
client.start()
val user = ExperimentUser.builder().userId("user-id").build()
// Evaluate locally — targeting, bucketing and flag dependencies are all applied
val variant = client.evaluate(user, "flag-key")
val all = client.evaluateAll(user)
client.stop()EvaluationFlag type and evaluated by evaluation-core. The server maps ExperimentUser into the
engine's nested EvaluationContext and converts results back into the public Variant.expect/actual — only storage and the HTTP engine are platform-specific../gradlew build # build + test everything
./gradlew :server:jvmTest
./gradlew :client:testDebugUnitTestRequirements: JDK 17+, Kotlin 2.3, Android SDK (for Android targets), a macOS host (for iOS targets).
experiment-android-client (flat com.amplitude.experiment package, config options,
logging via logLevel/loggerProvider, customRequestHeaders). The async API is suspend (KMP-idiomatic)
rather than Android's JVM-only Future.initializeWithAmplitudeAnalytics — full Amplitude Analytics bridge on Android and iOS (since 1.0.1):
identity sync + exposure forwarding through the shared native analytics connector (the same one the
Amplitude Analytics SDK / amplitude-kmp writes to). In 1.0.0 it delegated to initialize.
On iOS this pulls in the AnalyticsConnector CocoaPod — see the note below.The iOS analytics bridge integrates the @objc AnalyticsConnector pod via Kotlin/Native cinterop.
The published artifact ships Kotlin Multiplatform klibs (consumed from another KMP project — no
XCFramework/SPM/podspec is published), and the iOS klib carries a cinterop dependency on the
AnalyticsConnector pod. So an iOS consumer must provide that pod at link time — regardless of
which code path runs (the connector code is inert at runtime if you only call Experiment.initialize,
but it is still a link input). The pod already ships with AmplitudeSwift / amplitude-kmp, so if you
use Amplitude Analytics on iOS you already have it.
Building/publishing the iOS targets therefore requires a macOS host with the CocoaPods CLI installed.
MIT License — this project ports/wraps Amplitude's MIT-licensed SDKs. Copyright (c) Amplitude Inc. and contributors.