Sentry’s out-of-the-box integration with Jetpack Compose allows you to quickly identify and resolve errors and performance issues in your Android applications. Here’s how to get started. Credit: Jenu Prasad / Google Jetpack Compose is Android’s recommended toolkit for building native UIs, representing the platform’s demonstrative shift from imperative to declarative UIs. Google is making a big push to drive adoption, and it’s paying off. As announced at the Android Dev Summit ’22 last October, 160 of the top 1,000 apps on the Google Play store are shipping Jetpack Compose, including companies like Airbnb, Lyft, and Square. Jetpack Compose offers many benefits—it’s more intuitive, requires less code, and accelerates development. But it’s not without its challenges. Moving from an imperative toolkit to Jetpack Compose comes with a learning curve, which is exacerbated by limited documentation, a smaller community, and performance issues. Sentry recently announced their support of Jetpack Compose, with an out-of-the-box integration that allows developers to quickly identify and solve issues in their application. Here’s exactly how Sentry helps teams get started with Jetpack Compose. Start with Android Studio If you are building a new application from scratch with Jetpack Compose, first download and install Android Studio, an integrated development environment (IDE) optimized for Android apps. Then, create a new project and select either the Empty Compose Activity, which uses Material v2, or Empty Compose Activity (Material3), which uses Material v3. You can see both options in the top right of this screenshot: Sentry If you’d like to integrate Jetpack Compose into an existing Android application, add the following build configurations in your app’s build.gradle file. android { buildFeatures { // this flag enables Jetpack Compose compose true } composeOptions { // the compiler version should match // your project's Kotlin version kotlinCompilerExtensionVersion = "1.3.2" } } Then, add the Compose BOM (Bill of Materials) and the subset of Compose dependencies to your dependencies. dependencies { def composeBom = platform('androidx.compose:compose-bom:2023.01.00') implementation composeBom androidTestImplementation composeBom // Choose one of the following: // Material Design 3 implementation 'androidx.compose.material3:material3' // or Material Design 2 implementation 'androidx.compose.material:material' // or skip Material Design and build directly on top of foundational components implementation 'androidx.compose.foundation:foundation' // or only import the main APIs for the underlying toolkit systems, // such as input and measurement/layout implementation 'androidx.compose.ui:ui' // Android Studio Preview support implementation 'androidx.compose.ui:ui-tooling-preview' debugImplementation 'androidx.compose.ui:ui-tooling' // UI Tests androidTestImplementation 'androidx.compose.ui:ui-test-junit4' debugImplementation 'androidx.compose.ui:ui-test-manifest' // Optional - Included automatically by material, only add when you need // the icons but not the material library (e.g. when using Material3 or a // custom design system based on Foundation) implementation 'androidx.compose.material:material-icons-core' // Optional - Add full set of material icons implementation 'androidx.compose.material:material-icons-extended' // Optional - Add window size utils implementation 'androidx.compose.material3:material3-window-size-class' // Optional - Integration with activities implementation 'androidx.activity:activity-compose:1.5.1' // Optional - Integration with ViewModels implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1' // Optional - Integration with LiveData implementation 'androidx.compose.runtime:runtime-livedata' // Optional - Integration with RxJava implementation 'androidx.compose.runtime:runtime-rxjava2' } Integrate Sentry To integrate Sentry into your new Jetpack Compose app, all you need to do is add Sentry’s Gradle plugin in your module’s build.gradle file and perform a Gradle sync afterwards. buildscript { repositories { mavenCentral() } } plugins { id "com.android.application" id "io.sentry.android.gradle" version "3.4.2" } And then add the necessary values in the AndroidManifest.xml file. <application> <!-- Required: set your sentry.io project identifier (DSN) --> <meta-data android:name="io.sentry.dsn" android:value="https://examplePublicKey@o0.ingest.sentry.io/0" /> <!-- enable automatic breadcrumbs for user interactions (clicks, swipes, scrolls) --> <meta-data android:name="io.sentry.traces.user-interaction.enable" android:value="true" /> <!-- enable screenshot for crashes --> <meta-data android:name="io.sentry.attach-screenshot" android:value="true" /> <!-- enable view hierarchy for crashes --> <meta-data android:name="io.sentry.attach-view-hierarchy" android:value="true" /> <!-- enable the performance API by setting a sample-rate, adjust in production env --> <meta-data android:name="io.sentry.traces.sample-rate" android:value="1.0" /> <!-- enable profiling when starting transactions, adjust in production env --> <meta-data android:name="io.sentry.traces.profiling.sample-rate" android:value="1.0" /> </application> These two steps install and configure Sentry into your project. Aside from error reporting, your project now also has automatically instrumented performance monitoring. The Sentry SDK will automatically collect and analyze performance profiles so you can see how your application performs on different user devices in production. Capturing errors By default, Sentry captures all errors and crashes automatically for you. If you want to capture errors and exceptions manually, you can use the captureException method. import io.sentry.Sentry try { aMethodThatMightFail() } catch (e: Exception) { Sentry.captureException(e) } Adding context You have the option to add additional context to all of the errors that happen within your app. That’s arbitrary data that automatically gets attached to the event, and is viewable on the issue details page. To do that, we can attach custom contexts on the current scope like this: import io.sentry.Sentry Sentry.configureScope { scope -> scope.setContexts("Hero Details", mapOf( "Name" to "Mighty Fighter", "Age" to 19, "Attack type" to "Melee", )) } This data will now be appended to each issue. We can check it out at the issue details page: Sentry Important: There are two limitations you should be aware of. Namely: This data is not searchable. It’s only used to attach values to the events. If you need to be able to search on custom data, use tags instead. There are size limitations on how much data you can add to the scope. Sentry does not recommend sending the entire application state and large data blobs in contexts. In the event of appending too much data, Sentry will respond with the HTTP error “413 Payload Too Large” and reject the event. Adding tags Just like the additional context, we can also add custom tags on your events, which by contrast are indexed and searchable. You can use tags to quickly access related events and view the tag distribution for a set of events. Common uses for tags include hostname, platform version, and user language. Adding tags is very similar to adding additional contexts. They’re key-value pairs and they can be added to the current scope by using the setTag method. import io.sentry.Sentry Sentry.configureScope { scope -> scope.setTag(“user-type”, “premium”) } Sentry As mentioned, tags are indexed and searchable, so if you add “user-type:premium” in the Custom Search field in the Issues page you’ll see all of the issues that have that tag: Sentry Three things to be aware of when working with tags: Sentry automatically adds some tags to every issue. It is not a good idea to overwrite those tags. Instead, name your tags using your organization’s nomenclature. The keys have a maximum length of 32 characters and they can contain only letters, numbers, underscores, periods, colons, and dashes. The values have a maximum length of 200 characters and they cannot contain the newline (n) character. Adding attachments Adding attachments is yet another way to supplement the events with additional data, and it’s the recommended way if you need to add larger data than contexts and tags. Attachments can be any type of file. To add an attachment, you can either add it to the scope, pass it to any of the capture methods, or manipulate the list of attachments in an EventProcessor or beforeSend. Some rules you need to be aware of when working with attachments: Attachments are kept for 30 days. If your total storage quota is exceeded, attachments won’t be stored. You can delete attachments at any time, but that won’t affect your quota. Sentry counts an attachment towards your quota as soon as it is stored. You can manage access to the attachments based on the user role. Navigate to your organization’s General Settings, then select the Attachment Access dropdown to set appropriate access. By default, access is granted to all members when storage is enabled. The maximum size for each attachment is set on options.maxAttachmentSize in the init method. The scale is in bytes and the default is 20 MiB. You can change the size like so: Sentry.init { options -> options.maxAttachmentSize = 5 * 1024 * 1024 // 5 MiB } Passing attachments to capture methods This is probably the simplest way to add attachments. Whenever you’re using one of the capture methods, you can append the attachment as the second argument by using the Hint.withAttachment method. import io.sentry.Attachment import io.sentry.Hint import io.sentry.Sentry … try { … } catch (e: Exception) { Sentry.captureException(e, Hint.withAttachment(“/path/to/file.txt”)) } Adding attachments in beforeSend Another way of adding attachments is using the beforeSend callback. import io.sentry.Sentry import io.sentry.SentryOptions.BeforeSendCallback import io.sentry.Hint import io.sentry.Attachment Sentry.init(this) { options -> options.beforeSend = BeforeSendCallback { event, hint -> hint.addAttachment(Attachment(“/path/to/file.txt”)) } } This configuration will add the file.txt file to every issue before sending it to the cloud. Viewing attachments You can see the attachments for a given issue at the bottom of the issue details page. There’s an Attachments section that lists all attachments and you have the option to delete, download, or preview them. Sentry You can also access attachments through the Attachments tab on the same page, where you can view the type of attachment, as well as associated events. You can click the Event ID to open the Issue Details of that specific event. Sentry Measuring performance If you’ve provided the Sample Rate value (io.sentry.traces.sample-rate) in your AndroidManifest.xml file, then you’ve already configured Sentry to automatically instrument your application. Sentry will automatically capture transactions for lifecycle events of activities and fragments, cold and warm app start, slow and frozen frames, and other events. It’s also possible to manually instrument a specific function, for example a function that spends some time reshaping a large chunk of data, or a function that obtains data from an API and puts it in the local storage, etc. In order to create custom instrumentations, you’d need to start a transaction by calling the startTransaction method. import io.sentry.Sentry import io.sentry.SpanStatus val transaction = Sentry.startTransaction(“processOrderBatch()”, “task”) try { processOrderBatch() } catch (e: Exception) { transaction.throwable = e transaction.status = SpanStatus.INTERNAL_ERROR throw e } finally { transaction.finish() } Warning: Don’t forget to call the finish() method on the transaction, otherwise the transaction won’t be sent to Sentry. If the function you’re trying to instrument is more complex and involves multiple sub-functions that you’d prefer to instrument individually, you can create child spans for each of them and attach them to the main transaction. import io.sentry.SpanStatus val transaction = Sentry.startTransaction("processOrderBatch()", "task") try { processOrderBatch(transaction) } catch (e: Exception) { transaction.throwable = e transaction.status = SpanStatus.INTERNAL_ERROR throw e } finally { transaction.finish() } fun processOrderBatch(span: ISpan) { // span operation: task, span description: operation val innerSpan = it.startChild("task", "operation") try { // omitted code } catch (e: FileNotFoundException) { innerSpan.throwable = e innerSpan.status = SpanStatus.NOT_FOUND throw e } finally { innerSpan.finish() } } Don’t forget to call the finish() method on each of the spans before calling the main transaction’s finish() method! If you don’t, they won’t be attached to the main transaction. Flying with Jetpack Compose In conclusion, while Jetpack Compose offers many benefits for building native Android UI, it comes with a steep learning curve and some performance issues. Sentry can help developers get started with Jetpack Compose. As outlined in this article, developers can easily integrate Sentry into their Jetpack Compose applications to capture errors and exceptions, add custom context and tags, get alerted on File I/O issues on the main thread, and utilize performance monitoring. With Sentry, teams can ensure that their Jetpack Compose apps are performing optimally and delivering a great user experience. If you’d like to learn more about what Sentry can do for your application, feel free to visit the documentation, or check out our Sandbox, which is a fully functional version of Sentry that you can play with. Lazar Nikolov is developer relations advocate at Sentry. — New Tech Forum provides a venue to explore and discuss emerging enterprise technology in unprecedented depth and breadth. The selection is subjective, based on our pick of the technologies we believe to be important and of greatest interest to InfoWorld readers. InfoWorld does not accept marketing collateral for publication and reserves the right to edit all contributed content. Send all inquiries to newtechforum@infoworld.com. Related content opinion The dirty little secret of open source contributions It isn’t the person making the contributions—it’s how easy the contributions make it to use the software. By Matt Asay Nov 18, 2024 4 mins Technology Industry Open Source opinion Breaking down digital silos Want your digital initiatives to succeed? Put the business and tech teams in the same sandbox and let them work together. By Matt Asay Nov 11, 2024 4 mins Chief Digital Officer CIO Technology Industry opinion The cloud reaches its equilibrium point Workloads in the cloud and workloads on premises are both reasonable choices. Enterprises are about evenly split between the two. By Matt Asay Nov 04, 2024 5 mins Technology Industry Cloud Architecture Cloud Computing analysis Overlooked cloud sustainability issues Unsustainable demands for AI are on the horizon. People will soon have to stop fake-caring about cloud sustainability and give a damn for real. By David Linthicum Nov 01, 2024 5 mins Technology Industry Cloud Architecture Cloud Computing Resources Videos