
Analytics client: non-blocking event queue, batching with retries and exponential backoff, local JSONL persistence for offline delivery, typed properties, session/device identity and lifecycle controls.
Kotlin Multiplatform analytics client for Grain.
Works on Android, iOS, and JVM.
// build.gradle.kts
dependencies {
implementation("com.grainql:grain-sdk:0.0.1")
}[libraries]
grain-sdk = { module = "com.grainql:grain-sdk", version = "0.0.1" }import com.grainql.sdk.GrainAnalytics
import com.grainql.sdk.GrainConfig
import com.grainql.sdk.platform.AndroidKeyValueStore
import com.grainql.sdk.platform.AndroidFileStore
// Initialize (typically in Application.onCreate)
GrainAnalytics.initialize(
config = GrainConfig(tenantAlias = "your-tenant"),
kvStore = AndroidKeyValueStore(applicationContext),
fileStore = AndroidFileStore(applicationContext),
)
// Track events
GrainAnalytics.track("button_clicked", mapOf(
"screen" to "home",
"button" to "signup",
))
// Identify a user after login
GrainAnalytics.identify("user-123")
// Track with typed properties — numbers and booleans stay typed over the wire
GrainAnalytics.track("purchase_complete", mapOf(
"amount" to 29.99,
"currency" to "USD",
"first_purchase" to true,
))let kvStore = IosKeyValueStore()
let fileStore = IosFileStore()
GrainAnalytics.shared.initialize(
config: GrainConfig(tenantAlias: "your-tenant"),
kvStore: kvStore,
fileStore: fileStore
)
GrainAnalytics.shared.track(eventName: "screen_viewed", properties: ["screen": "settings"])import com.grainql.sdk.platform.JvmKeyValueStore
import com.grainql.sdk.platform.JvmFileStore
GrainAnalytics.initialize(
config = GrainConfig(tenantAlias = "your-tenant"),
kvStore = JvmKeyValueStore(),
fileStore = JvmFileStore(),
)GrainConfig(
tenantAlias = "your-tenant", // Required
apiUrl = "https://clientapis.grainql.com", // Default
secret = null, // Tenant secret for authenticated ingestion
debug = false, // Enable debug logging
flushIntervalMs = 30_000, // Periodic flush interval (30s default)
flushThreshold = 25, // Flush when queue reaches this size
maxBatchSize = 100, // Max events per API call (server limit: 160)
maxQueueSize = 10_000, // Max events in local queue
maxRetries = 5, // Retry attempts per batch on failure
enablePersistence = true, // Persist events to disk for offline support
)GrainAnalytics.track(eventName, properties) // Track with current user
GrainAnalytics.track(eventName, userId, properties) // Track with explicit userGrainAnalytics.identify(userId) // Set the user identity
GrainAnalytics.resetIdentity() // Clear identity, revert to device ID
GrainAnalytics.getDeviceId() // Stable device ID (persists across resets)
GrainAnalytics.getSessionId() // Current session ID (fresh per app launch)GrainAnalytics.setUserProperties(mapOf("plan" to "pro", "source" to "organic"))
GrainAnalytics.setUserProperties(userId, properties) // For a specific userGrainAnalytics.onForeground() // Resume periodic flushing
GrainAnalytics.onBackground() // Flush pending events, pause timer
GrainAnalytics.onNetworkConnected() // Flush immediately
GrainAnalytics.onNetworkDisconnected() // Pause sending
GrainAnalytics.flush() // Manual flush (suspend)
GrainAnalytics.shutdown() // Final flush and cleanup (suspend)Events flow through a simple pipeline:
track() synchronously submits events into an unbounded channel — it never blocks.maxRetries are dropped.The SDK automatically attaches session_id, device_id, client_version, platform, and timestamp to every event. These match the conventions used by the Grain web SDK so all events participate in the same analytics pipeline.
./gradlew :library:jvmTest # Run tests
./gradlew :library:compileAndroidMain # Build Android
./gradlew :library:compileKotlinIosArm64 # Build iOSSee CONTRIBUTING.md.
Kotlin Multiplatform analytics client for Grain.
Works on Android, iOS, and JVM.
// build.gradle.kts
dependencies {
implementation("com.grainql:grain-sdk:0.0.1")
}[libraries]
grain-sdk = { module = "com.grainql:grain-sdk", version = "0.0.1" }import com.grainql.sdk.GrainAnalytics
import com.grainql.sdk.GrainConfig
import com.grainql.sdk.platform.AndroidKeyValueStore
import com.grainql.sdk.platform.AndroidFileStore
// Initialize (typically in Application.onCreate)
GrainAnalytics.initialize(
config = GrainConfig(tenantAlias = "your-tenant"),
kvStore = AndroidKeyValueStore(applicationContext),
fileStore = AndroidFileStore(applicationContext),
)
// Track events
GrainAnalytics.track("button_clicked", mapOf(
"screen" to "home",
"button" to "signup",
))
// Identify a user after login
GrainAnalytics.identify("user-123")
// Track with typed properties — numbers and booleans stay typed over the wire
GrainAnalytics.track("purchase_complete", mapOf(
"amount" to 29.99,
"currency" to "USD",
"first_purchase" to true,
))let kvStore = IosKeyValueStore()
let fileStore = IosFileStore()
GrainAnalytics.shared.initialize(
config: GrainConfig(tenantAlias: "your-tenant"),
kvStore: kvStore,
fileStore: fileStore
)
GrainAnalytics.shared.track(eventName: "screen_viewed", properties: ["screen": "settings"])import com.grainql.sdk.platform.JvmKeyValueStore
import com.grainql.sdk.platform.JvmFileStore
GrainAnalytics.initialize(
config = GrainConfig(tenantAlias = "your-tenant"),
kvStore = JvmKeyValueStore(),
fileStore = JvmFileStore(),
)GrainConfig(
tenantAlias = "your-tenant", // Required
apiUrl = "https://clientapis.grainql.com", // Default
secret = null, // Tenant secret for authenticated ingestion
debug = false, // Enable debug logging
flushIntervalMs = 30_000, // Periodic flush interval (30s default)
flushThreshold = 25, // Flush when queue reaches this size
maxBatchSize = 100, // Max events per API call (server limit: 160)
maxQueueSize = 10_000, // Max events in local queue
maxRetries = 5, // Retry attempts per batch on failure
enablePersistence = true, // Persist events to disk for offline support
)GrainAnalytics.track(eventName, properties) // Track with current user
GrainAnalytics.track(eventName, userId, properties) // Track with explicit userGrainAnalytics.identify(userId) // Set the user identity
GrainAnalytics.resetIdentity() // Clear identity, revert to device ID
GrainAnalytics.getDeviceId() // Stable device ID (persists across resets)
GrainAnalytics.getSessionId() // Current session ID (fresh per app launch)GrainAnalytics.setUserProperties(mapOf("plan" to "pro", "source" to "organic"))
GrainAnalytics.setUserProperties(userId, properties) // For a specific userGrainAnalytics.onForeground() // Resume periodic flushing
GrainAnalytics.onBackground() // Flush pending events, pause timer
GrainAnalytics.onNetworkConnected() // Flush immediately
GrainAnalytics.onNetworkDisconnected() // Pause sending
GrainAnalytics.flush() // Manual flush (suspend)
GrainAnalytics.shutdown() // Final flush and cleanup (suspend)Events flow through a simple pipeline:
track() synchronously submits events into an unbounded channel — it never blocks.maxRetries are dropped.The SDK automatically attaches session_id, device_id, client_version, platform, and timestamp to every event. These match the conventions used by the Grain web SDK so all events participate in the same analytics pipeline.
./gradlew :library:jvmTest # Run tests
./gradlew :library:compileAndroidMain # Build Android
./gradlew :library:compileKotlinIosArm64 # Build iOSSee CONTRIBUTING.md.