# Jetpack Compose | Sentry for Android

The `sentry-compose-android` library provides [Jetpack Compose](https://developer.android.com/jetpack/androidx/releases/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](https://docs.sentry.io/platforms/android/enriching-events/viewhierarchy.md)

## [Performance Metrics](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#performance-metrics)

*(New in version 6.16.0)*

With Jetpack Compose performance metrics the Sentry Android SDK can automatically measure the initial `composition` and `rendering` time as performance spans of your `@Composable` UI elements.

This feature requires an underlying transaction to attach it's spans to. Transactions can be created manually or automatically by configuring

* [Activity instrumentation](https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation.md)
* [Navigation Instrumentation](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#jetpack-compose-navigation)
* [User Interactions](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#user-interactions)

Once performance metrics have been enabled, you'll see two spans inside your transactions:

* `ui.compose.composition`, describing the time it took to execute your `@Composable`
* `ui.compose.rendering`, describing the time it took to render your `@Composable` to the canvas

The `SentryTraced` function also provides an `enableUserInteractionTracing` argument, which automatically applies a `Modifier.sentryTag()` and enables the *User Interactions* feature mentioned below.

### [Installation](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#installation)

To add the Jetpack Compose integration, install the [Android SDK](https://docs.sentry.io/platforms/android.md), then add the `sentry-compose-android` dependency using Gradle:

```groovy
implementation 'io.sentry:sentry-android:8.27.0'
implementation 'io.sentry:sentry-compose-android:8.27.0'
```

Once installed, you need to wrap your `@Composable` body with the `SentryTraced` function.

```kotlin
import io.sentry.compose.SentryTraced

@Composable
fun LoginScreen() {
  SentryTraced("login_screen") {
    Column {
      // ...
      Button(
          onClick = { TODO() }) {
          Text(text = "Login")
      }
    }
  }
}
```

## [User Interactions](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#user-interactions)

*(New in version 6.10.0)*

The Sentry User Interactions feature for Jetpack Compose can detect click, scroll, and swipe gestures. For every gesture, the SDK can automatically collect breadcrumbs and launch transactions.

### [Installation](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#installation-1)

To add the Jetpack Compose integration, install the [Android SDK](https://docs.sentry.io/platforms/android.md), then add the `sentry-compose-android` dependency using Gradle:

```groovy
implementation 'io.sentry:sentry-android:8.27.0'
implementation 'io.sentry:sentry-compose-android:8.27.0'
```

### [Configuration](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#configuration)

This feature is disabled by default, but you can enable it the following ways.

#### [Using `AndroidManifest.xml`](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#using-androidmanifestxml)

`AndroidManifest.xml`

```xml
<application>
    <meta-data
    android:name="io.sentry.traces.user-interaction.enable"
    android:value="true"
  />
    <meta-data
    android:name="io.sentry.breadcrumbs.user-interaction"
    android:value="true"
  />
</application>
```

#### [Using `SentryOptions`](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#using-sentryoptions)

If you initialize the SDK [manually as mentioned here](https://docs.sentry.io/platforms/android/configuration/manual-init.md#manual-initialization), you can enable user interactions like this:

`MyApplication.kt`

```kotlin
SentryAndroid.init(this) { options ->
    // ...
    options.isEnableUserInteractionTracing = true
    options.isEnableUserInteractionBreadcrumbs = true
}
```

#### [Capture Composables](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#capture-composables)

The Sentry SDK requires you to have either `Modifier.sentryTag("<tag>")` or `Modifier.testTag("<tag>")` applied on each `@Composable` you want to capture to determine its identifier. Here's an example:

```kotlin
import io.sentry.compose.sentryTag

@Composable
fun LoginScreen() {
  Column {
    // ...
    Button(
        modifier = Modifier.sentryTag("button_login"),
        onClick = { TODO() }) {
        Text(text = "Login")
    }
  }
}
```

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](https://docs.sentry.io/platforms/android/enhance-errors/kotlin-compiler-plugin.md).

If you want to customize the recorded breadcrumbs/transactions, you can skip to [the section below](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#customize-the-recorded-breadcrumbtransaction).

## [Jetpack Compose Navigation](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#jetpack-compose-navigation)

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.

### [Auto-Installation With the Sentry Android Gradle Plugin](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#auto-installation-with-the-sentry-android-gradle-plugin)

#### [Install](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#install)

If you're using the [Sentry Android Gradle Plugin](https://docs.sentry.io/platforms/android/configuration/gradle.md), 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 "5.12.2"
}
```

Learn more about the Sentry Android Gradle plugin in our [Gradle](https://docs.sentry.io/platforms/android/configuration/gradle.md) documentation. Details about auto-installation can be found in the [Auto Instrumentation](https://docs.sentry.io/platforms/android/configuration/gradle.md#tracing-auto-instrumentation) section.

#### [Disable](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#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
  }
}
```

### [Manual Installation](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#manual-installation)

#### [Install](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#install-1)

Sentry captures data by adding a `withSentryObservableEffect` extension function to `NavHostController`. To add the Navigation integration, install the [Android SDK](https://docs.sentry.io/platforms/android.md), then add the `sentry-compose` dependency using Gradle:

```groovy
implementation 'io.sentry:sentry-android:8.27.0'
implementation 'io.sentry:sentry-compose-android:8.27.0'
```

#### [Configure](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#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](https://docs.sentry.io/platforms/android/configuration/options.md#idleTimeout) and all of its child spans are finished. You can customize the timeout to your needs.

### [Verify](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#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](https://docs.sentry.io/platforms/android/integrations/jetpack-compose.md#customize-the-recorded-breadcrumbtransaction)

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](https://docs.sentry.io/platforms/android/configuration/manual-init.md#manual-initialization) 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
    }
  })
}
```
