Android SDK Integration
This guide covers every feature and integration available in the Kubesense Android SDK (v2.1.0): adding dependencies, initializing the core SDK, and enabling RUM, distributed tracing, session replay, NDK crash reporting, profiling, WebView tracking, and feature flags — plus the supported third-party integrations (OkHttp, Jetpack Compose, image loaders, Apollo, Cronet, coroutines, RxJava, SQLDelight, and more).
| Field | Value |
|---|---|
| Min SDK | 23 (Android 6.0) — auto-instrumented integrations require minSdk 29 |
| Target / Compile SDK | 36 |
| Language | Java 11 source/target compatibility, Kotlin |
| Distribution | Maven Central, group ai.kubesense |
Core Setup
1. Configure repositories
Ensure Maven Central and Google's Maven repository are available in your project-level settings.gradle.kts:
dependencyResolutionManagement {
repositories {
google()
mavenCentral()
}
}note: The SDK is published to Maven Central under the ai.kubesense group. No custom Maven repository is required.
2. Add the core dependency
// module-level build.gradle.kts
dependencies {
implementation("ai.kubesense:kubesense-android-core:2.1.0")
}3. Initialize the SDK
Call Kubesense.initialize() in Application.onCreate() before enabling any feature:
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
val configuration = Configuration.Builder(
clientToken = "<CLIENT_TOKEN>",
env = "production",
variant = BuildConfig.VERSION_NAME
)
// Single intake host for every feature. Host only — no scheme, no path.
.useKubesenseRumEndpoint("<your-collector-host>")
.setFirstPartyHosts(listOf("api.example.com"))
.build()
Kubesense.initialize(this, configuration, TrackingConsent.GRANTED)
}
}Register the Application class in AndroidManifest.xml:
<application
android:name=".MyApplication"
...>
</application>warning: Changed in 2.1.0 — one endpoint for every feature. The per-feature useCustomEndpoint(...) builders have been removed. Point the SDK at your collector once, on the core Configuration.Builder, with useKubesenseRumEndpoint(...); RUM, Logs, Traces, Session Replay, Profiling, and Flags all upload through it.
The value is a host, with an optional port — intake.example.com or intake.example.com:8443. Any scheme or path you include is stripped: uploads always go over HTTPS to https://<host>/rum/api/v1/.... This is the opposite of the old per-feature setters, which took a full URL and appended nothing to it. Upgrading from 2.0.0? See Migrating from 2.0.0.
Omit useKubesenseRumEndpoint(...) to upload to the site selected with useSite(...) — KubesenseSite.PROD (us2.kubesense.ai) by default.
Remote Configuration
info: New in 2.1.0, and enabled by default. Applications write no remote-configuration code — Kubesense.initialize() does all of it.
Sampling rates, feature switches, and privacy masking can be managed centrally and delivered to the SDK, so you can retune or switch off a feature fleet-wide without shipping an app release.
How it works
Launch N Launch N+1
──────────────────────────────── ────────────────────────────
1. read cached config (local, ~1ms) 1. read cached config ← new values
2. init SDK with cached-or-default 2. init SDK with them
3. app runs with the current config
4. async fetch of the config document
5. cache the response for next launchKubesense.initialize()reads the document cached by the previous launch — a small local file read, never a network call — and applies itscoresettings before the upload pipeline starts.- Each
X.enable()skips the feature if it is switched off remotely, then applies its own settings over what your code passed. - A fresh document is fetched on a background thread using an un-instrumented HTTP client, so the config request never shows up as a RUM resource or span.
- The fetch repeats on an interval, so long-running kiosk-style apps still converge.
warning: SDK configuration is frozen at enable() time by design, so changes apply at the next launch, not mid-session.
Opting out or retuning
Configuration.Builder(clientToken, env, variant)
.setRemoteConfigurationEnabled(false) // stay on in-code settings only
.setRemoteConfigurationRefreshPeriodMs(6 * 3600_000L) // default; <= 0 fetches once
.build()Values below one minute are raised to one minute. A value of zero or less fetches once after initialization and never again.
Defaults always win
A broken or missing configuration can never take your app away from safe SDK defaults:
- Absent values change nothing. The document is an override layer, not a second source of defaults — a missing key leaves the setting exactly as your code built it.
- Bad values are dropped individually. A value resolves to "absent" when it has the wrong JSON type, names an unknown enum constant, or is out of range (sample rates outside
0..100, sizes and counts<= 0). A completely malformed payload falls back to pure defaults, and unknown keys are ignored, so the format is forward-compatible. - Network problems are non-events. An HTTP failure, timeout, or unreadable body leaves the existing cache untouched. Responses are parse-validated before overwriting the cache, and the cache is written via a temp file and rename.
- First launch and offline launches run on pure SDK defaults.
What can be controlled
| Section | Settings |
|---|---|
features | Switch rum, logs, trace, sessionReplay, or profiling off entirely — enable() returns without registering the feature, so your code needs no branch of its own. Only an explicit false disables. |
rum | sessionSampleRate, telemetrySampleRate, trackUserInteractions, trackFrustrations, trackNonFatalAnrs, trackBackgroundEvents, trackAnonymousUser, collectAccessibility, vitalsUpdateFrequency, longTaskThresholdMs |
sessionReplay | sampleRate, startRecordingImmediately, imagePrivacy, touchPrivacy, textAndInputPrivacy, dynamicOptimizationEnabled, minRAMSizeMb, minCPUCoreNumber |
logs | networkInfoEnabled, logcatLogsEnabled, bundleWithRumEnabled, bundleWithTraceEnabled — applied in Logger.Builder.build(), so every logger picks them up |
trace | networkInfoEnabled |
flags | trackExposures, rumIntegrationEnabled |
profiling | launchSampleRate |
core | batchSize, uploadFrequency |
Session Replay privacy values are applied after any app-local privacy logic, so masking can always be tightened centrally.
Features
Real User Monitoring (RUM)
Track user sessions, screen views, interactions, errors, network resources, and long tasks.
Dependency
implementation("ai.kubesense:kubesense-android-rum:2.1.0")Enable RUM
Rum.enable(
RumConfiguration.Builder(applicationId = "<RUM_APPLICATION_ID>")
// The intake host is set once on Configuration.Builder — see Core Setup
.trackUserInteractions()
.trackLongTasks(longTaskThresholdMs = 100L)
.useViewTrackingStrategy(ActivityViewTrackingStrategy(trackExtras = true))
.build()
)Manual view tracking
// Start a view
GlobalRumMonitor.get().startView(key = this, name = "HomeScreen")
// Stop a view
GlobalRumMonitor.get().stopView(key = this)Manual action tracking
GlobalRumMonitor.get().addAction(
type = RumActionType.TAP,
name = "Login Button",
attributes = emptyMap()
)Manual error reporting
GlobalRumMonitor.get().addError(
message = "Something went wrong",
source = RumErrorSource.SOURCE,
throwable = exception,
attributes = mapOf("order_id" to "12345")
)Custom global attributes
GlobalRumMonitor.get().addAttribute("user_tier", "premium")
GlobalRumMonitor.get().removeAttribute("user_tier")note: RUM exposes a much larger configuration surface — view-tracking strategies (Activity / Fragment / Navigation), user-interaction and action tracking, long tasks and ANRs, vitals and frame metrics, session sampling, event mappers, and feature-flag evaluations. Configure these on RumConfiguration.Builder before calling Rum.enable(...).
Distributed Tracing
Instrument operations to measure latency end-to-end and correlate traces with RUM sessions.
Dependency
implementation("ai.kubesense:kubesense-android-trace:2.1.0")Enable Trace and register a tracer
Trace.enable(TraceConfiguration.Builder().build())
// Register a global tracer once, after Trace.enable(...)
GlobalKubesenseTracer.registerIfAbsent(
KubesenseTracing.newTracerBuilder()
.build()
)Create and use spans
val tracer = GlobalKubesenseTracer.get()
val span = tracer.buildSpan("process-payment").start()
try {
// your operation
} finally {
span.finish()
}Or use the withinSpan helper, which starts and finishes a span around the block automatically:
withinSpan("process-payment") {
// your operation — `this` is the KubesenseSpan
setTag("order_id", orderId)
}OpenTelemetry support
implementation("ai.kubesense:kubesense-android-trace-otel:2.1.0")Register a Kubesense-backed OpenTelemetry instance and use the standard OpenTelemetry API:
GlobalOpenTelemetry.set(
KubesenseOpenTelemetry("<service-name>")
)See the OkHttp OpenTelemetry section for propagating trace context over HTTP.
Session Replay
Capture a visual replay of user sessions so you can reproduce exactly what a user experienced.
Dependencies
implementation("ai.kubesense:kubesense-android-session-replay:2.1.0")
// Optional: Material Design component support
implementation("ai.kubesense:kubesense-android-session-replay-material:2.1.0")
// Optional: Jetpack Compose support
implementation("ai.kubesense:kubesense-android-session-replay-compose:2.1.0")note: Session Replay requires RUM to be enabled first.
Enable Session Replay
SessionReplay.enable(
SessionReplayConfiguration.Builder(sampleRate = 100f)
.setImagePrivacy(ImagePrivacy.MASK_LARGE_ONLY)
.setTextAndInputPrivacy(TextAndInputPrivacy.MASK_SENSITIVE_INPUTS)
.setTouchPrivacy(TouchPrivacy.HIDE)
.addExtensionSupport(MaterialExtensionSupport()) // if using Material
.addExtensionSupport(ComposeExtensionSupport()) // if using Compose
.build()
)Privacy levels
Privacy is configured independently for images, text/inputs, and touches:
| Setter | Values |
|---|---|
setImagePrivacy(...) | MASK_NONE, MASK_LARGE_ONLY, MASK_ALL |
setTextAndInputPrivacy(...) | MASK_SENSITIVE_INPUTS, MASK_ALL_INPUTS, MASK_ALL |
setTouchPrivacy(...) | SHOW, HIDE |
warning: The older single-axis setPrivacy(SessionReplayPrivacy.…) API (ALLOW / MASK / MASK_USER_INPUT) is deprecated in favour of the three granular setters above.
You can also override privacy per view at runtime with View.setSessionReplayHidden(...), View.setSessionReplayImagePrivacy(...), View.setSessionReplayTextAndInputPrivacy(...), and View.setSessionReplayTouchPrivacy(...) (or the Modifier.sessionReplay* variants in Compose).
NDK Crash Reporting
Capture crashes that originate from native C/C++ code via NDK.
Dependency
implementation("ai.kubesense:kubesense-android-ndk:2.1.0")Enable NDK crash reporting
Call NdkCrashReports.enable() after Kubesense.initialize(...):
Kubesense.initialize(this, configuration, TrackingConsent.GRANTED)
NdkCrashReports.enable()Profiling
Record CPU and memory profiles to identify performance bottlenecks at runtime.
Dependency
implementation("ai.kubesense:kubesense-android-profiling:2.1.0")Enable Profiling
Profiling is an experimental feature and requires Android 15 (API 35) or later. Call Profiling.enable() after the SDK is initialized:
@OptIn(ExperimentalProfilingApi::class)
fun initProfiling() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {
Profiling.enable(ProfilingConfiguration.DEFAULT)
}
}note: On supported devices, once enabled the SDK also samples an application-launch profile on subsequent launches (via the bundled KubesenseProfilingContentProvider).
WebView Tracking
Bridge RUM events captured inside a WebView into the native mobile session, giving you a unified view of web and native interactions.
Dependency
implementation("ai.kubesense:kubesense-android-webview:2.1.0")Setup
webView.settings.javaScriptEnabled = true
WebViewTracking.enable(
webView = webView,
allowedHosts = listOf("example.com")
)The web page must also be instrumented with the Kubesense Browser SDK, and the hosts you pass to allowedHosts must match the URLs the page loads.
Feature Flags
Evaluate feature flags and experiments, and automatically correlate flag evaluations with RUM session data.
Dependency
implementation("ai.kubesense:kubesense-android-flags:2.1.0")
// Recommended: enables flag evaluations to appear in RUM views
implementation("ai.kubesense:kubesense-android-rum:2.1.0")Enable Flags
val flagsConfig = FlagsConfiguration.Builder()
// .rumIntegrationEnabled(false) // disable RUM correlation (default: enabled)
// .trackExposures(false) // disable exposure tracking (default: enabled)
.build()
Flags.enable(flagsConfig)Evaluate flags
val client = FlagsClient.Builder().build()
// Set the targeting context (call this after user authentication)
client.setEvaluationContext(
EvaluationContext(
targetingKey = userId, // use a persistent UUID for anonymous users
attributes = mapOf(
"plan" to "premium",
"email" to "user@example.com"
)
)
)
// Evaluate flags
val isEnabled: Boolean = client.resolveBooleanValue("new-checkout-flow", false)
val theme: String = client.resolveStringValue("app-theme", "light")
val maxRetries: Int = client.resolveIntValue("max-retry-count", 3)
val discount: Double = client.resolveDoubleValue("discount-rate", 0.0)
// Structured flags
val defaultConfig = JSONObject("""{"timeout": 30, "retries": 3}""")
val config = client.resolveStructureValue("api-config", defaultConfig)Detailed resolution (with error info)
val result = client.resolve("feature-enabled", false)
if (result.errorCode != null) {
Log.e("Flags", "Resolution failed: ${result.errorMessage}")
} else {
val value = result.value
val variant = result.variant // e.g. "control" or "treatment"
val reason = result.reason // e.g. ResolutionReason.TARGETING_MATCH
}Retrieve an existing client
val client = FlagsClient.get() // default client
val analyticsClient = FlagsClient.get("analytics") // named clientOpenFeature provider
Use the standard OpenFeature API instead of the native client:
implementation("ai.kubesense:kubesense-android-flags-openfeature:2.1.0")import ai.kubesense.android.flags.FlagsClient
import ai.kubesense.android.flags.openfeature.asOpenFeatureProvider
import dev.openfeature.kotlin.sdk.OpenFeatureAPI
import dev.openfeature.kotlin.sdk.ImmutableContext
import dev.openfeature.kotlin.sdk.Value
// Register the provider
val provider = FlagsClient.Builder().build().asOpenFeatureProvider()
OpenFeatureAPI.setProviderAndWait(provider)
// Set evaluation context
OpenFeatureAPI.setEvaluationContext(
ImmutableContext(
targetingKey = userId,
attributes = mapOf("plan" to Value.String("premium"))
)
)
// Evaluate a flag
val client = OpenFeatureAPI.getClient()
val isEnabled = client.getBooleanValue("new-checkout-flow", false)When to use OpenFeature vs. FlagsClient directly:
| FlagsClient | OpenFeature Provider | |
|---|---|---|
| API style | Kubesense-native, instance-based context | Vendor-neutral, global context |
| Type system | Kotlin-native types | OpenFeature Value types |
| Structured flags | Returns JSONObject | Returns Value.Structure |
| Best for | Full Kubesense integration | Vendor-neutral or multi-provider setups |
Integrations
OkHttp
Automatically track all HTTP requests made through OkHttp as RUM resources and APM trace spans.
Dependency
implementation("ai.kubesense:kubesense-android-okhttp:2.1.0")Setup
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(
KubesenseInterceptor.Builder(
tracedHosts = listOf("api.example.com", "cdn.example.com")
).build()
)
.eventListenerFactory(KubesenseEventListener.Factory())
.build()info: Pass your backend hostnames in tracedHosts to inject distributed tracing headers. Requests to other hosts are tracked as RUM resources without trace headers.The eventListenerFactory(...) supplies detailed resource timing (DNS, connect, TTFB, download). If you also need to trace requests that are the result of redirects, add the interceptor as a network interceptor too: .addNetworkInterceptor(TracingInterceptor.Builder(tracedHosts).build()).
OpenTelemetry propagation
implementation("ai.kubesense:kubesense-android-okhttp-otel:2.1.0")Attach an OpenTelemetry parent span to an outgoing OkHttp request with the addParentSpan extension:
val request = Request.Builder()
.url("https://api.example.com/items")
.addParentSpan(parentSpan) // io.opentelemetry.api.trace.Span
.build()Jetpack Compose
Track user interactions and screen views in Compose-based UIs.
Dependency
implementation("ai.kubesense:kubesense-android-rum:2.1.0")
implementation("ai.kubesense:kubesense-android-compose:2.1.0")Automatic tap tracking
Enable Compose tap tracking on the RUM configuration:
RumConfiguration.Builder(applicationId)
.trackUserInteractions()
.enableComposeActionTracking()
.build()Navigation view tracking (Navigation for Compose)
val navController = rememberNavController().apply {
NavigationViewTrackingEffect(
navController = this,
trackArguments = true,
destinationPredicate = AcceptAllNavDestinations()
)
}If your app mixes Compose and Fragment navigation, exclude Compose host activities from ActivityViewTrackingStrategy:
RumConfiguration.Builder(applicationId)
.useViewTrackingStrategy(
ActivityViewTrackingStrategy(
trackExtras = true,
componentPredicate = object : ComponentPredicate<Activity> {
override fun accept(component: Activity) = component !is MyComposeActivity
override fun getViewName(component: Activity): String? = null
}
)
)
.build()Click tracking
Button(
onClick = trackClick(targetName = "Confirm Order") {
// your click logic
}
) {
Text("Confirm")
}Swipe / scroll tracking
val swipeableState = rememberSwipeableState(...)
val interactionSource = remember { MutableInteractionSource() }.apply {
TrackInteractionEffect(
targetName = "Product Card",
interactionSource = this,
interactionType = InteractionType.Swipe(swipeableState, Orientation.Horizontal),
attributes = mapOf("card_id" to productId)
)
}Coil
Track image loading requests made by Coil as RUM resources and APM traces.
Dependency
// Coil 2
implementation("ai.kubesense:kubesense-android-coil:2.1.0")
// Coil 3
implementation("ai.kubesense:kubesense-android-coil3:2.1.0")Also requires OkHttp integration (for network tracking):
implementation("ai.kubesense:kubesense-android-okhttp:2.1.0")Coil 2 setup
val imageLoader = ImageLoader.Builder(context)
.okHttpClient(okHttpClient) // okHttpClient configured with KubesenseInterceptor
.build()
Coil.setImageLoader(imageLoader)
// Per-request: listen for cache errors
imageView.load(imageUri) {
listener(KubesenseCoilRequestListener())
}Coil 3 setup
val imageLoader = ImageLoader.Builder(context)
.components {
add(OkHttpNetworkFetcherFactory(okHttpClient))
}
.build()
SingletonImageLoader.setSafe { imageLoader }
// Per-request: listen for loading failures
imageView.load(imageUri) {
listener(KubesenseCoilRequestListener())
}The KubesenseInterceptor on OkHttpClient creates RUM Resource and APM Trace events for network calls. KubesenseCoilRequestListener creates RUM Error events for disk cache and decoding failures.
Glide
Track image loading requests made by Glide.
Dependency
implementation("ai.kubesense:kubesense-android-rum:2.1.0")
implementation("ai.kubesense:kubesense-android-trace:2.1.0")
implementation("ai.kubesense:kubesense-android-glide:2.1.0")Setup
Extend KubesenseGlideModule and annotate with @GlideModule:
@GlideModule
class CustomGlideModule : KubesenseGlideModule(
firstPartyHosts = listOf("cdn.example.com"),
sampleRate = 20f // sample 20% of requests (default)
)This automatically tracks Glide's network requests (RUM Resource + APM Trace events) and disk cache / transformation errors (RUM Error events).
Fresco
Track image loading requests made by Fresco.
Dependency
implementation("ai.kubesense:kubesense-android-rum:2.1.0")
implementation("ai.kubesense:kubesense-android-okhttp:2.1.0")
implementation("ai.kubesense:kubesense-android-fresco:2.1.0")Setup
val config = OkHttpImagePipelineConfigFactory
.newBuilder(context, okHttpClient) // okHttpClient with KubesenseInterceptor
.setMainDiskCacheConfig(
DiskCacheConfig.newBuilder(context)
.setCacheEventListener(KubesenseFrescoCacheListener())
.build()
)
.build()
Fresco.initialize(context, config)Apollo (GraphQL)
Track Apollo Kotlin GraphQL queries and mutations.
note: Supports Apollo version 4+ only. Subscription operations are not tracked.
Dependency
implementation("ai.kubesense:kubesense-android-okhttp:2.1.0")
implementation("ai.kubesense:kubesense-android-rum:2.1.0")
implementation("ai.kubesense:kubesense-android-apollo:2.1.0")Setup
val apolloClient = ApolloClient.Builder()
.serverUrl("https://graphql.example.com/graphql")
.addInterceptor(KubesenseApolloInterceptor())
.okHttpClient(okHttpClient) // okHttpClient with KubesenseInterceptor
.build()Send GraphQL payloads (optional)
GraphQL payload sending is disabled by default. Enable it explicitly:
.addInterceptor(KubesenseApolloInterceptor(sendGraphQLPayloads = true))Cronet
Track network requests made through Cronet as RUM resources.
Dependency
implementation("ai.kubesense:kubesense-android-cronet:2.1.0")
implementation("com.google.android.gms:play-services-cronet:<version>")Setup
Replace CronetEngine.Builder with KubesenseCronetEngine.Builder:
@OptIn(ExperimentalRumApi::class)
val cronetEngine: CronetEngine = KubesenseCronetEngine.Builder(application)
// your Cronet configuration
.build()Kotlin Coroutines
Propagate RUM context and trace spans across coroutine boundaries.
RUM Coroutines
implementation("ai.kubesense:kubesense-android-rum:2.1.0")
implementation("ai.kubesense:kubesense-android-rum-coroutines:2.1.0")Report Flow errors to the RUM dashboard automatically:
suspend fun loadData() {
flow { emit(fetchFromNetwork()) }
.sendErrorToKubesense()
.collect { data -> render(data) }
}Trace Coroutines
implementation("ai.kubesense:kubesense-android-trace:2.1.0")
implementation("ai.kubesense:kubesense-android-trace-coroutines:2.1.0")Wrap coroutine blocks in a trace span:
fun doWork() {
GlobalScope.launchTraced("fetch-user", Dispatchers.IO) {
// creates and finishes a span around this block
}
runBlockingTraced("sync-data", Dispatchers.IO) {
// …
}
}
suspend fun doSuspendWork() {
withContextTraced("process-items", Dispatchers.Default) {
// …
}
val deferred = asyncTraced("fetch-config", Dispatchers.IO) { fetchConfig() }
val result = deferred.awaitTraced("fetch-config-await")
}RxJava
Propagate errors from RxJava streams to the RUM dashboard.
Dependency
implementation("ai.kubesense:kubesense-android-rum:2.1.0")
implementation("ai.kubesense:kubesense-android-rx:2.1.0")Kotlin extension
Observable.create<MyData> { emitter -> /* ... */ }
.sendErrorToKubesense()
.subscribe { data -> /* ... */ }Java API
Observable.create(emitter -> { /* ... */ })
.doOnError(new KubesenseRumErrorConsumer())
.subscribe(data -> { /* ... */ });Works with Observable, Flowable, Single, Maybe, and Completable.
SQLDelight
Detect database corruption and trace SQLDelight transactions.
Dependency
implementation("ai.kubesense:kubesense-android-rum:2.1.0")
implementation("ai.kubesense:kubesense-android-trace:2.1.0")
implementation("ai.kubesense:kubesense-android-sqldelight:2.1.0")Setup
Pass KubesenseSqliteCallback to AndroidSqliteDriver:
val database = AppDatabase(
AndroidSqliteDriver(
schema = AppDatabase.Schema,
context = context,
callback = KubesenseSqliteCallback(AppDatabase.Schema)
)
)Database corruption is detected automatically and a RUM Error event is created.
Traced transactions
// No return value
database.queries.transactionTraced("<SPAN_NAME>") {
// your queries
}
// With return value
val result = database.queries.transactionTracedWithResult("<SPAN_NAME>") {
// your queries
queryResult()
}Picasso
Track network requests made by Picasso by passing the instrumented OkHttpClient as its downloader:
// okHttpClient must be configured with KubesenseInterceptor (see OkHttp section)
val picasso = Picasso.Builder(context)
.downloader(OkHttp3Downloader(okHttpClient))
.build()
Picasso.setSingletonInstance(picasso)Retrofit
Track network requests made by Retrofit by passing the instrumented OkHttpClient:
// okHttpClient must be configured with KubesenseInterceptor (see OkHttp section)
val retrofit = Retrofit.Builder()
.client(okHttpClient)
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()Identifying Users
Attach a user identity to every event so sessions, errors, and replays can be attributed to a person:
Kubesense.setUserInfo(
id = "d0b4c2f1",
name = "Jane Doe",
email = "jane@example.com",
extraInfo = mapOf("plan" to "enterprise")
)
// Add properties later without clearing the identity
Kubesense.addUserProperties(mapOf("plan" to "trial"))
// On sign-out
Kubesense.clearUserInfo()A mobile number can be attached as of 2.1.0. It is the last parameter, after sdkCore, so pass it by name:
Kubesense.setUserInfo(id = "d0b4c2f1", name = "Jane Doe", mobile = "+15551234567")note: mobile trails sdkCore deliberately. @JvmOverloads builds its overload set by dropping trailing parameters, so appending the parameter there keeps every pre-existing overload and positional call site compiling unchanged. The value travels in the user extras and arrives as a sibling of id, name, and email; an explicit mobile argument wins over a stale "mobile" key in extraInfo.
Tracking Consent
The SDK respects user privacy via TrackingConsent. Pass the current consent state at initialization and update it any time the user changes their preference:
// At initialization
Kubesense.initialize(context, configuration, TrackingConsent.PENDING)
// After the user accepts
Kubesense.setTrackingConsent(TrackingConsent.GRANTED)
// After the user declines
Kubesense.setTrackingConsent(TrackingConsent.NOT_GRANTED)| Value | Behaviour |
|---|---|
GRANTED | Data is collected and uploaded |
NOT_GRANTED | Data is not collected; existing data is discarded |
PENDING | Data is collected locally but not uploaded until consent is granted |
Migrating from 2.0.0
Removed: per-feature endpoint setters
useCustomEndpoint(...) is gone from every feature configuration builder, along with the two Flags-specific setters. Replace all of them with a single call on the core Configuration.Builder.
| Removed in 2.1.0 | Replacement |
|---|---|
RumConfiguration.Builder.useCustomEndpoint(url) | Configuration.Builder.useKubesenseRumEndpoint(host) |
LogsConfiguration.Builder.useCustomEndpoint(url) | same |
TraceConfiguration.Builder.useCustomEndpoint(url) | same |
SessionReplayConfiguration.Builder.useCustomEndpoint(url) | same |
ProfilingConfiguration.Builder.useCustomEndpoint(url) | same |
FlagsConfiguration.Builder.useCustomFlagEndpoint(url) | same |
FlagsConfiguration.Builder.useCustomExposureEndpoint(url) | same |
// 2.0.0 — a full intake URL per feature
Rum.enable(
RumConfiguration.Builder(appId)
.useCustomEndpoint("https://intake.example.com/rum/api/v1")
.build()
)
Trace.enable(
TraceConfiguration.Builder()
.useCustomEndpoint("https://intake.example.com/rum/api/v1")
.build()
)
// 2.1.0 — one host on the core configuration, no scheme and no path
val configuration = Configuration.Builder(clientToken, env, variant)
.useKubesenseRumEndpoint("intake.example.com")
.build()
Kubesense.initialize(this, configuration, TrackingConsent.GRANTED)
Rum.enable(RumConfiguration.Builder(appId).build())
Trace.enable(TraceConfiguration.Builder().build())warning: Pass a host, not a URL. useKubesenseRumEndpoint strips any scheme and path you include and always uploads over HTTPS to https://<host>/rum/api/v1/.... Carrying an old full-URL value straight across still works — the path is discarded — but write it as a bare host to keep the intent clear.
Added: remote configuration, on by default
Existing apps pick it up with no code change. If you need the SDK to honour only what your code sets, opt out explicitly with .setRemoteConfigurationEnabled(false) — see Remote Configuration.
Added: mobile on setUserInfo
Purely additive. Every existing call site keeps compiling — see Identifying Users.
Fixed
- Session Replay no longer drops views with non-color backgrounds.
- Compose Session Replay no longer draws controls over container content.
- Session Replay re-offers resources when the RUM session changes, so replays that span a session boundary keep their assets.
Best Practices
- Initialize early — call
Kubesense.initialize()and all*.enable()calls inApplication.onCreate()to capture events from the very first screen. - Enable RUM before Session Replay and Flags — both features correlate with RUM, so RUM must be enabled first.
- Use persistent targeting keys — for anonymous users, generate a UUID once and store it in
SharedPreferences. Transition to the real user ID after authentication. - All flag attributes must be strings —
EvaluationContext.attributesis aMap<String, String>; convert numbers and booleans before passing them. - Provide flag defaults — always supply a sensible default value so your UI degrades gracefully when the network is unavailable.
- Pass your server hostnames to
KubesenseInterceptor— only requests to listed hosts receive distributed tracing headers, and those hosts should matchsetFirstPartyHosts(...)on the coreConfiguration. - Apply the granular Session Replay privacy setters — mask sensitive inputs and images by default (
TextAndInputPrivacy.MASK_SENSITIVE_INPUTS,ImagePrivacy.MASK_LARGE_ONLY) and relax only where needed. - Set
TrackingConsent.PENDINGuntil the user consents — this buffers events locally and only uploads them afterGRANTEDis set, ensuring GDPR compliance.