Skip to main content

Coralogix Android SDK — Compose

Jetpack Compose support for the Coralogix Android RUM SDK.

Adds auto-initialization of Compose instrumentation, navigation tracking, interaction naming, and session replay masking for Compose UIs.

Requirements

  • Coralogix Android RUM SDK 2.16.0+
  • Jetpack Compose

Installation

Add the dependency to your app's Gradle build file:

dependencies {
implementation("com.coralogix:android-sdk-compose:2.21.1")
}
Note

The core SDK (com.coralogix:coralogix-android-sdk) must also be initialized before the Compose module has any effect. See the core SDK README for initialization instructions.

Setup

No extra initialization code is required. The Compose module auto-initializes via AndroidX Startup when the artifact is present on the classpath. Once the core SDK is initialized in your Application.onCreate(), Compose support is active automatically.

API Reference

Tracks Compose Navigation route changes and reports them to Coralogix as screen navigation events.

@Composable
fun MyApp() {
val navController = rememberNavController()

NavigationTrackingEffect(navController = navController)

NavHost(navController = navController, startDestination = "home") {
composable("home") { HomeScreen() }
composable("detail/{id}") { DetailScreen() }
}
}

Call NavigationTrackingEffect once at the top level of your composable tree, passing your NavController. Each route change is reported as a navigation event in the active session.

Modifier.coralogixName(name: String)

Sets the element name reported in Coralogix user interaction spans for a composable.

By default, interactable composables are reported as "Composable". Use this modifier to assign a meaningful name for dashboards and session replays.

Button(
onClick = { addToCart() },
modifier = Modifier.coralogixName("AddToCartButton")
) {
Text("Add to Cart")
}

Automatic Interaction Tracking

Once the Compose module is on the classpath, tap and scroll interactions on Compose elements are captured automatically — no additional code is required.

Taps: When a user taps an interactable composable (a Button, a composable with Modifier.clickable, etc.), the SDK walks the semantics tree to find the tapped node and records an interaction span. The span's element name is the node's contentDescription, or "Composable" by default — use Modifier.coralogixName() to assign a meaningful name.

Scrolls: Scrollable containers (LazyColumn, LazyRow, ScrollableColumn, etc.) that expose scroll semantics are tracked. Scroll spans are attributed to the container node.

Limitations

  • Only composables that expose semantics to the accessibility system are tracked. Custom composables that draw content without setting semantics properties will not be hit-tested.
  • Elements marked with Modifier.semantics { invisibleToUser() } are pruned from the semantics tree before the SDK sees them. Taps on these elements will not produce interaction spans (see Known Limitations).

Modifier.coralogixMasked()

Opts a composable into Coralogix masking, regardless of the global masking policy.

Text(
text = user.creditCardNumber,
modifier = Modifier.coralogixMasked()
)

Masking applies to the whole subtree — every descendant is masked too, and a descendant cannot be opted back out. Applying the modifier to a container is therefore enough to cover its contents, which is the recommended way to protect a composite element such as a PIN keypad whose keys are individually clickable.

A masked composable, and anything inside it:

  • is covered by a black overlay in session replays;
  • absorbs tap markers — a tap landing anywhere inside it draws no marker in the recording, so the replay does not reveal which part of the masked area was touched;
  • reports its inner text as *** in user interaction events.

Everything else on the interaction event is reported as-is — including the element's test tag, its Modifier.coralogixName, and the touch coordinates — and the event is still emitted. This matches the Coralogix web SDK: masking hides what an element says, not what it is or that it was used. The event carries interaction_context.is_masked_element (always present) so you can drop or rewrite masked interactions in beforeSend — see the core README's Privacy and masking section for recipes.

Upgrading from 2.19.4: that release also redacted the id, name, and resolved element of a masked composable. From 2.20.0 those fields are reported as-is again to match the web SDK; a coralogixName you would not want reported for a masked element should be handled via is_masked_element in beforeSend.

Session Replay Masking

The Compose module integrates with the core SDK's privacy masking pipeline. All masking rules configured in SessionReplayOptions apply to Compose content, with the limitations noted below.

Text masking

maskAllTexts = true and textsToMask work for Compose nodes whose SemanticsProperties.Text is non-empty (i.e. composables that expose text to the accessibility system — Text, Button labels, etc.).

SessionReplayOptions(
maskAllTexts = false,
textsToMask = listOf("(?i)password", "tok_.*") // Kotlin regex, not JS /pattern/flags syntax
)
Note

Regex patterns use Kotlin/Java syntax. Write (?i)button for case-insensitive matching, not /button/i.

Explicit masking

Modifier.coralogixMasked() masks the composable and its whole subtree unconditionally, regardless of any global policy. See Modifier.coralogixMasked() for exactly what masking covers.

Limitation: a real Android View embedded via AndroidView { } inside a masked composable is still covered by the black overlay, but is not treated as masked for interaction reporting, so its text is reported normally. Embedded views are attached at the root of the Compose host rather than under the composable that contains them, so the masked ancestor is not visible to them. Wrapping in another masked composable does not help. If the embedded view's text is sensitive, mask it directly inside the factory:

AndroidView(factory = { ctx -> MySensitiveView(ctx).apply { maskView() } })

Image masking

maskAllImages = true masks composables that expose Role.Image semantics.

SessionReplayOptions(maskAllImages = true)

Limitation: Role.Image is only set when contentDescription != null on Image / AsyncImage composables. Images without a content description are not detected and will not be masked by this option. Use Modifier.coralogixMasked() on the composable directly to guarantee masking in those cases.

Password field masking

maskInputFieldsOfTypes = listOf(EditTextType.PASSWORD) masks Compose text fields that use PasswordVisualTransformation.

SessionReplayOptions(
maskInputFieldsOfTypes = listOf(EditTextType.PASSWORD)
)

Limitation: For Compose nodes, only EditTextType.PASSWORD is evaluated — detected via SemanticsProperties.Password, which is set by PasswordVisualTransformation. All other EditTextType values (EMAIL, PHONE, NUMBER, etc.) have no effect on Compose text fields. Use Modifier.coralogixMasked() on fields that do not use PasswordVisualTransformation but should still be masked.

Known Limitations

Modifier.semantics { invisibleToUser() } — elements invisible to the SDK

Compose's invisibleToUser() semantics modifier marks a node as invisible to the accessibility system. Compose prunes these nodes from the accessibility-facing semantics tree before the SDK ever sees them. Because the SDK's Compose integration — hit-testing, interaction tracking, and session replay masking — is built entirely on the semantics tree, any node marked invisibleToUser() is completely invisible to the SDK.

This has two consequences:

1. Interaction tracking gaps

Taps on elements marked invisibleToUser() will not produce interaction spans. The hit-test walk never finds the node. This is consistent with how TalkBack and other accessibility services behave, but it is a silent gap.

2. Masking privacy risk

If a composable that should be masked (an image, a sensitive text field, or a node with Modifier.coralogixMasked()) is also marked invisibleToUser(), it will not be masked in session replay. The node renders visually and appears in the captured frame, but the SDK's masking pass never sees it. maskAllImages, maskAllTexts, and Modifier.coralogixMasked() are all affected.

Mitigation: Apply Modifier.coralogixMasked() to a parent container that is itself visible in the semantics tree. The parent node will be masked — covering the invisibleToUser() children by area, even though those children are not individually traversed.

maskAllImages requires contentDescription

maskAllImages detects images via Role.Image semantics, which is only set when a contentDescription is provided on Image / AsyncImage. Images without a content description are not detected. Use Modifier.coralogixMasked() directly on those composables.

maskInputFieldsOfTypes — PASSWORD only for Compose

Only EditTextType.PASSWORD is evaluated for Compose text fields (detected via SemanticsProperties.Password / PasswordVisualTransformation). All other EditTextType values have no effect on Compose content. Use Modifier.coralogixMasked() for other sensitive field types.

License

Copyright (c) 2025 Coralogix
Licensed under the Apache License, Version 2.0
Last updated on