The official, production-grade Kotlin SDK for Altertable Product Analytics.
- JDK 17 or higher
- Gradle 8.x (wrapper included)
implementation("ai.altertable.sdk:altertable-kotlin:0.1.0")For Android projects, also add the Android module:
implementation("ai.altertable.sdk:altertable-android:0.1.0")import ai.altertable.sdk.Altertable
// Initialize the singleton client using DSL
Altertable.setup {
apiKey = "your-api-key"
environment = "production"
}
// Track an event
Altertable.shared?.track(
event = "Button Clicked",
properties = mapOf("button_id" to "signup")
)import ai.altertable.sdk.android.AltertableAndroid
import android.app.Application
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AltertableAndroid.setup(this) {
apiKey = "your-api-key"
environment = "production"
}
}
}Then track events anywhere:
import ai.altertable.sdk.Altertable
Altertable.shared?.track(
event = "Button Clicked",
properties = mapOf("button_id" to "signup")
)Initializes the singleton client using a DSL builder. This is the recommended way to initialize the SDK.
import ai.altertable.sdk.Altertable
Altertable.setup {
apiKey = "your-api-key"
environment = "production"
debug = true
network {
baseUrl = "https://api.altertable.ai"
requestTimeout = 10.seconds
maxRetries = 3
}
tracking {
consent = TrackingConsent.GRANTED
captureScreenViews = true
flushOnBackground = true
maxQueueSize = 1000
}
release = "1.0.0"
logger = MyCustomLogger()
beforeSend = listOf { event ->
// Transform or filter events
event
}
}Returns the initialized singleton client instance, or null if setup() hasn't been called.
val client = Altertable.shared
client?.track("Event")Creates a new non-singleton client instance. Use this for multi-environment setups, testing, or when embedding the SDK in a library.
val testClient = Altertable.create {
apiKey = "test-key"
environment = "test"
}Records an event with optional properties.
client.track(
event = "Purchase",
properties = mapOf("amount" to 29.99, "currency" to "USD")
)Tracks a screen view event. Automatically includes $screen_name in properties.
client.screen(
name = "HomeScreen",
properties = mapOf("section" to "main")
)Identifies a user with a unique ID and optional traits. Links the current anonymous state to the known user identity.
client.identify(
userId = "user_123",
traits = mapOf(
"email" to "user@example.com",
"name" to "John Doe"
)
)Updates user traits for the current identified user. Must call identify() first.
client.updateTraits(mapOf("plan" to "premium"))Associates a new user ID with the existing user ID. Useful for linking anonymous and identified users.
client.alias(newUserId = "user_456")Resets the client state, clearing the current user identity and session. Optionally resets the device ID and tracking consent.
// Reset identity and session only
client.reset()
// Also reset device ID
client.reset(resetDeviceId = true)
// Also reset consent to GRANTED
client.reset(resetTrackingConsent = true)Updates the client's configuration dynamically. Only safe-to-change-at-runtime options are available (tracking consent, debug, logger, beforeSend).
client.configure {
tracking {
consent = TrackingConsent.GRANTED
}
debug = true
}Reactive flow of the current tracking consent state. Use this to observe consent changes in Compose or with lifecycle-aware components.
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
@Composable
fun ConsentObserver() {
val consent = client.trackingConsent.collectAsState()
LaunchedEffect(consent.value) {
when (consent.value) {
TrackingConsent.GRANTED -> {
// Consent granted
}
TrackingConsent.DENIED -> {
// Consent denied
}
TrackingConsent.PENDING -> {
// Waiting for consent
}
TrackingConsent.DISMISSED -> {
// Consent dialog dismissed
}
}
}
}Reactive flow of errors that occur during SDK operations.
import androidx.compose.runtime.LaunchedEffect
LaunchedEffect(Unit) {
client.errors.collect { error ->
when (error) {
is AltertableError.Validation -> {
// Validation error
}
is AltertableError.Api -> {
// API error (HTTP status, error code)
}
is AltertableError.Network -> {
// Network error
}
}
}
}Super properties are automatically included in all tracked events. Use bracket syntax for fire-and-forget operations, or suspend methods for synchronous access.
// Fire-and-forget (non-coroutine contexts)
client.superProperties["app_version"] = "1.0.0"
client.superProperties["user_type"] = "premium"
client.superProperties.removeAsync("user_type")
// Suspend methods (coroutine contexts)
lifecycleScope.launch {
client.superProperties.setValue("app_version", "1.0.0")
val version = client.superProperties.get("app_version")
client.superProperties.remove("user_type")
val allProps = client.superProperties.toMap()
}Force-flushes any pending consent-queue events and in-memory outbound batches. This is a fire-and-forget operation that does not block the calling thread.
client.flush()Force-flushes any pending consent-queue events and in-memory outbound batches, and suspends until completion. Use this when you need to ensure events are sent before proceeding (e.g., before app termination).
// In a suspend function or coroutine
lifecycleScope.launch {
client.awaitFlush()
}Note: Do not call awaitFlush() from the main thread on Android.
When the API returns a non-retryable error (e.g. 4xx other than rate limits handled as retries), batched events are written back to the persisted queue (when your app uses disk-backed storage) so they are not silently dropped.
Releases resources (coroutine scope, HTTP client, integrations). Call when the client is no longer needed.
client.close()Use AltertableAndroid.setup() in your Application.onCreate():
import ai.altertable.sdk.android.AltertableAndroid
import android.app.Application
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
AltertableAndroid.setup(this) {
apiKey = "your-api-key"
environment = "production"
debug = BuildConfig.DEBUG
tracking {
captureScreenViews = true // Auto-track Activity screen views
flushOnBackground = true // Flush events when app backgrounds
}
}
}
}Provide the Altertable client to your Compose hierarchy:
import ai.altertable.sdk.Altertable
import ai.altertable.sdk.android.ProvideAltertable
import ai.altertable.sdk.android.rememberAltertable
@Composable
fun MyApp() {
val client = Altertable.shared ?: return
ProvideAltertable(client) {
// Your app content
HomeScreen()
}
}
@Composable
fun HomeScreen() {
val client = rememberAltertable()
// Use client here
}Track screen views using the Modifier.screenView() extension:
import ai.altertable.sdk.android.screenView
@Composable
fun HomeScreen() {
Column(
modifier = Modifier
.fillMaxSize()
.screenView(name = "Home")
) {
// Screen content
}
}Or use the TrackScreenView composable:
import ai.altertable.sdk.android.TrackScreenView
@Composable
fun HomeScreen() {
TrackScreenView(name = "Home")
// Screen content
}When captureScreenViews is enabled (default), the SDK automatically tracks screen views when Activities appear. Screen names are extracted by removing the Activity suffix from the class name (e.g., HomeActivity → Home).
To disable automatic tracking:
AltertableAndroid.setup(this) {
apiKey = "your-api-key"
tracking {
captureScreenViews = false
}
}Then use manual tracking with client.screen() or Compose modifiers.
Control event collection based on user consent:
// Start with consent pending
Altertable.setup {
apiKey = "your-api-key"
tracking {
consent = TrackingConsent.PENDING
}
}
// Events are queued until consent is granted
Altertable.shared?.track("Button Clicked")
// When user grants consent
Altertable.shared?.configure {
tracking {
consent = TrackingConsent.GRANTED
}
}
// Queued events are automatically flushed
// If consent is denied
Altertable.shared?.configure {
tracking {
consent = TrackingConsent.DENIED
}
}
// Queue is cleared, new events are droppedConsent states:
GRANTED: Events sent immediatelyDENIED: Events dropped, queue clearedPENDING: Events queued until consent changesDISMISSED: Events queued (same asPENDING)
The Android module provides SharedPreferencesStorage by default. For custom storage implementations (e.g., DataStore), implement the Storage interface. Note that Storage is marked @AltertableInternal and is intended for use by platform-specific modules.
import ai.altertable.sdk.Storage
import kotlinx.coroutines.flow.first
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
@OptIn(AltertableInternal::class)
class DataStoreStorage(
private val dataStore: DataStore<Preferences>
) : Storage {
override suspend fun get(key: String): String? {
return dataStore.data.first()[stringPreferencesKey(key)]
}
override suspend fun set(key: String, value: String) {
dataStore.edit { prefs ->
prefs[stringPreferencesKey(key)] = value
}
}
override suspend fun remove(key: String) {
dataStore.edit { prefs ->
prefs.remove(stringPreferencesKey(key))
}
}
override suspend fun migrate(from: String, to: String) {
val value = get(from)
if (value != null) {
set(to, value)
remove(from)
}
}
}Initialize with an AltertableConfig object or use the DSL builder. All configuration options:
| Option | Type | Default | Description |
|---|---|---|---|
apiKey |
String |
(Required) | Your project API key. |
environment |
String |
"production" |
Environment name (e.g., "production", "staging"). |
release |
String? |
null |
The release version of your app. |
debug |
Boolean |
false |
Enables verbose logging. |
dispatcher |
CoroutineDispatcher |
Dispatchers.IO |
Coroutine dispatcher for async operations. |
logger |
AltertableLogger? |
null |
Optional logger for SDK events. |
integrations |
List<AltertableIntegration> |
[] |
Integrations to install on setup. |
beforeSend |
List<EventInterceptor> |
[] |
Hooks to transform or filter events before sending. Return null to drop an event. |
| Option | Type | Default | Description |
|---|---|---|---|
network.baseUrl |
String |
"https://api.altertable.ai" |
The API endpoint URL. |
network.requestTimeout |
Duration |
10.seconds |
Network request timeout. |
network.maxRetries |
Int |
3 |
Retries after the first attempt for transient failures (5xx, 429, network); up to four HTTP attempts total. |
| Option | Type | Default | Description |
|---|---|---|---|
tracking.consent |
TrackingConsent |
GRANTED |
Initial tracking consent state. |
tracking.captureScreenViews |
Boolean |
true |
Automatically track screen views on Android (Activity-based). |
tracking.flushOnBackground |
Boolean |
true |
Automatically flush events when app goes into background (Android only). |
tracking.maxQueueSize |
Int |
1000 |
Maximum events to hold in the queue (older events dropped when exceeded). |
tracking.flushEventThreshold |
Int |
20 |
Flush outbound batches when the combined buffered event count (all types) reaches this value. |
tracking.flushIntervalMs |
Long |
5000 |
Periodic flush interval for outbound batches (milliseconds). |
tracking.maxBatchSize |
Int |
20 |
Maximum payloads per HTTP request per endpoint (/track, /identify, /alias). |
The Examples/app directory contains an Android Compose app demonstrating a full signup funnel with track, identify, alias, and reset calls.
ALTERTABLE_API_KEY=pk_... ./gradlew :example-app:installDebugSee Examples/app/README.md for setup instructions including Android SDK installation.
| Step | Command | Description |
|---|---|---|
| Build | make build or ./gradlew build |
Compiles and runs unit tests. |
| Format | make format or ./gradlew spotlessApply |
Formats the codebase. |
| Lint | make lint or ./gradlew detekt |
Runs the linter. |
| API docs | make docs or ./gradlew dokkaHtml |
Generates API documentation. |
| Full check | make check or ./gradlew check |
Runs all verification tasks. |
Run unit tests:
./gradlew testRun integration tests (requires altertable-mock):
docker compose up -d
./gradlew integrationTestOr use the Makefile: make integration
Generate API documentation:
./gradlew dokkaHtmlDocumentation will be available at altertable/build/dokka/html/index.html and altertable-android/build/dokka/html/index.html.
Releases are automated via Release Please and the GitHub Actions release workflow. When you change the public API, the ABI validation will catch breaking changes during the build.
- Fork the repository
- Create a feature branch
- Commit your changes
- Push to the branch
- Open a Pull Request
See the LICENSE file for details.