
Unified wrapper for an analytics platform, offering event and revenue tracking, type-safe identify (150+ overloads), plugin extensibility, async-friendly extensions, thread-safe offline and session handling.
Official Kotlin Multiplatform SDK for Amplitude Analytics.
The Amplitude KMP SDK brings the power of Amplitude Analytics to your multiplatform Kotlin projects with 100% API compatibility with the Android SDK. Migrate from the Android SDK with import-only changes - no code refactoring required.
Hello Amplitude Team, I've made this Kotlin Multiplatform SDK to help developers use Amplitude in their multiplatform
apps because of this issue. Please consider officially
supporting and maintaining this SDK as part of the Amplitude family. I'll be happy to talk about this, you can reach me
out from my GitHub profile. We could transfer this repository to the Amplitude
organization and publish it under the official com.amplitude Maven group. Thank you!
| Platform | Minimum Version |
|---|---|
| Android | API 21 (Android 5.0) |
| iOS | iOS 13.0 |
| iOS Simulator | iOS 13.0 |
Add to your build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("digital.guimauve.amplitude:analytics-kmp:1.0.2")
}
}
}dependencies {
implementation("digital.guimauve.amplitude:analytics-kmp:1.0.2")
}The KMP SDK wraps the native Amplitude Swift SDK. Add it via SPM in your Xcode project:
https://github.com/amplitude/Amplitude-Swift
Then add your Kotlin framework to the Xcode project via Gradle's embedAndSignAppleFrameworkForXcode task.
Create platform-agnostic configuration:
// commonMain/AmplitudeConfig.kt
expect val amplitudeApiKey: String
expect val platformContext: Any? // Android Context or null for iOSImplement for each platform:
// androidMain/AmplitudeConfig.android.kt
import android.content.Context
internal var androidContext: Context? = null
actual val amplitudeApiKey: String = "YOUR_ANDROID_API_KEY"
actual val platformContext: Any? get() = androidContext
// Set in Application.onCreate()
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
androidContext = applicationContext
}
}// iosMain/AmplitudeConfig.ios.kt
actual val amplitudeApiKey: String = "YOUR_IOS_API_KEY"
actual val platformContext: Any? = null // iOS doesn't need contextInitialize Amplitude once in shared code:
// commonMain - In your App composable or init function
import com.amplitude.kmp.Amplitude
import com.amplitude.kmp.Configuration
import com.amplitude.kmp.AutocaptureOption
val configuration = Configuration(
apiKey = amplitudeApiKey,
androidContext = platformContext // Only used on Android
).apply {
autocapture = setOf(
AutocaptureOption.SESSIONS,
AutocaptureOption.APP_LIFECYCLES,
AutocaptureOption.SCREEN_VIEWS
)
flushQueueSize = 30
flushIntervalMillis = 30000
}
val amplitude = Amplitude(configuration)
amplitude.setUserId("user-123")// Track simple event
amplitude.track("Button Clicked")
// Track event with properties
amplitude.track(
"Purchase Completed", mapOf(
"amount" to 99.99,
"currency" to "USD",
"items" to 3
)
)
// Set user properties
val identify = Identify()
.set("email", "user@example.com")
.set("plan", "premium")
.add("loginCount", 1)
amplitude.identify(identify)
// Track revenue
val revenue = Revenue()
revenue.price = 9.99
revenue.productId = "premium_subscription"
revenue.quantity = 1
amplitude.revenue(revenue)Android: Store context in Application.onCreate()
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
androidContext = applicationContext
}
}iOS: No additional setup needed! Just use the shared Kotlin code.
// Simple event
amplitude.track("Button Clicked")
// Event with properties
amplitude.track(
"Video Played", mapOf(
"video_id" to "12345",
"duration" to 180,
"quality" to "HD"
)
)
// Event with BaseEvent object
val event = BaseEvent(
eventType = "Purchase",
eventProperties = mutableMapOf(
"amount" to 49.99,
"items" to listOf("item1", "item2")
)
)
amplitude.track(event)
// Event with callback
amplitude.track(event) { event, status, message ->
if (status == 200) {
println("Event tracked successfully")
} else {
println("Failed: $message")
}
}The Identify API provides 150+ type-safe methods for user property operations:
val identify = Identify()
// Set properties
.set("email", "user@example.com")
.set("age", 25)
.set("premium", true)
// Set once (only if not already set)
.setOnce("initial_source", "google")
// Increment numeric properties
.add("login_count", 1)
.add("credits", 100)
// Array operations
.append("interests", "music")
.prepend("notifications", "new_feature")
.preInsert("tags", "vip") // Insert at start if not present
.postInsert("tags", "active") // Insert at end if not present
.remove("tags", "inactive")
// Unset properties
.unset("temporary_flag")
amplitude.identify(identify)
// Simple identify with map
amplitude.identify(
mapOf(
"email" to "user@example.com",
"plan" to "premium"
)
)Supported types for each operation (14-15 overloads):
Boolean, Double, Float, Int, Long, String
Map<String, Any>, List<Any>
Array<Boolean>, Array<Double>, Array<Float>, Array<Int>, Array<Long>, Array<String>
Any (for set/setOnce only)// Create revenue event
val revenue = Revenue()
revenue.productId = "premium_plan_monthly"
revenue.price = 9.99
revenue.quantity = 1
revenue.currency = "USD"
revenue.revenueType = "subscription"
// With receipt validation (Android)
revenue.receipt = "receipt_data"
revenue.receiptSig = "signature"
// Add custom properties
revenue.properties = mutableMapOf(
"plan_name" to "Premium",
"billing_cycle" to "monthly"
)
// Track the revenue
amplitude.revenue(revenue)
// Verify revenue is valid
if (revenue.isValid()) {
amplitude.revenue(revenue)
} else {
println("Invalid revenue: price is required")
}// Set user ID
amplitude.setUserId("user_12345")
// Get user ID
val userId = amplitude.getUserId()
// Set device ID
amplitude.setDeviceId("device_67890")
// Get device ID
val deviceId = amplitude.getDeviceId()
// Reset user (clears userId, generates new deviceId)
amplitude.reset()
// Get current session ID
val sessionId = amplitude.sessionId// Set user to a single group
amplitude.setGroup("org_id", "amplitude")
// Set user to multiple groups
amplitude.setGroup("team_id", arrayOf("engineering", "product"))
// Set group properties
amplitude.groupIdentify(
groupType = "org_id",
groupName = "amplitude",
groupProperties = mapOf(
"plan" to "enterprise",
"employees" to 500
)
)
// Set group properties with Identify
amplitude.groupIdentify(
groupType = "org_id",
groupName = "amplitude",
identify = Identify()
.set("industry", "analytics")
.add("num_projects", 1)
)// Sessions are tracked automatically
// Access current session ID
val sessionId = amplitude.sessionId
// Configure session timeout (default: 5 minutes)
val amplitude = Amplitude(apiKey, context) {
minTimeBetweenSessionsMillis = 300000 // 5 minutes
}
// Session events are tracked automatically
// Listen for: Amplitude.START_SESSION_EVENT and Amplitude.END_SESSION_EVENTAll main methods return the Amplitude instance for fluent chaining:
amplitude
.setUserId("user_123")
.track("App Opened")
.identify(Identify().set("last_seen", System.currentTimeMillis()))
.flush()// Flush events manually (normally happens automatically)
amplitude.flush()Optional suspend function extensions for async operations:
import com.amplitude.kmp.ktx.*
suspend fun trackUserJourney() {
// Track with Result return type
amplitude.awaitTrack(
eventType = "Signup Started",
eventProperties = mapOf("source" to "homepage")
).onSuccess {
println("Event tracked!")
}.onFailure { error ->
println("Failed: ${error.message}")
}
// Identify user
val identify = Identify()
.set("email", "user@example.com")
.set("signup_date", System.currentTimeMillis())
amplitude.awaitIdentify(identify)
// Track revenue
val revenue = Revenue()
revenue.price = 49.99
revenue.productId = "premium_plan"
amplitude.awaitRevenue(revenue)
// Flush and wait for completion
amplitude.awaitFlush()
// Other suspend operations
amplitude.awaitSetUserId("user_123")
amplitude.awaitSetDeviceId("device_456")
amplitude.awaitReset()
}Extend functionality with custom plugins:
import com.amplitude.kmp.plugins.Plugin
import com.amplitude.kmp.events.BaseEvent
class CustomPlugin : Plugin {
override val type = Plugin.Type.ENRICHMENT
override fun setup(amplitude: Amplitude) {
println("Plugin initialized")
}
override fun execute(event: BaseEvent): BaseEvent? {
// Enrich event with custom data
event.eventProperties?.put("app_version", "1.0.2")
event.eventProperties?.put("build_number", "123")
return event
}
}
// Add plugin
amplitude.add(CustomPlugin())
// Remove plugin
amplitude.remove(plugin)Plugin Types:
BEFORE - Runs before event processingENRICHMENT - Enriches events with additional dataDESTINATION - Sends events to custom destinationsOBSERVE - Observes events without modificationUTILITY - Utility pluginsComprehensive configuration for all use cases:
val amplitude = Amplitude(apiKey = "KEY", context = context) {
// Flushing
flushQueueSize = 30 // Events before auto-flush
flushIntervalMillis = 30000 // Auto-flush interval (30s)
flushMaxRetries = 5 // Max retry attempts
flushEventsOnClose = true // Flush on SDK close
// Server & API
serverZone = ServerZone.US // US or EU data center
serverUrl = "https://custom.api.com" // Custom API endpoint
useBatch = false // Use batch API
// Logging
logLevel = LogLevel.WARN // Logging level
// Levels: NONE, ERROR, WARN, LOG, DEBUG, VERBOSE
// Session Management
minTimeBetweenSessionsMillis = 300000 // Session timeout (5min)
trackingSessionEvents = true // Auto-track sessions
// Privacy & Tracking
optOut = false // Opt-out of tracking
trackingOptions = TrackingOptions() // Customize tracked fields
enableCoppaControl = false // COPPA compliance mode
// User & Device
deviceId = "custom-device-id" // Override device ID
minIdLength = 5 // Min length for IDs
// Auto-capture (Android)
autocapture = setOf(
AutocaptureOption.SESSIONS, // Session events
AutocaptureOption.APP_LIFECYCLES, // App lifecycle
AutocaptureOption.SCREEN_VIEWS, // Screen views
AutocaptureOption.ELEMENT_INTERACTIONS, // Click events
)
// Android-specific
useAdvertisingIdForDeviceId = false // Use Android Ad ID
useAppSetIdForDeviceId = false // Use App Set ID
locationListening = false // Track location
migrateLegacyData = true // Migrate old SDK data
// Advanced
offline = false // Offline mode
callback = { event, status, message -> // Global callback
println("Event: ${event.eventType}, Status: $status")
}
partnerId = "partner_123" // Partner attribution
plan = Plan(...) // Tracking plan
ingestionMetadata = IngestionMetadata(...) // Custom metadata
}Control which fields are tracked:
val trackingOptions = TrackingOptions()
.disableIpAddress()
.disableLatLng()
.disableCity()
.disableCountry()
.disableRegion()
.disableCarrier()
.disableDeviceBrand()
.disableDeviceModel()
val amplitude = Amplitude(apiKey, context) {
trackingOptions = trackingOptions
}
// COPPA-compliant tracking
val coppaOptions = TrackingOptions.forCoppaControl()Customize individual events:
val options = EventOptions().apply {
userId = "custom_user_123"
deviceId = "custom_device_456"
timestamp = System.currentTimeMillis()
sessionId = 1234567890
locationLat = 37.7749
locationLng = -122.4194
ip = "192.168.1.1"
plan = Plan(...)
callback = { event, status, message ->
println("Custom callback")
}
}
amplitude.track("Custom Event", properties, options)The KMP SDK maintains 100% API compatibility with the Android SDK. Migration requires ONLY import changes.
// Remove Android SDK
// implementation("com.amplitude:analytics-android:X.X.X")
// Add KMP SDK
implementation("digital.guimauve.amplitude:analytics-kmp:1.0.2")// Before (Android SDK)
import com.amplitude.android.Amplitude
import com.amplitude.android.Configuration
import com.amplitude.core.events.Identify
import com.amplitude.core.events.Revenue
import com.amplitude.core.events.BaseEvent
// After (KMP SDK) - ONLY CHANGE NEEDED
import com.amplitude.kmp.Amplitude
import com.amplitude.kmp.Configuration
import com.amplitude.kmp.events.Identify
import com.amplitude.kmp.events.Revenue
import com.amplitude.kmp.events.BaseEventYour code works identically:
// This code is IDENTICAL for both SDKs
val amplitude = Amplitude(apiKey = "KEY", context = context)
amplitude.track("Button Clicked", mapOf("color" to "blue"))
val identify = Identify()
.set("email", "user@example.com")
.add("loginCount", 1)
amplitude.identify(identify)
val revenue = Revenue()
revenue.price = 9.99
revenue.productId = "item_123"
revenue.quantity = 2
amplitude.revenue(revenue)
amplitude.setUserId("user_123").flush()No other changes required! 🎉
analytics-android to analytics-kmp
com.amplitude.android.* to com.amplitude.kmp.*
com.amplitude.core.* to com.amplitude.kmp.*
Initialize Amplitude as early as possible in your app lifecycle:
class MyApplication : Application() {
lateinit var amplitude: Amplitude
override fun onCreate() {
super.onCreate()
amplitude = Amplitude(apiKey = "KEY", context = this)
}
}fun onUserLogin(userId: String) {
amplitude.setUserId(userId)
amplitude.identify(
Identify()
.set("login_time", System.currentTimeMillis())
.add("login_count", 1)
)
}// Track key user actions
amplitude.track("Onboarding Started")
amplitude.track("Profile Created")
amplitude.track("First Purchase")
amplitude.track("Feature Discovered", mapOf("feature" to "export"))// Set properties that describe the user
val identify = Identify()
.set("plan", "premium")
.set("account_age_days", 30)
.set("features_enabled", listOf("export", "collaboration"))
.add("sessions_count", 1)
amplitude.identify(identify)val revenue = Revenue()
revenue.price = 9.99
revenue.productId = "premium_monthly"
if (revenue.isValid()) {
amplitude.revenue(revenue)
} else {
println("Revenue event invalid: price is required")
}Problem: IllegalArgumentException: androidContext must be android.content.Context
Solution: Make sure you pass an Android Context on Android:
// Correct
val amplitude = Amplitude(apiKey = "KEY", context = applicationContext)
// Incorrect
val amplitude = Amplitude(apiKey = "KEY", context = null)Problem: CocoaPods dependency not found
Solution: Make sure CocoaPods are installed:
cd iosApp
pod installCommon causes:
optOut = false
amplitude.flush() or wait for auto-flush// Enable debug logging to see what's happening
val amplitude = Amplitude(apiKey, context) {
logLevel = LogLevel.DEBUG
}The SDK is thread-safe. You can call methods from any thread:
// Safe to call from background threads
GlobalScope.launch {
amplitude.track("Background Event")
}Make sure to use application context, not activity context:
// Good
Amplitude(apiKey, applicationContext)
// Bad - may cause memory leak
Amplitude(apiKey, activityContext)See API Compatibility Issues Report for known issues and fixes in progress.
Goal: The KMP SDK must have identical API to the Android SDK. Migration should require ONLY import changes - no code modifications.
If you encounter any API differences after migrating from the Android SDK (beyond the known issues listed in the report above), please:
We treat API compatibility breaks as critical bugs and will fix them with high priority.
Expected: Change imports only, everything else stays the same
// ONLY these should change:
import com.amplitude.android.* → import com.amplitude.kmp.*
import com.amplitude.core.* → import com.amplitude.kmp.*
Not Expected: Any code changes, refactoring, or API adjustments
// Initialization
Amplitude(apiKey: String, context: Context, configs: Configuration.() -> Unit = {}) // Android
Amplitude(apiKey: String, configs: Configuration.() -> Unit = {}) // iOS
Amplitude(configuration: Configuration) // Both
// Event Tracking
track(eventType: String, eventProperties: Map< String, Any?>? = null, options: EventOptions? = null): Amplitude
track(event: BaseEvent, options: EventOptions? = null, callback: EventCallBack? = null): Amplitude
// User Identity
identify(userProperties: Map< String, Any?>?, options: EventOptions? = null): Amplitude
identify(identify: Identify, options: EventOptions? = null): Amplitude
setUserId(userId: String?): Amplitude
getUserId(): String?
// Device Management
setDeviceId(deviceId: String): Amplitude
getDeviceId(): String?
reset(): Amplitude
// Revenue
revenue(revenue: Revenue, options: EventOptions? = null): Amplitude
revenue(event: RevenueEvent): Amplitude
// Groups
setGroup(groupType: String, groupName: String, options: EventOptions? = null): Amplitude
setGroup(groupType: String, groupName: Array< String >, options: EventOptions? = null): Amplitude
groupIdentify(
groupType: String,
groupName: String,
groupProperties: Map< String, Any?>?, options: EventOptions? = null): Amplitude
groupIdentify(groupType: String, groupName: String, identify: Identify, options: EventOptions? = null): Amplitude
// Plugins
add(plugin: Plugin): Amplitude
remove(plugin: Plugin): Amplitude
// Flushing
flush()
// Properties
val sessionId: Long // Read-only current session ID
// Constants
Amplitude.START_SESSION_EVENT // "session_start"
Amplitude.END_SESSION_EVENT // "session_end"amplitude-kmp/
├── kmp/ # Main KMP module
│ ├── src/
│ │ ├── commonMain/ # Platform-agnostic code
│ │ │ ├── kotlin/com/amplitude/kmp/
│ │ │ │ ├── Amplitude.kt # Main API
│ │ │ │ ├── Configuration.kt
│ │ │ │ ├── events/ # Event models
│ │ │ │ ├── plugins/ # Plugin system
│ │ │ │ ├── ktx/ # Coroutines extensions
│ │ │ │ └── utils/ # Utilities
│ │ ├── androidMain/ # Android implementation
│ │ │ ├── kotlin/com/amplitude/kmp/
│ │ │ │ ├── Amplitude.android.kt
│ │ │ │ └── mappings/android/ # Type mappers
│ │ ├── iosMain/ # iOS implementation
│ │ │ ├── kotlin/com/amplitude/kmp/
│ │ │ │ ├── Amplitude.ios.kt
│ │ │ │ └── mappings/ios/ # Type converters
│ │ ├── androidUnitTest/ # Android tests
│ │ └── iosTest/ # iOS tests
│ └── build.gradle.kts
├── samples/ # Sample applications
├── reports/ # Documentation & analysis
└── README.md # This file
./gradlew build./gradlew test
./gradlew connectedAndroidTest # Android instrumentation tests./gradlew publishToMavenLocalThis project follows Kotlin Coding Conventions.
This KMP SDK wraps the following native SDKs for optimal performance:
com.amplitude:analytics-android:2.40.1
Amplitude-Swift:1.16.2
We welcome contributions! To contribute:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Please see CONTRIBUTING.md for detailed guidelines.
See CHANGELOG.md for release history.
This project is licensed under the MIT License - see the LICENSE file for details.
Official Kotlin Multiplatform SDK for Amplitude Analytics.
The Amplitude KMP SDK brings the power of Amplitude Analytics to your multiplatform Kotlin projects with 100% API compatibility with the Android SDK. Migrate from the Android SDK with import-only changes - no code refactoring required.
Hello Amplitude Team, I've made this Kotlin Multiplatform SDK to help developers use Amplitude in their multiplatform
apps because of this issue. Please consider officially
supporting and maintaining this SDK as part of the Amplitude family. I'll be happy to talk about this, you can reach me
out from my GitHub profile. We could transfer this repository to the Amplitude
organization and publish it under the official com.amplitude Maven group. Thank you!
| Platform | Minimum Version |
|---|---|
| Android | API 21 (Android 5.0) |
| iOS | iOS 13.0 |
| iOS Simulator | iOS 13.0 |
Add to your build.gradle.kts:
kotlin {
sourceSets {
commonMain.dependencies {
implementation("digital.guimauve.amplitude:analytics-kmp:1.0.2")
}
}
}dependencies {
implementation("digital.guimauve.amplitude:analytics-kmp:1.0.2")
}The KMP SDK wraps the native Amplitude Swift SDK. Add it via SPM in your Xcode project:
https://github.com/amplitude/Amplitude-Swift
Then add your Kotlin framework to the Xcode project via Gradle's embedAndSignAppleFrameworkForXcode task.
Create platform-agnostic configuration:
// commonMain/AmplitudeConfig.kt
expect val amplitudeApiKey: String
expect val platformContext: Any? // Android Context or null for iOSImplement for each platform:
// androidMain/AmplitudeConfig.android.kt
import android.content.Context
internal var androidContext: Context? = null
actual val amplitudeApiKey: String = "YOUR_ANDROID_API_KEY"
actual val platformContext: Any? get() = androidContext
// Set in Application.onCreate()
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
androidContext = applicationContext
}
}// iosMain/AmplitudeConfig.ios.kt
actual val amplitudeApiKey: String = "YOUR_IOS_API_KEY"
actual val platformContext: Any? = null // iOS doesn't need contextInitialize Amplitude once in shared code:
// commonMain - In your App composable or init function
import com.amplitude.kmp.Amplitude
import com.amplitude.kmp.Configuration
import com.amplitude.kmp.AutocaptureOption
val configuration = Configuration(
apiKey = amplitudeApiKey,
androidContext = platformContext // Only used on Android
).apply {
autocapture = setOf(
AutocaptureOption.SESSIONS,
AutocaptureOption.APP_LIFECYCLES,
AutocaptureOption.SCREEN_VIEWS
)
flushQueueSize = 30
flushIntervalMillis = 30000
}
val amplitude = Amplitude(configuration)
amplitude.setUserId("user-123")// Track simple event
amplitude.track("Button Clicked")
// Track event with properties
amplitude.track(
"Purchase Completed", mapOf(
"amount" to 99.99,
"currency" to "USD",
"items" to 3
)
)
// Set user properties
val identify = Identify()
.set("email", "user@example.com")
.set("plan", "premium")
.add("loginCount", 1)
amplitude.identify(identify)
// Track revenue
val revenue = Revenue()
revenue.price = 9.99
revenue.productId = "premium_subscription"
revenue.quantity = 1
amplitude.revenue(revenue)Android: Store context in Application.onCreate()
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
androidContext = applicationContext
}
}iOS: No additional setup needed! Just use the shared Kotlin code.
// Simple event
amplitude.track("Button Clicked")
// Event with properties
amplitude.track(
"Video Played", mapOf(
"video_id" to "12345",
"duration" to 180,
"quality" to "HD"
)
)
// Event with BaseEvent object
val event = BaseEvent(
eventType = "Purchase",
eventProperties = mutableMapOf(
"amount" to 49.99,
"items" to listOf("item1", "item2")
)
)
amplitude.track(event)
// Event with callback
amplitude.track(event) { event, status, message ->
if (status == 200) {
println("Event tracked successfully")
} else {
println("Failed: $message")
}
}The Identify API provides 150+ type-safe methods for user property operations:
val identify = Identify()
// Set properties
.set("email", "user@example.com")
.set("age", 25)
.set("premium", true)
// Set once (only if not already set)
.setOnce("initial_source", "google")
// Increment numeric properties
.add("login_count", 1)
.add("credits", 100)
// Array operations
.append("interests", "music")
.prepend("notifications", "new_feature")
.preInsert("tags", "vip") // Insert at start if not present
.postInsert("tags", "active") // Insert at end if not present
.remove("tags", "inactive")
// Unset properties
.unset("temporary_flag")
amplitude.identify(identify)
// Simple identify with map
amplitude.identify(
mapOf(
"email" to "user@example.com",
"plan" to "premium"
)
)Supported types for each operation (14-15 overloads):
Boolean, Double, Float, Int, Long, String
Map<String, Any>, List<Any>
Array<Boolean>, Array<Double>, Array<Float>, Array<Int>, Array<Long>, Array<String>
Any (for set/setOnce only)// Create revenue event
val revenue = Revenue()
revenue.productId = "premium_plan_monthly"
revenue.price = 9.99
revenue.quantity = 1
revenue.currency = "USD"
revenue.revenueType = "subscription"
// With receipt validation (Android)
revenue.receipt = "receipt_data"
revenue.receiptSig = "signature"
// Add custom properties
revenue.properties = mutableMapOf(
"plan_name" to "Premium",
"billing_cycle" to "monthly"
)
// Track the revenue
amplitude.revenue(revenue)
// Verify revenue is valid
if (revenue.isValid()) {
amplitude.revenue(revenue)
} else {
println("Invalid revenue: price is required")
}// Set user ID
amplitude.setUserId("user_12345")
// Get user ID
val userId = amplitude.getUserId()
// Set device ID
amplitude.setDeviceId("device_67890")
// Get device ID
val deviceId = amplitude.getDeviceId()
// Reset user (clears userId, generates new deviceId)
amplitude.reset()
// Get current session ID
val sessionId = amplitude.sessionId// Set user to a single group
amplitude.setGroup("org_id", "amplitude")
// Set user to multiple groups
amplitude.setGroup("team_id", arrayOf("engineering", "product"))
// Set group properties
amplitude.groupIdentify(
groupType = "org_id",
groupName = "amplitude",
groupProperties = mapOf(
"plan" to "enterprise",
"employees" to 500
)
)
// Set group properties with Identify
amplitude.groupIdentify(
groupType = "org_id",
groupName = "amplitude",
identify = Identify()
.set("industry", "analytics")
.add("num_projects", 1)
)// Sessions are tracked automatically
// Access current session ID
val sessionId = amplitude.sessionId
// Configure session timeout (default: 5 minutes)
val amplitude = Amplitude(apiKey, context) {
minTimeBetweenSessionsMillis = 300000 // 5 minutes
}
// Session events are tracked automatically
// Listen for: Amplitude.START_SESSION_EVENT and Amplitude.END_SESSION_EVENTAll main methods return the Amplitude instance for fluent chaining:
amplitude
.setUserId("user_123")
.track("App Opened")
.identify(Identify().set("last_seen", System.currentTimeMillis()))
.flush()// Flush events manually (normally happens automatically)
amplitude.flush()Optional suspend function extensions for async operations:
import com.amplitude.kmp.ktx.*
suspend fun trackUserJourney() {
// Track with Result return type
amplitude.awaitTrack(
eventType = "Signup Started",
eventProperties = mapOf("source" to "homepage")
).onSuccess {
println("Event tracked!")
}.onFailure { error ->
println("Failed: ${error.message}")
}
// Identify user
val identify = Identify()
.set("email", "user@example.com")
.set("signup_date", System.currentTimeMillis())
amplitude.awaitIdentify(identify)
// Track revenue
val revenue = Revenue()
revenue.price = 49.99
revenue.productId = "premium_plan"
amplitude.awaitRevenue(revenue)
// Flush and wait for completion
amplitude.awaitFlush()
// Other suspend operations
amplitude.awaitSetUserId("user_123")
amplitude.awaitSetDeviceId("device_456")
amplitude.awaitReset()
}Extend functionality with custom plugins:
import com.amplitude.kmp.plugins.Plugin
import com.amplitude.kmp.events.BaseEvent
class CustomPlugin : Plugin {
override val type = Plugin.Type.ENRICHMENT
override fun setup(amplitude: Amplitude) {
println("Plugin initialized")
}
override fun execute(event: BaseEvent): BaseEvent? {
// Enrich event with custom data
event.eventProperties?.put("app_version", "1.0.2")
event.eventProperties?.put("build_number", "123")
return event
}
}
// Add plugin
amplitude.add(CustomPlugin())
// Remove plugin
amplitude.remove(plugin)Plugin Types:
BEFORE - Runs before event processingENRICHMENT - Enriches events with additional dataDESTINATION - Sends events to custom destinationsOBSERVE - Observes events without modificationUTILITY - Utility pluginsComprehensive configuration for all use cases:
val amplitude = Amplitude(apiKey = "KEY", context = context) {
// Flushing
flushQueueSize = 30 // Events before auto-flush
flushIntervalMillis = 30000 // Auto-flush interval (30s)
flushMaxRetries = 5 // Max retry attempts
flushEventsOnClose = true // Flush on SDK close
// Server & API
serverZone = ServerZone.US // US or EU data center
serverUrl = "https://custom.api.com" // Custom API endpoint
useBatch = false // Use batch API
// Logging
logLevel = LogLevel.WARN // Logging level
// Levels: NONE, ERROR, WARN, LOG, DEBUG, VERBOSE
// Session Management
minTimeBetweenSessionsMillis = 300000 // Session timeout (5min)
trackingSessionEvents = true // Auto-track sessions
// Privacy & Tracking
optOut = false // Opt-out of tracking
trackingOptions = TrackingOptions() // Customize tracked fields
enableCoppaControl = false // COPPA compliance mode
// User & Device
deviceId = "custom-device-id" // Override device ID
minIdLength = 5 // Min length for IDs
// Auto-capture (Android)
autocapture = setOf(
AutocaptureOption.SESSIONS, // Session events
AutocaptureOption.APP_LIFECYCLES, // App lifecycle
AutocaptureOption.SCREEN_VIEWS, // Screen views
AutocaptureOption.ELEMENT_INTERACTIONS, // Click events
)
// Android-specific
useAdvertisingIdForDeviceId = false // Use Android Ad ID
useAppSetIdForDeviceId = false // Use App Set ID
locationListening = false // Track location
migrateLegacyData = true // Migrate old SDK data
// Advanced
offline = false // Offline mode
callback = { event, status, message -> // Global callback
println("Event: ${event.eventType}, Status: $status")
}
partnerId = "partner_123" // Partner attribution
plan = Plan(...) // Tracking plan
ingestionMetadata = IngestionMetadata(...) // Custom metadata
}Control which fields are tracked:
val trackingOptions = TrackingOptions()
.disableIpAddress()
.disableLatLng()
.disableCity()
.disableCountry()
.disableRegion()
.disableCarrier()
.disableDeviceBrand()
.disableDeviceModel()
val amplitude = Amplitude(apiKey, context) {
trackingOptions = trackingOptions
}
// COPPA-compliant tracking
val coppaOptions = TrackingOptions.forCoppaControl()Customize individual events:
val options = EventOptions().apply {
userId = "custom_user_123"
deviceId = "custom_device_456"
timestamp = System.currentTimeMillis()
sessionId = 1234567890
locationLat = 37.7749
locationLng = -122.4194
ip = "192.168.1.1"
plan = Plan(...)
callback = { event, status, message ->
println("Custom callback")
}
}
amplitude.track("Custom Event", properties, options)The KMP SDK maintains 100% API compatibility with the Android SDK. Migration requires ONLY import changes.
// Remove Android SDK
// implementation("com.amplitude:analytics-android:X.X.X")
// Add KMP SDK
implementation("digital.guimauve.amplitude:analytics-kmp:1.0.2")// Before (Android SDK)
import com.amplitude.android.Amplitude
import com.amplitude.android.Configuration
import com.amplitude.core.events.Identify
import com.amplitude.core.events.Revenue
import com.amplitude.core.events.BaseEvent
// After (KMP SDK) - ONLY CHANGE NEEDED
import com.amplitude.kmp.Amplitude
import com.amplitude.kmp.Configuration
import com.amplitude.kmp.events.Identify
import com.amplitude.kmp.events.Revenue
import com.amplitude.kmp.events.BaseEventYour code works identically:
// This code is IDENTICAL for both SDKs
val amplitude = Amplitude(apiKey = "KEY", context = context)
amplitude.track("Button Clicked", mapOf("color" to "blue"))
val identify = Identify()
.set("email", "user@example.com")
.add("loginCount", 1)
amplitude.identify(identify)
val revenue = Revenue()
revenue.price = 9.99
revenue.productId = "item_123"
revenue.quantity = 2
amplitude.revenue(revenue)
amplitude.setUserId("user_123").flush()No other changes required! 🎉
analytics-android to analytics-kmp
com.amplitude.android.* to com.amplitude.kmp.*
com.amplitude.core.* to com.amplitude.kmp.*
Initialize Amplitude as early as possible in your app lifecycle:
class MyApplication : Application() {
lateinit var amplitude: Amplitude
override fun onCreate() {
super.onCreate()
amplitude = Amplitude(apiKey = "KEY", context = this)
}
}fun onUserLogin(userId: String) {
amplitude.setUserId(userId)
amplitude.identify(
Identify()
.set("login_time", System.currentTimeMillis())
.add("login_count", 1)
)
}// Track key user actions
amplitude.track("Onboarding Started")
amplitude.track("Profile Created")
amplitude.track("First Purchase")
amplitude.track("Feature Discovered", mapOf("feature" to "export"))// Set properties that describe the user
val identify = Identify()
.set("plan", "premium")
.set("account_age_days", 30)
.set("features_enabled", listOf("export", "collaboration"))
.add("sessions_count", 1)
amplitude.identify(identify)val revenue = Revenue()
revenue.price = 9.99
revenue.productId = "premium_monthly"
if (revenue.isValid()) {
amplitude.revenue(revenue)
} else {
println("Revenue event invalid: price is required")
}Problem: IllegalArgumentException: androidContext must be android.content.Context
Solution: Make sure you pass an Android Context on Android:
// Correct
val amplitude = Amplitude(apiKey = "KEY", context = applicationContext)
// Incorrect
val amplitude = Amplitude(apiKey = "KEY", context = null)Problem: CocoaPods dependency not found
Solution: Make sure CocoaPods are installed:
cd iosApp
pod installCommon causes:
optOut = false
amplitude.flush() or wait for auto-flush// Enable debug logging to see what's happening
val amplitude = Amplitude(apiKey, context) {
logLevel = LogLevel.DEBUG
}The SDK is thread-safe. You can call methods from any thread:
// Safe to call from background threads
GlobalScope.launch {
amplitude.track("Background Event")
}Make sure to use application context, not activity context:
// Good
Amplitude(apiKey, applicationContext)
// Bad - may cause memory leak
Amplitude(apiKey, activityContext)See API Compatibility Issues Report for known issues and fixes in progress.
Goal: The KMP SDK must have identical API to the Android SDK. Migration should require ONLY import changes - no code modifications.
If you encounter any API differences after migrating from the Android SDK (beyond the known issues listed in the report above), please:
We treat API compatibility breaks as critical bugs and will fix them with high priority.
Expected: Change imports only, everything else stays the same
// ONLY these should change:
import com.amplitude.android.* → import com.amplitude.kmp.*
import com.amplitude.core.* → import com.amplitude.kmp.*
Not Expected: Any code changes, refactoring, or API adjustments
// Initialization
Amplitude(apiKey: String, context: Context, configs: Configuration.() -> Unit = {}) // Android
Amplitude(apiKey: String, configs: Configuration.() -> Unit = {}) // iOS
Amplitude(configuration: Configuration) // Both
// Event Tracking
track(eventType: String, eventProperties: Map< String, Any?>? = null, options: EventOptions? = null): Amplitude
track(event: BaseEvent, options: EventOptions? = null, callback: EventCallBack? = null): Amplitude
// User Identity
identify(userProperties: Map< String, Any?>?, options: EventOptions? = null): Amplitude
identify(identify: Identify, options: EventOptions? = null): Amplitude
setUserId(userId: String?): Amplitude
getUserId(): String?
// Device Management
setDeviceId(deviceId: String): Amplitude
getDeviceId(): String?
reset(): Amplitude
// Revenue
revenue(revenue: Revenue, options: EventOptions? = null): Amplitude
revenue(event: RevenueEvent): Amplitude
// Groups
setGroup(groupType: String, groupName: String, options: EventOptions? = null): Amplitude
setGroup(groupType: String, groupName: Array< String >, options: EventOptions? = null): Amplitude
groupIdentify(
groupType: String,
groupName: String,
groupProperties: Map< String, Any?>?, options: EventOptions? = null): Amplitude
groupIdentify(groupType: String, groupName: String, identify: Identify, options: EventOptions? = null): Amplitude
// Plugins
add(plugin: Plugin): Amplitude
remove(plugin: Plugin): Amplitude
// Flushing
flush()
// Properties
val sessionId: Long // Read-only current session ID
// Constants
Amplitude.START_SESSION_EVENT // "session_start"
Amplitude.END_SESSION_EVENT // "session_end"amplitude-kmp/
├── kmp/ # Main KMP module
│ ├── src/
│ │ ├── commonMain/ # Platform-agnostic code
│ │ │ ├── kotlin/com/amplitude/kmp/
│ │ │ │ ├── Amplitude.kt # Main API
│ │ │ │ ├── Configuration.kt
│ │ │ │ ├── events/ # Event models
│ │ │ │ ├── plugins/ # Plugin system
│ │ │ │ ├── ktx/ # Coroutines extensions
│ │ │ │ └── utils/ # Utilities
│ │ ├── androidMain/ # Android implementation
│ │ │ ├── kotlin/com/amplitude/kmp/
│ │ │ │ ├── Amplitude.android.kt
│ │ │ │ └── mappings/android/ # Type mappers
│ │ ├── iosMain/ # iOS implementation
│ │ │ ├── kotlin/com/amplitude/kmp/
│ │ │ │ ├── Amplitude.ios.kt
│ │ │ │ └── mappings/ios/ # Type converters
│ │ ├── androidUnitTest/ # Android tests
│ │ └── iosTest/ # iOS tests
│ └── build.gradle.kts
├── samples/ # Sample applications
├── reports/ # Documentation & analysis
└── README.md # This file
./gradlew build./gradlew test
./gradlew connectedAndroidTest # Android instrumentation tests./gradlew publishToMavenLocalThis project follows Kotlin Coding Conventions.
This KMP SDK wraps the following native SDKs for optimal performance:
com.amplitude:analytics-android:2.40.1
Amplitude-Swift:1.16.2
We welcome contributions! To contribute:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)Please see CONTRIBUTING.md for detailed guidelines.
See CHANGELOG.md for release history.
This project is licensed under the MIT License - see the LICENSE file for details.