diff --git a/.craft.yml b/.craft.yml index bee668917c..68eb5fac67 100644 --- a/.craft.yml +++ b/.craft.yml @@ -64,6 +64,7 @@ targets: maven:io.sentry:sentry-quartz: maven:io.sentry:sentry-okhttp: maven:io.sentry:sentry-android-navigation: + maven:io.sentry:sentry-android-navigation3: maven:io.sentry:sentry-compose: maven:io.sentry:sentry-compose-android: maven:io.sentry:sentry-compose-desktop: diff --git a/CHANGELOG.md b/CHANGELOG.md index f5d6ef0031..0036c65807 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Features +- Add support for Android Navigation 3 through the new `sentry-android-navigation3` library ([#6233](https://github.com/getsentry/sentry-java/pull/6233)) + - Use `SentryNavEffect` to record navigation transactions, breadcrumbs, screen names, and additional context as your nav back stack changes. + - See the [Navigation for Android docs](https://docs.sentry.io/platforms/android/integrations/navigation/) for additional details. - Report the cellular network technology generation in `device.connection_effective_type`, for example `4g` or `5g` ([#6146](https://github.com/getsentry/sentry-java/pull/6146)) ### Dependencies diff --git a/README.md b/README.md index a827af6f98..f94eb9a9ae 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ Sentry SDK for Java and Android | sentry-android-timber | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-timber?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-android-timber) | 21 | | sentry-android-fragment | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-fragment?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-android-fragment) | 21 | | sentry-android-navigation | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-navigation?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-android-navigation) | 21 | +| sentry-android-navigation3 | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-navigation3?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-android-navigation3) | 23 | | sentry-android-sqlite | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-sqlite?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-android-sqlite) | 21 | | sentry-android-replay | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-android-replay?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-android-replay) | 26 | | sentry-compose-android | [![Maven Central Version](https://img.shields.io/maven-central/v/io.sentry/sentry-compose-android?style=for-the-badge&logo=sentry&color=green)](https://central.sonatype.com/artifact/io.sentry/sentry-compose-android) | 21 | diff --git a/sentry-android-navigation3/README.md b/sentry-android-navigation3/README.md new file mode 100644 index 0000000000..bdfec781fc --- /dev/null +++ b/sentry-android-navigation3/README.md @@ -0,0 +1,12 @@ +# sentry-android-navigation3 + +[Navigation 3](https://developer.android.com/guide/navigation/navigation-3) instrumentation for the Sentry Android SDK. + +Use `SentryNavEffect` alongside your `NavDisplay` to record navigation transactions, breadcrumbs, screen names, and back stack context as +users move around your app. Configure the data you want captured via `SentryNavOptions`. + +Because Nav3 back stacks can have arbitrary key types, host apps need to provide `SentryNavEffect` with a mapping between their back stack +keys and the data Sentry should display for each (see `BackStackEntryMapper`). + +See the [Navigation for Android docs](https://docs.sentry.io/platforms/android/integrations/navigation/) for additional details, including +installation, configuration, and limitations. diff --git a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackEntryMapper.kt b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackEntryMapper.kt index 2412afffe8..7455cfda5f 100644 --- a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackEntryMapper.kt +++ b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackEntryMapper.kt @@ -5,7 +5,6 @@ import org.jetbrains.annotations.ApiStatus /** Info about a given back stack entry, suitable for display in Sentry. */ @ApiStatus.Experimental -@ApiStatus.Internal public class SentryBackStackEntry( /** * A host-app defined name for a given back stack entry. @@ -104,13 +103,15 @@ public class SentryBackStackEntry( * - [Map]s * - [Collection]s * - * Containers may be nested, but they must bottom out in supported scalar types. Cyclic or deeply - * nested containers will be skipped. + * Containers may be nested, but they must bottom out in supported scalar types. * * **Arguments fall back to `toString()` or nothing** * - * All non-supported argument types are stringified via `toString()`. If [map] throws or returns - * `null`, no arguments are recorded for that back stack entry. + * All non-supported argument types are stringified via `toString()`. + * + * No arguments will be recorded for a back stack entry if [map] throws or returns `null`, or if its + * arguments are cyclic, too deeply nested, or too large. If the SDK drops an entry's arguments, it + * adds a `dropped_by_sentry` field and corresponding reason in the Sentry UI. * * **Using kotlinx.serialization** * @@ -123,11 +124,11 @@ public class SentryBackStackEntry( * ```kotlin * @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, tab: Tab) : NavKey + * data class ProductDetail(val userName: String, val productId: String) : NavKey * * ... * @@ -138,7 +139,7 @@ public class SentryBackStackEntry( * name = ProductDetail.serializer().descriptor.serialName, * // Select a subset of diagnostic arguments when serialization is unsafe * // or non-performant. - * arguments = mapOf("product_id" to entry.productId, "tab" to entry.tab) + * arguments = mapOf("product_id" to entry.productId) * ) * ... * } @@ -146,7 +147,6 @@ public class SentryBackStackEntry( * ``` */ @ApiStatus.Experimental -@ApiStatus.Internal public fun interface BackStackEntryMapper { public fun map(backStackEntry: T): SentryBackStackEntry? } diff --git a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackObserver.kt b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackObserver.kt index 9b687d0b25..37f694350f 100644 --- a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackObserver.kt +++ b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/BackStackObserver.kt @@ -273,7 +273,7 @@ private data class BackStackData( val topEntryNormalized: NormalizedSentryBackStackEntry, /** * [NormalizedSentryBackStackEntry]s representing the newest - * [SentryNavOption.maxCapturedBackStackEntries] entries from the host app's back stack. + * [SentryNavOptions.maxCapturedBackStackEntries] entries from the host app's back stack. * * Possibly empty. */ diff --git a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavEffect.kt b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavEffect.kt index d041a03b68..8b6269752b 100644 --- a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavEffect.kt +++ b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavEffect.kt @@ -44,10 +44,11 @@ import org.jetbrains.annotations.ApiStatus * By default, the following data is produced every time the top of the provided [backStack] * changes: * - * - a new navigation transaction + * - a new navigation transaction (unless another transaction is already active on the current + * scope) * - a breadcrumb * - a screen name - * - a record of the current back stack (last 10 entries) + * - a record of the current back stack (last 5 entries) * * You can configure the above defaults via [SentryNavOptions]. (Screen names can be disabled via * [SentryOptions.setEnableScreenTracking].) @@ -75,7 +76,6 @@ import org.jetbrains.annotations.ApiStatus * @param options The kinds of navigation info this effect should record. */ @ApiStatus.Experimental -@ApiStatus.Internal @Composable public fun SentryNavEffect( backStack: List, diff --git a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavOptions.kt b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavOptions.kt index f463567f5e..9a571d47de 100644 --- a/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavOptions.kt +++ b/sentry-android-navigation3/src/main/kotlin/io/sentry/compose/navigation3/SentryNavOptions.kt @@ -19,7 +19,6 @@ private const val DEFAULT_MAX_CAPTURED_BACK_STACK_ENTRIES = 5 * ``` */ @ApiStatus.Experimental -@ApiStatus.Internal @Immutable public class SentryNavOptions private constructor( @@ -116,6 +115,5 @@ private constructor( * ``` */ @ApiStatus.Experimental -@ApiStatus.Internal public fun SentryNavOptions(configure: SentryNavOptions.Builder.() -> Unit = {}): SentryNavOptions = SentryNavOptions.Builder().apply(configure).build() diff --git a/sentry-android-navigation3/src/test/kotlin/io/sentry/compose/navigation3/SentryNavEffectTest.kt b/sentry-android-navigation3/src/test/kotlin/io/sentry/compose/navigation3/SentryNavEffectTest.kt index 93c1cde567..7b8a4b9946 100644 --- a/sentry-android-navigation3/src/test/kotlin/io/sentry/compose/navigation3/SentryNavEffectTest.kt +++ b/sentry-android-navigation3/src/test/kotlin/io/sentry/compose/navigation3/SentryNavEffectTest.kt @@ -3,7 +3,7 @@ package io.sentry.compose.navigation3 import android.app.Application import android.content.ComponentName import androidx.activity.ComponentActivity -import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -317,9 +317,10 @@ class SentryNavEffectTest { ) val currentTop = backStack.last() - LaunchedEffect(currentTop) { + DisposableEffect(currentTop) { val transaction = fixture.scopes.getSpan() as? SentryTracer observedTransactionNames += transaction?.name ?: "" + onDispose {} } } composeRule.waitForIdle()