From f8f5b047fa8437bc623723644d3475156bd990d1 Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Wed, 7 Oct 2026 15:46:40 +0200 Subject: [PATCH 1/2] docs(android): Harmonize Android Navigation and Jetpack Compose docs Commit moves navigation-specific content from the Android Jetpack Compose page to the corresponding Navigation page. It also refines the Navigation page for consistency and correctness. --- .../integrations/jetpack-compose/index.mdx | 240 +------------- .../android/integrations/navigation/index.mdx | 295 +++++++++++++++--- 2 files changed, 273 insertions(+), 262 deletions(-) diff --git a/docs/platforms/android/integrations/jetpack-compose/index.mdx b/docs/platforms/android/integrations/jetpack-compose/index.mdx index 2aa348b379e88..0b62b372c9a0c 100644 --- a/docs/platforms/android/integrations/jetpack-compose/index.mdx +++ b/docs/platforms/android/integrations/jetpack-compose/index.mdx @@ -3,18 +3,18 @@ title: Jetpack Compose caseStyle: camelCase supportLevel: production sdk: sentry.java.compose -description: Learn more about the Sentry Compose integration. +description: Learn more about Sentry's integrations for Jetpack Compose. categories: - mobile og_image: /og-images/platforms-android-integrations-jetpack-compose.png --- -The `sentry-compose-android` library provides [Jetpack Compose](https://developer.android.com/jetpack/androidx/releases/compose) support in the following areas: +The `sentry-compose-android` and `sentry-android-navigation3` libraries provide [Jetpack Compose](https://developer.android.com/compose) support in the following areas: - `@Composable` performance metrics -- Transaction and breadcrumb support for user interactions (clicks, swipes and scroll gestures) -- Transaction and breadcrumb support for navigation events -- Support for View Hierarchies +- Support for user interactions (clicks, swipes and scroll gestures) +- Support for navigation +- Support for debugging view hierarchies ## Performance Metrics @@ -28,8 +28,12 @@ With Jetpack Compose performance metrics the Sentry Android SDK can automaticall This feature requires an underlying transaction to attach it's spans to. Transactions can be created manually or automatically by configuring -- Activity instrumentation -- [Navigation Instrumentation](#jetpack-compose-navigation) +- + Activity instrumentation + +- + Navigation instrumentation + - [User Interactions](#user-interactions) @@ -132,231 +136,19 @@ fun LoginScreen() { } ``` - - -If a `@Composable` doesn't have a `.sentryTag` or a `.testTag` modifier applied, both breadcrumbs and transactions won't be captured because they can't be uniquely identified. -To automatically generate tags, refer to the docs about our Sentry Kotlin Compiler Plugin. - - - - - -If you want to customize the recorded breadcrumbs/transactions, you can skip to [the section below](#customize-the-recorded-breadcrumbtransaction). - - - -## Jetpack Compose Navigation +#### Customize the Recorded Breadcrumb/Transaction -In this section, we get you up and running with Sentry's Compose Navigation Integration, so that it will automatically add a breadcrumb and start a transaction for each navigation event. +User interaction instrumentation uses the `sentryTag` or `testTag` value to identify the composable in the recorded breadcrumb and transaction. Keep these tags stable and free of PII or other sensitive data. -![Jetpack Compose Navigation Breadcrumbs](./img/jetpack-compose-navigation.png) - -### Auto-Installation With the Sentry Android Gradle Plugin - -#### Install - -If you're using the [Sentry Android Gradle Plugin](/platforms/android/configuration/gradle/), the Jetpack Compose Navigation Integration is automatically applied, and both breadcrumbs and transactions are automatically created for every navigation event. - -Add the Sentry Android Gradle plugin in `build.gradle`: - -```groovy -plugins { - id "io.sentry.android.gradle" version "{{@inject packages.version('sentry.java.android.gradle-plugin', '3.0.0') }}" -} -``` - -```kotlin -plugins { - id("io.sentry.android.gradle") version "{{@inject packages.version('sentry.java.android.gradle-plugin', '3.0.0') }}" -} -``` +If needed, you can filter sensitive data from breadcrumbs and transactions before they're sent to Sentry via the `SentryOptions.BeforeBreadcrumbCallback` and `SentryOptions.BeforeSendTransactionCallback`, respectively. -Learn more about the Sentry Android Gradle plugin in our [Gradle](/platforms/android/configuration/gradle/) documentation. Details about auto-installation can be found in the [Auto Instrumentation](/platforms/android/configuration/gradle/#tracing-auto-instrumentation) section. +If a `@Composable` doesn't have a `.sentryTag` or a `.testTag` modifier applied, both breadcrumbs and transactions won't be captured because they can't be uniquely identified. +To automatically generate tags, refer to the docs about our Sentry Kotlin Compiler Plugin. -#### Disable - -If you want to disable the Jetpack Compose instrumentation feature, you can adapt your `build.gradle(.kts)` file like so: - -```groovy -import io.sentry.android.gradle.extensions.InstrumentationFeature - -sentry { - tracingInstrumentation { - enabled = true - features = EnumSet.allOf(InstrumentationFeature) - InstrumentationFeature.COMPOSE - } -} -``` - -```kotlin -import java.util.EnumSet -import io.sentry.android.gradle.extensions.InstrumentationFeature - -sentry { - tracingInstrumentation { - enabled.set(true) - features.set(EnumSet.allOf(InstrumentationFeature::class.java) - InstrumentationFeature.COMPOSE) - } -} -``` - -### Manual Installation - -#### Install - -Sentry captures data by adding a `withSentryObservableEffect` extension function to `NavHostController`. To add the Navigation integration, install the [Android SDK](/platforms/android/), then add the `sentry-compose` dependency using Gradle: - -```groovy -implementation 'io.sentry:sentry-android:{{@inject packages.version('sentry.java.android', '6.2.0') }}' -implementation 'io.sentry:sentry-compose-android:{{@inject packages.version('sentry.java.compose.android', '6.2.0') }}' -``` - -#### Configure - -Configuration should happen in the respective `@Composable` function, once you obtain an instance of `NavHostController`: - -```kotlin -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.rememberNavController -import io.sentry.compose.withSentryObservableEffect - -val navController = rememberNavController().withSentryObservableEffect( - enableNavigationBreadcrumbs = true, // enabled by default - enableNavigationTracing = true // enabled by default -) -NavHost( - navController = navController -) { - ... -} -``` - -By default, the navigation transaction finishes automatically after it reaches the specified [idleTimeout](/platforms/android/configuration/options/#idleTimeout) and all of its child spans are finished. You can customize the timeout to your needs. - -### Verify - -This snippet includes a sample `Activity` with a couple of navigation events between composables and captures an intentional message, so you can test that everything is working as soon as you set it up: - -```kotlin -import android.os.Bundle -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import androidx.navigation.NavHostController -import androidx.navigation.NavType -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController -import io.sentry.Sentry -import io.sentry.compose.withSentryObservableEffect - -class ComposeActivity : ComponentActivity() { - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContent { - val navController = rememberNavController().withSentryObservableEffect() - SampleNavigation(navController) - } - } -} - -@Composable -fun SampleNavigation(navController: NavHostController) { - NavHost( - navController = navController, - startDestination = "first" - ) { - composable("first") { - First( - navigateToSecond = { navController.navigate("second") } - ) - } - composable("second") { - Second() - } - } -} - -@Composable -fun First( - navigateToSecond: () -> Unit -) { - Column( - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize() - ) { - Button( - onClick = { navigateToSecond() }, - modifier = Modifier.padding(top = 32.dp) - ) { - Text("Navigate to Second") - } - Button( - onClick = { Sentry.captureMessage("Some message from Compose.") }, - modifier = Modifier.padding(top = 32.dp) - ) { - Text("Message from Compose") - } - } -} - -@Composable -fun Second() { - Column( - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize() - ) { - Text("Second Screen") - } -} -``` - -## Customize the Recorded Breadcrumb/Transaction - -By default, the Navigation integration captures route arguments as additional data on breadcrumbs and transactions. In case the arguments contain any PII data, you can strip it out by way of `BeforeBreadcrumbCallback` and `EventProcessor` respectively. To do that, [manually initialize](/platforms/android/manual-setup/#configuration-via-sentryoptions) the SDK and add the following snippet: - -```kotlin -import io.sentry.EventProcessor -import io.sentry.android.core.SentryAndroid -import io.sentry.SentryOptions.BeforeBreadcrumbCallback -import io.sentry.android.navigation.SentryNavigationListener - -SentryAndroid.init(this) { options -> - options.beforeBreadcrumb = BeforeBreadcrumbCallback { breadcrumb, hint -> - if (SentryNavigationListener.NAVIGATION_OP == breadcrumb.category) { - breadcrumb.data.remove("from_arguments") - breadcrumb.data.remove("to_arguments") - } - breadcrumb - } - options.addEventProcessor(object : EventProcessor { - override fun process(transaction: SentryTransaction, hint: Hint): SentryTransaction? { - if (SentryNavigationListener.NAVIGATION_OP == transaction.contexts.trace.operation) { - transaction.removeExtra("arguments") - } - return transaction - } - }) -} -``` - ## Custom Telemetry in Compose Composable functions can run many times during recomposition. Don't send custom Sentry spans, messages, diff --git a/docs/platforms/android/integrations/navigation/index.mdx b/docs/platforms/android/integrations/navigation/index.mdx index 27bc2bbd49b0d..dca8d316ca349 100644 --- a/docs/platforms/android/integrations/navigation/index.mdx +++ b/docs/platforms/android/integrations/navigation/index.mdx @@ -10,24 +10,36 @@ categories: The `sentry-android-navigation` and `sentry-android-navigation3` libraries provide support for Sentry's Android navigation instrumentation. Use them to automatically add breadcrumbs, track screen names, and start navigation transactions whenever users navigate around your app. -This page covers both: +This page covers: -- **Navigation 2** (`androidx.navigation`) via `SentryNavigationListener` -- **Navigation 3** (`androidx.navigation3`) via `SentryNavEffect` +- **[Navigation 2](https://developer.android.com/guide/navigation/)** (`androidx.navigation`) for classic Views and Jetpack Compose via `SentryNavigationListener` +- **[Navigation 3](https://developer.android.com/guide/navigation/navigation-3)** (`androidx.navigation3`) for Jetpack Compose via `SentryNavEffect` -If you're still using `NavController`, follow the Navigation 2 setup below. If you've moved to `NavDisplay`, skip to [Navigation 3](#navigation-3). +If you're using `NavController`, follow the Navigation 2 setup below. If you're using `NavDisplay`, skip to [Navigation 3](#navigation-3). ## Navigation 2 - +`SentryNavigationListener` for Navigation 2 integrations lets you observe destination changes reported by your `NavController`. Upon every destination change the listener: -If you're using Navigation 2 with Jetpack Compose, see Jetpack Compose Navigation instead. +- starts a new navigation transaction and binds it to the current [Sentry scope](https://docs.sentry.io/platforms/android/enriching-events/scopes/)† +- emits a breadcrumb +- updates the currently tracked screen name - +`SentryNavigationListener` can be used with: -### Install +- [Classic Views](#classic-views) +- [Jetpack Compose](#jetpack-compose) + + + † A new navigation transaction will *not* be bound if another unfinished + transaction is already bound to the current scope. + -Sentry captures data by adding a `NavController.OnDestinationChangedListener`. To add the Navigation integration, install the [Android SDK](/platforms/android/), then add the `sentry-android-navigation` dependency using Gradle: +### Classic Views + +#### Install + +Install the [Android SDK](/platforms/android/), then add the `sentry-android-navigation` dependency using Gradle: ```groovy {filename:build.gradle} implementation 'io.sentry:sentry-android:{{@inject packages.version('sentry.java.android', '6.2.0') }}' @@ -39,9 +51,9 @@ implementation("io.sentry:sentry-android:{{@inject packages.version('sentry.java implementation("io.sentry:sentry-android-navigation:{{@inject packages.version('sentry.java.android.navigation', '6.2.0') }}") ``` -### Configure +#### Configure -Configuration should happen in the respective lifecycle callbacks, once you retrieve your `NavController` instance: +Registration of the `SentryNavigationListener` should happen in the appropriate lifecycle callbacks of the entity that owns your `NavController`: ```kotlin import androidx.navigation.NavController @@ -88,11 +100,9 @@ protected void onPause() { } ``` -By default, the navigation transaction finishes automatically after it reaches the specified [idleTimeout](/platforms/android/configuration/options/#idleTimeout) and all of its child spans are finished. You can customize the timeout to your needs. +#### Verify -### Verify - -The snippet below includes a sample `Fragment` with a couple of navigation events and captures an intentional message, so you can test that everything is working as soon as you set it up: +The snippet below includes a sample `Fragment` with example navigation events. It captures an intentional message, so you can test that everything is working as soon as you set it up: ```kotlin import android.os.Bundle @@ -139,14 +149,206 @@ class HomeFragment : Fragment() { } ``` -### Customize the Recorded Breadcrumb/Transaction +### Jetpack Compose + +#### Auto-Installation with the Sentry Android Gradle Plugin + +If you're using the [Sentry Android Gradle Plugin](/platforms/android/configuration/gradle/), it will automatically register `SentryNavigationListener` with any `NavHostController`s you create via [`rememberNavController`](https://developer.android.com/reference/kotlin/androidx/navigation/compose/rememberNavController.composable). + + + +Be sure not to call Sentry's `withSentryObservableEffect` method (discussed [below](#configure-1)) on a `NavHostController` that the Sentry Android Gradle Plugin already auto-instruments. Doing so can register multiple `SentryNavigationListener`s and produce duplicate navigation data. + + + +##### Install + +Add the Sentry Android Gradle plugin in your `build.gradle` file: + +```groovy +plugins { + id "io.sentry.android.gradle" version "{{@inject packages.version('sentry.java.android.gradle-plugin', '3.0.0') }}" +} +``` + +```kotlin +plugins { + id("io.sentry.android.gradle") version "{{@inject packages.version('sentry.java.android.gradle-plugin', '3.0.0') }}" +} +``` + + + +Learn more about the Sentry Android Gradle plugin in our [Gradle](/platforms/android/configuration/gradle/) documentation. Details about auto-installation can be found in the [Auto Instrumentation](/platforms/android/configuration/gradle/#tracing-auto-instrumentation) section. + + + +##### Disable + +If you want to disable the plugin's automatic Compose instrumentation, including Navigation 2 instrumentation, add the following to your `build.gradle` file: + +```groovy +import java.util.EnumSet +import io.sentry.android.gradle.extensions.InstrumentationFeature + +sentry { + tracingInstrumentation { + enabled = true + features = EnumSet.allOf(InstrumentationFeature) - InstrumentationFeature.COMPOSE + } +} +``` + +```kotlin +import java.util.EnumSet +import io.sentry.android.gradle.extensions.InstrumentationFeature + +sentry { + tracingInstrumentation { + enabled.set(true) + features.set(EnumSet.allOf(InstrumentationFeature::class.java) - InstrumentationFeature.COMPOSE) + } +} +``` + +#### Manual Installation + +##### Install + +Sentry captures data via a `NavHostController.withSentryObservableEffect` extension function that registers a `SentryNavigationListener` under the hood. To use it, install the Android SDK, then add the `sentry-compose-android` dependency in Gradle: + +```groovy +implementation 'io.sentry:sentry-android:{{@inject packages.version('sentry.java.android', '6.2.0') }}' +implementation 'io.sentry:sentry-compose-android:{{@inject packages.version('sentry.java.compose.android', '6.2.0') }}' +``` + +```kotlin +implementation("io.sentry:sentry-android:{{@inject packages.version('sentry.java.android', '6.2.0') }}") +implementation("io.sentry:sentry-compose-android:{{@inject packages.version('sentry.java.compose.android', '6.2.0') }}") +``` + +##### Configure + +Configuration should happen in the `@Composable` function that owns your `NavHost`: + +```kotlin +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.rememberNavController +import io.sentry.compose.withSentryObservableEffect + +val navController = rememberNavController().withSentryObservableEffect( + enableNavigationBreadcrumbs = true, // enabled by default + enableNavigationTracing = true // enabled by default +) + +NavHost( + navController = navController +) { + ... +} +``` + +#### Verify + +This snippet includes a sample `Activity` with example navigation events between composables. It captures an intentional message, so you can test that everything is working as soon as you set it up: + +```kotlin +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import io.sentry.Sentry +import io.sentry.compose.withSentryObservableEffect + +class ComposeActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + val navController = rememberNavController().withSentryObservableEffect() + SampleNavigation(navController) + } + } +} + +@Composable +fun SampleNavigation(navController: NavHostController) { + NavHost( + navController = navController, + startDestination = "first" + ) { + composable("first") { + First( + navigateToSecond = { navController.navigate("second") } + ) + } + composable("second") { + Second() + } + } +} + +@Composable +fun First( + navigateToSecond: () -> Unit +) { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize() + ) { + Button( + onClick = { navigateToSecond() }, + modifier = Modifier.padding(top = 32.dp) + ) { + Text("Navigate to Second") + } + Button( + onClick = { Sentry.captureMessage("Some message from Compose.") }, + modifier = Modifier.padding(top = 32.dp) + ) { + Text("Message from Compose") + } + } +} + +@Composable +fun Second() { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxSize() + ) { + Text("Second Screen") + } +} +``` + +### Scrub Sensitive Info from the Recorded Breadcrumb/Transaction + +`SentryNavigationListener` captures route arguments as additional data on breadcrumbs and transactions. If the arguments contain PII, you can strip them out with `BeforeBreadcrumbCallback` and `BeforeSendTransactionCallback`.† -By default, the Navigation integration captures route arguments as additional data on breadcrumbs and transactions. In case the arguments contain any PII data, you can strip it out by way of `BeforeBreadcrumbCallback` and `EventProcessor` respectively. To do that, [manually initialize](/platforms/android/manual-setup/#configuration-via-sentryoptions) the SDK and add the following snippet: +To do so, [manually initialize](/platforms/android/manual-setup/#configuration-via-sentryoptions) the SDK and add the following snippet: ```kotlin -import io.sentry.EventProcessor import io.sentry.android.core.SentryAndroid import io.sentry.SentryOptions.BeforeBreadcrumbCallback +import io.sentry.SentryOptions.BeforeSendTransactionCallback import io.sentry.android.navigation.SentryNavigationListener SentryAndroid.init(this) { options -> @@ -157,17 +359,24 @@ SentryAndroid.init(this) { options -> } breadcrumb } - options.addEventProcessor(object : EventProcessor { - override fun process(transaction: SentryTransaction, hint: Hint): SentryTransaction? { - if (SentryNavigationListener.NAVIGATION_OP == transaction.contexts.trace.operation) { - transaction.removeExtra("arguments") - } - return transaction + + options.beforeSendTransaction = BeforeSendTransactionCallback { transaction, _ -> + if (SentryNavigationListener.NAVIGATION_OP == transaction.contexts.trace?.operation) { + transaction.setData("arguments", null) } - }) + transaction + } } ``` + + † The snippet above removes route arguments, but it doesn't scrub route names. + Define routes with stable names and placeholders, such as `profile/{user_id}`, + and pass only sanitized values as arguments. Avoid putting PII directly in + route names, as they can appear in breadcrumb `from` and `to` fields, + transaction names, and screen names. + + ## Navigation 3 @@ -178,20 +387,23 @@ See the [Limitations](#limitations) section below for additional restrictions. -Android's [Navigation 3 library](https://developer.android.com/guide/navigation/navigation-3) expects apps to manage their own back stack and to push and pop entries in order to navigate. Apps pass their back stack to a `NavDisplay` and define corresponding composables. `NavDisplay` renders those composables to screen as the back stack entries change. - -`SentryNavEffect` instruments the back stack passed to `NavDisplay`. By default, every time the top of the back stack changes, `SentryNavEffect` will: +Android's [Navigation 3 library](https://developer.android.com/guide/navigation/navigation-3) expects apps to manage their own back stack and to push and pop entries in order to navigate. `SentryNavEffect` instruments the back stack passed to your `NavDisplay`. By default, every time the top of the back stack changes, `SentryNavEffect` will: -- start a new navigation transaction (and finish any prior navigation transaction) +- start a new navigation transaction and binds it to the current [Sentry scope](https://docs.sentry.io/platforms/android/enriching-events/scopes/)† - emit a breadcrumb - update the currently tracked screen name -- record the latest 10 entries from the back stack as event context +- record the latest 5 entries from the back stack as event context See [Customize Nav3 Options](#customize-nav3-options) to learn how to modify those defaults. + + † A new navigation transaction will *not* be started if another unfinished + transaction is already bound to the current scope. + + ### Install -Install the Android SDK and add Sentry's Navigation 3 library: +Install the Android SDK and add the `sentry-android-navigation3` library: ```groovy {filename:build.gradle} dependencies { @@ -221,11 +433,11 @@ import kotlinx.serialization.Serializable @Composable fun AppNavigation() { // Create your back stack like usual. - val backStack = rememberNavBackStack(Route.Home) + val backStack = rememberNavBackStack(Home) // Place SentryNavEffect in the same composable as your NavDisplay and call the // effect first. Provide a BackStackEntryMapper with the values you want to - // show up in Sentry. Be sure to omit PII and other sensitive info. + // show up in Sentry. Omit PII and other sensitive info. SentryNavEffect( backStack = backStack, backStackEntryMapper = { entry -> @@ -256,10 +468,9 @@ Values returned from `BackStackEntryMapper` are **not** scrubbed by the Sentry S - - Multiple, simultaneously active `SentryNavEffect` instances writing to the same [Sentry scope](https://docs.sentry.io/platforms/android/enriching-events/scopes/) are ***not*** supported. Violating this restriction can result in duplicated or interleaved data and undefined transaction behavior. +Multiple, simultaneously active `SentryNavEffect` instances writing to the same [Sentry scope](https://docs.sentry.io/platforms/android/enriching-events/scopes/) are **_not_** supported. Violating this restriction can result in duplicated or interleaved data and undefined transaction behavior. @@ -404,11 +615,11 @@ import kotlinx.serialization.Serializable @Serializable @SerialName("Home") -data class Home(userName: String) : NavKey +data class Home(val userName: String) : NavKey @Serializable @SerialName("ProductDetail") -data class ProductDetail(userName: String, productId: String) : NavKey +data class ProductDetail(val userName: String, val productId: String) : NavKey val backStackItemMapper = BackStackEntryMapper { entry -> when (entry) { @@ -424,9 +635,17 @@ val backStackItemMapper = BackStackEntryMapper { entry -> ### Limitations -`SentryNavEffect` treats the top entry in your back stack as the current screen. That works well for most Nav3 setups, but it means that `SentryNavEffect` does ***not***: +`SentryNavEffect` treats the top entry in your back stack as the current screen. That works well for most Nav3 setups, but it means that `SentryNavEffect` does **_not_**: - understand the concept of Nav3 [Scenes](https://developer.android.com/guide/navigation/navigation-3/scenes) or multi-pane layouts - track multiple back stacks - permit multiple, simultaneously active `SentryNavEffect` instances - make special accommodations for [predictive back](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture) gestures (e.g., spans produced by predictively rendered composables may appear under the current destination's transaction) + +## Navigation 2 and Navigation 3 Interoperability + +Navigation 2 and Navigation 3 can coexist in the same app. Their Sentry integrations observe navigation independently, but both write to the same Sentry scope by default. + +If both integrations observe the same navigation flow – for instance, a Nav2 destination hosts a Nav3 flow – Sentry can receive duplicate breadcrumbs or conflicting screen names and transactions. Choose one integration to report each flow, or configure the integrations so they don't report the same data to the same scope. + +If you're using the Sentry Android Gradle Plugin, be sure to account for automatically enabled Navigation 2 instrumentation when deciding which integration should report a flow. From 686aeab38e2333dc53fd0baadd1ce06d46c9d11f Mon Sep 17 00:00:00 2001 From: Adam Brown Date: Thu, 8 Oct 2026 11:14:04 +0200 Subject: [PATCH 2/2] Fix transaction scrubbing code block --- docs/platforms/android/integrations/navigation/index.mdx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/platforms/android/integrations/navigation/index.mdx b/docs/platforms/android/integrations/navigation/index.mdx index dca8d316ca349..d000dd0c1cba1 100644 --- a/docs/platforms/android/integrations/navigation/index.mdx +++ b/docs/platforms/android/integrations/navigation/index.mdx @@ -361,8 +361,9 @@ SentryAndroid.init(this) { options -> } options.beforeSendTransaction = BeforeSendTransactionCallback { transaction, _ -> - if (SentryNavigationListener.NAVIGATION_OP == transaction.contexts.trace?.operation) { - transaction.setData("arguments", null) + val trace = transaction.contexts.trace + if (SentryNavigationListener.NAVIGATION_OP == trace?.operation) { + trace.setData("arguments", null) } transaction }