Getting started with observability for Android

This guide shows how to add LaunchDarkly observability to an Android application written in Kotlin or Java. When you follow this guide, you’ll pair the Android SDK with two plugins that ship telemetry from the device back to LaunchDarkly. By the end, you’ll have the SDK and plugins installed, observability and session replay turned on, and confirmation that data is flowing.

This SDK is in early access

The Android observability plugin is in early access. Its APIs may change before the 1.x release.

The Android SDK supports two complementary telemetry plugins:

  • Observability captures errors, crashes, logs, and traces. Use it to debug failures in production, watch error rates for a release, and trace network calls end-to-end. Observability automatically reports uncaught exceptions and activity lifecycle events.
  • Session replay captures UI snapshots of what a user encountered. Use it when a stack trace alone doesn’t tell you why a user got stuck, and pair it with masking to keep sensitive fields out of the recording.

Both plugins install alongside the LaunchDarkly Android SDK and initialize in the same place. This guide explains how to enable both. To learn more about every configuration field, the full LDObserve method surface, and advanced distributed-tracing patterns, read the Android SDK observability reference.

Prerequisites

To complete this guide, you need:

  • An Android project targeting Android 5.0 (API level 21) or higher.
  • The LaunchDarkly Android SDK version 5.9.0 or later.
  • A build set up with Gradle (Groovy or Kotlin DSL).
  • A LaunchDarkly account and a mobile key for the environment you’re testing against. Find the mobile key on the SDK keys page under Settings. Keys are specific to each project and environment. Mobile keys are not secrets and are safe to ship in client-side code.
Building UI with Jetpack Compose?

Mobile keys are safe to ship in client-side code; never embed a server-side SDK key in a client app.

Step 1: Install the plugin

Add both the LaunchDarkly Android SDK and the observability plugin as dependencies. In your module’s build.gradle:

build.gradle
1implementation 'com.launchdarkly:launchdarkly-android-client-sdk:5.+'
2implementation 'com.launchdarkly:launchdarkly-observability-android:0.21.0'

Then import the plugin where you’ll initialize the client:

Import the plugin
1import com.launchdarkly.sdk.*
2import com.launchdarkly.sdk.android.*
3import com.launchdarkly.observability.plugin.Observability
4import com.launchdarkly.sdk.android.integrations.Plugin

Step 2: Initialize the client

On Android, the LaunchDarkly client is initialized one time for the lifetime of the app, so the natural home is your Application subclass rather than an Activity. The Observability plugin constructor takes your application context and your mobile key:

BaseApplication.kt
1class BaseApplication : Application() {
2 override fun onCreate() {
3 super.onCreate()
4
5 val mobileKey = "example-mobile-key"
6
7 val ldConfig = LDConfig.Builder(AutoEnvAttributes.Enabled)
8 .mobileKey(mobileKey)
9 .plugins(
10 Components.plugins().setPlugins(
11 listOf(
12 Observability(this, mobileKey)
13 )
14 )
15 )
16 .build()
17
18 val context = LDContext.create("example-context-key")
19 val client = LDClient.init(this, ldConfig, context, 0)
20 }
21}

To give your app a recognizable name in the LaunchDarkly UI, set serviceName through ObservabilityOptions:

Set serviceName
1Observability(
2 this,
3 mobileKey,
4 ObservabilityOptions(
5 serviceName = "my-android-app",
6 serviceVersion = "1.0.0"
7 )
8)

serviceName defaults to observability-android if you don’t set it.

Step 3: Turn on session replay

Add the SessionReplay plugin to the list — after Observability, per the ordering rule from Concepts:

Add session replay plugin
1import com.launchdarkly.observability.plugin.Observability
2import com.launchdarkly.observability.replay.plugin.SessionReplay
3
4val ldConfig = LDConfig.Builder(AutoEnvAttributes.Enabled)
5 .mobileKey(mobileKey)
6 .plugins(
7 Components.plugins().setPlugins(
8 listOf(
9 Observability(this, mobileKey),
10 SessionReplay() // must come after Observability
11 )
12 )
13 )
14 .build()
Gating session replay on consent or a flag

If you need to wait for user consent or roll session replay out behind a feature flag, construct it with ReplayOptions(enabled = false). When you’re ready to enable it, call LDReplay.start(). This is the privacy-friendly path for regulated apps, so we recommend not recording until you have permission.

Step 4: Decide what to mask

By default, text inputs are masked and other text is visible. Rather than memorize every flag, pick the situation you’re in. Masking is configured through a PrivacyProfile on ReplayOptions. There are several configuration options depending on your use case.

They are:

  • Production — mask everything, including non-input text and views flagged by sensitive keywords.
  • Local debugging — turn masking off on your own test data so you can see what’s going on.
  • Balanced — mask inputs and anything matching sensitive keywords (passwords, etc.) but keep ordinary labels visible.
1SessionReplay(
2 options = ReplayOptions(
3 privacyProfile = PrivacyProfile(
4 maskTextInputs = true,
5 maskText = true,
6 maskBySemanticsKeywords = true
7 )
8 )
9)

The simple rule is to start fully masked, and only unmask what you’ve confirmed is safe to record.

An Android session replay showing a login screen where the password field is rendered as a solid block while surrounding labels remain visible, demonstrating text-input masking.

A session replay with masking applied: the password field is captured as a blank block while non-sensitive labels remain readable.

Masking a specific element

When a profile is almost right but one field is the exception, override it at the element level. The specific mechanisms to do this differ based on which UI toolkit you use:

  • Jetpack Compose — add the .ldMask() modifier (or .ldUnmask() to reveal something the profile would hide).
  • Traditional XML views — retrieve the View with findViewById, then call the .ldMask() extension function on the result.
1import com.launchdarkly.observability.api.ldMask
2
3@Composable
4fun CreditCardField() {
5 var number by remember { mutableStateOf("") }
6 TextField(
7 value = number,
8 onValueChange = { number = it },
9 modifier = Modifier
10 .fillMaxWidth()
11 .ldMask() // mask this composable in session replay
12 )
13}

Step 5: Run it and confirm data is arriving

Build and run on a device or emulator:

Build and run
$./gradlew installDebug

Test out the app by navigating between a couple of screens. Trigger an error deliberately if you want to verify that Observability captures it correctly. Because crash reporting is on by default, an uncaught exception shows up as an error automatically.

Then open the Observability page in the LaunchDarkly UI. Logs and errors attributed to your serviceName appear there, as well as a session replay recording you can scrub through with your masking choices applied. When your service name appears in Observability, that is a confirmation that the SDK initialized successfully and is recording data.

The LaunchDarkly observability view listing logs and errors from an Android app, attributed to the service name set during initialization.

Logs and errors arriving in LaunchDarkly, attributed to the serviceName you set in Step 2.

Here is an example session replay from an Android app in LaunchDarkly:

Complete code sample

BaseApplication.kt (complete)
1import android.app.Application
2import com.launchdarkly.sdk.*
3import com.launchdarkly.sdk.android.*
4import com.launchdarkly.observability.plugin.Observability
5import com.launchdarkly.observability.replay.plugin.SessionReplay
6import com.launchdarkly.observability.replay.ReplayOptions
7import com.launchdarkly.observability.replay.PrivacyProfile
8
9class BaseApplication : Application() {
10 override fun onCreate() {
11 super.onCreate()
12
13 val mobileKey = "example-mobile-key"
14
15 val ldConfig = LDConfig.Builder(AutoEnvAttributes.Enabled)
16 .mobileKey(mobileKey)
17 .plugins(
18 Components.plugins().setPlugins(
19 listOf(
20 Observability(
21 this,
22 mobileKey,
23 ObservabilityOptions(
24 serviceName = "my-android-app",
25 serviceVersion = "1.0.0"
26 )
27 ),
28 SessionReplay(
29 options = ReplayOptions(
30 privacyProfile = PrivacyProfile(
31 maskTextInputs = true,
32 maskText = true,
33 maskBySemanticsKeywords = true
34 )
35 )
36 )
37 )
38 )
39 )
40 .build()
41
42 val context = LDContext.create("example-context-key")
43 val client = LDClient.init(this, ldConfig, context, 0)
44 }
45}

What to explore next

  • Automatic HTTP and interaction instrumentation: Add the ByteBuddy Gradle plugin and the OkHttp or HttpURLConnection instrumentation dependencies to trace network calls automatically. To learn more, read the Android SDK docs’ Configure additional instrumentations.
  • Every configuration field: The complete ObservabilityOptions, including instrumentations, tracesApi, metricsApi, and logsApiLevel, and ReplayOptions tables are in the SDK reference documentation and in Configuration for client-side observability.
  • Custom and distributed tracing: Create spans with LDObserve.startSpan(name, attributes), and propagate context across coroutines, threads, and services using the OpenTelemetry Context API and W3CTraceContextPropagator.
  • Working with your data: When telemetry is flowing, set up alerts and dashboards, then investigate issues with Vega.

Troubleshooting

Use this section to investigate issues.

No data appearing

  • Confirm you pasted the mobile key for the environment you’re viewing in the UI, and not for a different environment.
  • Make sure initialization runs in your Application subclass and that the subclass is registered in AndroidManifest.xml with android:name.
  • Test the app a bit more and refresh. Data isn’t always instantaneous, so it may take a few minutes to display correctly.

Session replay isn’t recording, but observability works

  • Check plugin order: SessionReplay must be listed after Observability. If it’s first or Observability is absent, session replay logs an error and stays inactive.
  • If you configured ReplayOptions(enabled = false) for consent-gated rollout, confirm LDReplay.start() is actually being called.

Android Observability SDK plugin ordering error.

Android Observability SDK plugin ordering error

Masking not applied to a specific view

  • For Compose, confirm .ldMask() is applied as a Modifier on the composable. For XML, confirm .ldMask() is called on the resolved View after findViewById.

Conclusion

In this guide, you instrumented an Android application for LaunchDarkly observability. You can now:

  • Capture errors, crashes, logs, and session replays from a Kotlin or Java app
  • Control what session replay records through PrivacyProfile presets and per-element masking in both Compose and XML
  • Confirm telemetry is flowing in the LaunchDarkly UI, ready for alerts, dashboards, and Vega