Kubesense

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.

FieldValue
Maven coordinatesai.kubesense:kubesense-android-rum:<version>
Min SDK23 (Android 6.0) — auto-instrumented integrations require minSdk 29
Target SDK36
LanguageJava 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)

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:

FeatureBuilderSuggested path
RUMRumConfiguration.Builderhttps://<kubecol-host>/rum/api/v1
LogsLogsConfiguration.Builderhttps://<kubecol-host>/logs/api/v1
TracesTraceConfiguration.Builderhttps://<kubecol-host>/traces/api/v1
Session ReplaySessionReplayConfiguration.Builderhttps://<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 enable usesCleartextTraffic globally.
  • Ensure the device can reach the collector (VPN, firewall rules, DNS).
  • The clientToken set in Configuration.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.

StrategyWhen to use
ActivityViewTrackingStrategyMulti-Activity apps without Fragments
FragmentViewTrackingStrategySingle-Activity / multi-Fragment apps
MixedViewTrackingStrategyMixed Activities + Fragments
NavigationViewTrackingStrategyAndroidX Navigation Component
nullManual 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_task events.
  • 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 disable

10. 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

LibraryPurpose
kubesense-android-okhttpAuto-track OkHttp resources & distributed tracing
kubesense-android-composeTag composables for action tracking
kubesense-android-rum-coroutinesWrap coroutines with RUM scopes
kubesense-android-timberForward Timber logs
kubesense-android-rxRxJava error tracking
kubesense-android-glide / coil / coil3 / frescoImage loader resource tracking
kubesense-android-sqldelightSQLDelight query tracking
kubesense-android-ndkNative crash reporting

15. Verifying the integration

  1. Set verbose logging during bring-up:
    Kubesense.setVerbosity(Log.VERBOSE)
  2. Enable the on-screen view debug overlay:
    GlobalRumMonitor.get().debug = true
  3. (Optional) Add the debug widget for live event inspection:
    // dependency: ai.kubesense:kubesense-android-rum-debug-widget
    .enableRumDebugWidget(this)
  4. Build & install the app, exercise a screen, then confirm the session, view, action, and resource events appear in the Kubesense RUM Explorer.

16. Troubleshooting

SymptomLikely cause
No events in the dashboardapplicationId / clientToken mismatch, or TrackingConsent is not GRANTED
RUM Feature is already enabled warningRum.enable(...) was called twice on the same SDK core
Network resources missingKubesenseInterceptor not added, or host not in setFirstPartyHosts
Action names show generic IDsViews lack android:id / contentDescription (or Modifier.kubesenseTarget(...) in Compose)
Background events missingtrackBackgroundEvents(true) not enabled