Skip to content

Repository files navigation

Experiment-KMP

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.

Supported platforms

Module Android JVM iOS Linux x64
core
client
server

Modules

  • core — shared public types: ExperimentUser, Variant, ServerZone.
  • client — remote evaluation for client apps: fetch variants, local caching, exposure tracking.
  • server — local evaluation for backends, using the shared evaluation engine.

Installation

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.

Usage

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.

Client SDK

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.

Server SDK

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()

Architecture

  • No duplicated evaluation logic. Flag configurations are deserialized directly into the engine's 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.
  • Platform code via expect/actual — only storage and the HTTP engine are platform-specific.

Development

./gradlew build          # build + test everything
./gradlew :server:jvmTest
./gradlew :client:testDebugUnitTest

Requirements: JDK 17+, Kotlin 2.3, Android SDK (for Android targets), a macOS host (for iOS targets).

Status & roadmap

  • ✅ Client (Android / JVM / iOS) and server (JVM / Linux) compile, link and test.
  • ✅ Correct server-side local evaluation (targeting / bucketing / dependencies), covered by tests.
  • ✅ API parity with 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.
  • ⏳ Cohort sync (server) — not yet implemented; flags targeting cohorts won't match without it.

iOS & CocoaPods

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.

License

MIT License — this project ports/wraps Amplitude's MIT-licensed SDKs. Copyright (c) Amplitude Inc. and contributors.

About

Kotlin Multiplatform SDK for Amplitude Experiment.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages