Android RUM Integration
This guide walks through integrating the Kubesense Android RUM SDK into your application: adding the dependency, initializing the SDK, configuring view / action / resource tracking, and adding manual instrumentation where automatic tracking does not apply.
| Field | Value |
|---|---|
| Maven coordinates | ai.kubesense:kubesense-android-rum:<version> |
| Min SDK | 23 (Android 6.0) — auto-instrumented integrations require minSdk 29 |
| Target SDK | 36 |
| Language | Java 11 source/target compatibility, Kotlin |
1. Add the dependency
In your application module's build.gradle.kts:
dependencies {
implementation("ai.kubesense:kubesense-android-core:<version>")
implementation("ai.kubesense:kubesense-android-rum:<version>")
// Optional integrations
implementation("ai.kubesense:kubesense-android-okhttp:<version>")
implementation("ai.kubesense:kubesense-android-compose:<version>")
implementation("ai.kubesense:kubesense-android-rum-coroutines:<version>")
implementation("ai.kubesense:kubesense-android-timber:<version>")
}Configure the Maven repository:
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven { url = uri("https://repo.kubesense.ai/repository/kubesense/") }
mavenCentral()
google()
}
}2. Initialize the SDK
Initialize the core SDK and enable RUM in your Application.onCreate(). The Kubesense core must be initialized before Rum.enable(...).
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
val configuration = Configuration.Builder(
clientToken = BuildConfig.KUBESENSE_CLIENT_TOKEN,
env = BuildConfig.BUILD_TYPE, // "debug", "staging", "prod"
variant = BuildConfig.FLAVOR
)
.useSite(KubesenseSite.US2)
.setFirstPartyHosts(listOf("api.example.com"))
.build()
Kubesense.initialize(this, configuration, TrackingConsent.GRANTED)
val rumConfig = RumConfiguration.Builder(BuildConfig.KUBESENSE_RUM_APPLICATION_ID)
.useViewTrackingStrategy(ActivityViewTrackingStrategy(trackExtras = true))
.trackUserInteractions()
.trackLongTasks(longTaskThresholdMs = 100L)
.trackNonFatalAnrs(true)
.trackBackgroundEvents(false)
.trackAnonymousUser(true)
.setSessionSampleRate(100f)
.build()
Rum.enable(rumConfig)
}
}Register the Application class in AndroidManifest.xml:
<application
android:name=".MyApplication"
...>
</application>3. Send RUM events to a Kubecol collector
By default, the SDK sends RUM events to the managed Kubesense intake selected via useSite(...) (e.g. KubesenseSite.US2). The effective endpoint is <site.intakeEndpoint>/rum/api/v1.
To route events through your own Kubecol collector instead (self-hosted, on-prem, or staging), override the RUM endpoint with useCustomEndpoint(...) on the RumConfiguration.Builder. The value must be the full intake URL — the SDK will not append /rum/api/v1 to it.
val rumConfig = RumConfiguration.Builder(BuildConfig.KUBESENSE_RUM_APPLICATION_ID)
// Point RUM at your Kubecol collector
.useCustomEndpoint("https://kubecol.my-company.internal/rum/api/v1")
.useViewTrackingStrategy(ActivityViewTrackingStrategy(trackExtras = true))
.trackUserInteractions()
.build()
Rum.enable(rumConfig)Recommended: keep the URL out of code
Store the collector URL in local.properties / gradle.properties and surface it via BuildConfig so each environment (debug / staging / prod) can target a different collector without code changes:
// app/build.gradle.kts
android {
buildTypes {
debug {
buildConfigField(
"String",
"KUBESENSE_RUM_URL",
"\"${project.findProperty("KUBESENSE_RUM_URL") ?: ""}\""
)
}
release {
buildConfigField(
"String",
"KUBESENSE_RUM_URL",
"\"https://kubecol.prod.my-company.com/rum/api/v1\""
)
}
}
}val rumConfig = RumConfiguration.Builder(BuildConfig.KUBESENSE_RUM_APPLICATION_ID)
.apply {
if (BuildConfig.KUBESENSE_RUM_URL.isNotBlank()) {
useCustomEndpoint(BuildConfig.KUBESENSE_RUM_URL)
}
}
.build()Per-feature endpoints
Each Kubesense feature exposes its own useCustomEndpoint(...) — they are independent. Configure the matching collector path for any other feature you enable:
| Feature | Builder | Suggested path |
|---|---|---|
| RUM | RumConfiguration.Builder | https://<kubecol-host>/rum/api/v1 |
| Logs | LogsConfiguration.Builder | https://<kubecol-host>/logs/api/v1 |
| Traces | TraceConfiguration.Builder | https://<kubecol-host>/traces/api/v1 |
| Session Replay | SessionReplayConfiguration.Builder | https://<kubecol-host>/session-replay/api/v1 |
TLS & connectivity checklist
- Use HTTPS in production. If the collector uses a private CA, install the CA in the device's user/system trust store or ship a
network_security_config.xml. - For local development against an HTTP collector (e.g. a laptop on the LAN), enable cleartext for that host only via
network_security_config.xml— do not enableusesCleartextTrafficglobally. - Ensure the device can reach the collector (VPN, firewall rules, DNS).
- The
clientTokenset inConfiguration.Builder(...)must be one the Kubecol collector accepts.
note: With Kubesense.setVerbosity(Log.VERBOSE) you should see upload attempts targeting your collector host in Logcat. Generate a few interactions and confirm requests land at the collector.
4. View tracking
The SDK automatically reports a RUM view whenever the user lands on a tracked screen. Pick the strategy that matches your app's navigation model.
| Strategy | When to use |
|---|---|
ActivityViewTrackingStrategy | Multi-Activity apps without Fragments |
FragmentViewTrackingStrategy | Single-Activity / multi-Fragment apps |
MixedViewTrackingStrategy | Mixed Activities + Fragments |
NavigationViewTrackingStrategy | AndroidX Navigation Component |
null | Manual tracking only (call startView / stopView yourself) |
Activities
.useViewTrackingStrategy(ActivityViewTrackingStrategy(trackExtras = true))Fragments
.useViewTrackingStrategy(FragmentViewTrackingStrategy(trackArguments = true))AndroidX Navigation
.useViewTrackingStrategy(
NavigationViewTrackingStrategy(
navigationViewId = R.id.nav_host_fragment,
trackArguments = true
)
)Filter or rename screens
Provide a ComponentPredicate to skip components or rewrite their reported name:
val predicate = object : ComponentPredicate<Activity> {
override fun accept(component: Activity) =
component !is SplashActivity
override fun getViewName(component: Activity): String? =
component::class.simpleName?.removeSuffix("Activity")
}
.useViewTrackingStrategy(ActivityViewTrackingStrategy(true, predicate))Manual view tracking
GlobalRumMonitor.get().startView(
key = this, // any object key (Activity / Fragment / String)
name = "Checkout",
attributes = mapOf("cart_size" to 3)
)
// later, when the user leaves
GlobalRumMonitor.get().stopView(key = this)5. User-interaction (action) tracking
trackUserInteractions() installs a touch interceptor that auto-emits TAP, SCROLL, and SWIPE actions for tagged views.
.trackUserInteractions(
touchTargetExtraAttributesProviders = arrayOf(MyExtraAttrsProvider()),
interactionPredicate = MyInteractionPredicate()
)To make views distinguishable in the RUM Explorer, give them a stable android:id or a contentDescription. Tag a Compose composable with Modifier.kubesenseTarget("name") (requires the kubesense-android-compose integration).
Manual actions
val rum = GlobalRumMonitor.get()
// Discrete action
rum.addAction(RumActionType.TAP, "checkout_button")
// Continuous action (auto-stops after 10s if not stopped)
rum.startAction(RumActionType.SCROLL, "products_list")
// ... user finishes scrolling
rum.stopAction(RumActionType.SCROLL, "products_list")Action types: TAP, SCROLL, SWIPE, CLICK, BACK, CUSTOM.
6. Resource (network) tracking
The recommended setup uses the OkHttp integration:
val tracedHosts = listOf("api.example.com")
val client = OkHttpClient.Builder()
.addInterceptor(KubesenseInterceptor.Builder(tracedHosts).build())
.addNetworkInterceptor(TracingInterceptor.Builder(tracedHosts).build())
.eventListenerFactory(KubesenseEventListener.Factory())
.build()tracedHosts must match setFirstPartyHosts(...) on the core Configuration to enable distributed tracing headers.
Manual resource tracking
val rum = GlobalRumMonitor.get()
val key = "req-${UUID.randomUUID()}"
rum.startResource(key, RumResourceMethod.GET, "https://api.example.com/items")
try {
val response = httpCall()
rum.stopResource(
key = key,
statusCode = response.code,
size = response.contentLength,
kind = RumResourceKind.NATIVE
)
} catch (t: IOException) {
rum.stopResourceWithError(
key = key,
statusCode = null,
message = "Request failed",
source = RumErrorSource.NETWORK,
throwable = t
)
}7. Error tracking
Crashes (uncaught exceptions) are captured automatically. Add custom errors with:
GlobalRumMonitor.get().addError(
message = "Failed to parse response",
source = RumErrorSource.SOURCE,
throwable = exception,
attributes = mapOf("endpoint" to "/items")
)Sources: NETWORK, SOURCE, CONSOLE, LOGGER, AGENT, WEBVIEW, CUSTOM, REPORT.
For native (NDK) crashes, add kubesense-android-ndk and call NdkCrashReports.enable() after Kubesense.initialize.
8. Long tasks & ANRs
.trackLongTasks(longTaskThresholdMs = 100L) // any main-thread task > 100ms
.trackNonFatalAnrs(true) // Watchdog-thread ANR detection- Long tasks are reported as RUM
long_taskevents. - Fatal ANRs are auto-tracked on Android 11+ (API 30+) using
ApplicationExitInfo. - Non-fatal ANRs use a watchdog thread; recommended on API 29 and below. On API 30+ it can produce duplicate events alongside fatal-ANR reporting.
9. Vitals & frame metrics
CPU / memory / frame-rate samples are collected automatically and aggregated per view. Tune the cadence with:
.setVitalsUpdateFrequency(VitalsUpdateFrequency.AVERAGE)Slow-frame and freeze metrics are enabled by default. Customize or disable:
.setSlowFramesConfiguration(SlowFramesConfiguration.DEFAULT) // or null to disable10. Sessions
A session begins on the first user interaction or view, and ends after 15 min of inactivity (or 4 hours total). Override session sampling and lifecycle hooks:
.setSessionSampleRate(100f) // 0..100 — % of sessions kept
.setSessionListener { sessionId, isDiscarded ->
Log.d("Kubesense", "session=$sessionId discarded=$isDiscarded")
}Force-stop the active session:
GlobalRumMonitor.get().stopSession()11. Custom attributes & user info
Set user identity once after initialization:
Kubesense.setUserInfo(
id = "user-1234",
name = "Jane Doe",
email = "jane@example.com",
extraInfo = mapOf("plan" to "premium")
)Add global RUM attributes that apply to every event:
GlobalRumMonitor.get().addAttribute("build_flavor", BuildConfig.FLAVOR)Add attributes scoped to the active view only:
GlobalRumMonitor.get().addViewAttributes(mapOf("experiment_bucket" to "B"))12. Event mappers (PII scrubbing)
Mutate or drop events before they leave the device:
.setViewEventMapper { event ->
event.view.url = event.view.url.replace(Regex("/users/\\d+"), "/users/?")
event
}
.setErrorEventMapper { event ->
if (event.error.message.contains("token=")) null else event // drop event
}Mappers exist for ViewEvent, ResourceEvent, ActionEvent, ErrorEvent, LongTaskEvent, VitalOperationStepEvent, and VitalAppLaunchEvent.
13. Feature flag evaluations
GlobalRumMonitor.get().addFeatureFlagEvaluation("new_checkout", true)
GlobalRumMonitor.get().addFeatureFlagEvaluations(
mapOf("new_checkout" to true, "ui_variant" to "B")
)Evaluations are attached to the active view and cleared when the view stops.
14. Optional integrations
| Library | Purpose |
|---|---|
kubesense-android-okhttp | Auto-track OkHttp resources & distributed tracing |
kubesense-android-compose | Tag composables for action tracking |
kubesense-android-rum-coroutines | Wrap coroutines with RUM scopes |
kubesense-android-timber | Forward Timber logs |
kubesense-android-rx | RxJava error tracking |
kubesense-android-glide / coil / coil3 / fresco | Image loader resource tracking |
kubesense-android-sqldelight | SQLDelight query tracking |
kubesense-android-ndk | Native crash reporting |
15. Verifying the integration
- Set verbose logging during bring-up:
Kubesense.setVerbosity(Log.VERBOSE) - Enable the on-screen view debug overlay:
GlobalRumMonitor.get().debug = true - (Optional) Add the debug widget for live event inspection:
// dependency: ai.kubesense:kubesense-android-rum-debug-widget .enableRumDebugWidget(this) - Build & install the app, exercise a screen, then confirm the session, view, action, and resource events appear in the Kubesense RUM Explorer.
16. Troubleshooting
| Symptom | Likely cause |
|---|---|
| No events in the dashboard | applicationId / clientToken mismatch, or TrackingConsent is not GRANTED |
RUM Feature is already enabled warning | Rum.enable(...) was called twice on the same SDK core |
| Network resources missing | KubesenseInterceptor not added, or host not in setFirstPartyHosts |
| Action names show generic IDs | Views lack android:id / contentDescription (or Modifier.kubesenseTarget(...) in Compose) |
| Background events missing | trackBackgroundEvents(true) not enabled |