<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Sentry</title>
    <description>The latest articles on Forem by Sentry (@sentry).</description>
    <link>https://forem.com/sentry</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F3774%2F02d4162c-978f-4471-9d39-b2928cfb9e24.png</url>
      <title>Forem: Sentry</title>
      <link>https://forem.com/sentry</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/sentry"/>
    <language>en</language>
    <item>
      <title>App store rankings hate slow apps - mobile vitals can help you fix them</title>
      <dc:creator>pry0rity</dc:creator>
      <pubDate>Tue, 04 Mar 2025 20:27:42 +0000</pubDate>
      <link>https://forem.com/sentry/app-store-rankings-hate-slow-apps-mobile-vitals-can-help-you-fix-them-321p</link>
      <guid>https://forem.com/sentry/app-store-rankings-hate-slow-apps-mobile-vitals-can-help-you-fix-them-321p</guid>
      <description>&lt;p&gt;Mobile devs know the struggle. Small regressions can cause big issues in production, and fixing them isn't as easy as pushing a quick patch. Unlike a web app, shipping fixes for apps means navigating app store approvals, and often hopping on meetings with customers to debug because mobile issues can be so challenging to recreate.&lt;/p&gt;

&lt;p&gt;Catching these issues before the 1-star reviews roll in is crucial. Luckily, Sentry just made it easier than ever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mobile Vitals&lt;/strong&gt; - our latest addition to the Insights feature - highlights your slowest app starts, screen loads, and renders, so you can quickly tackle frozen or laggy interactions before users rage-quit. Whether you're building with React Native, Flutter, Android, or iOS, let's dive into what you can do with it.&lt;/p&gt;

&lt;p&gt;(P.S. We first wrote about mobile vitals years ago—if you're curious about how this new tool works under the hood, it's a &lt;a href="https://blog.sentry.io/mobile-vitals-four-metrics-every-mobile-developer-should-care-about/" rel="noopener noreferrer"&gt;good read&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  What does Mobile Vitals track?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1: App Start Performance
&lt;/h3&gt;

&lt;p&gt;You’re doom-scrolling Instagram recipes and suddenly a great ad for a calorie counting app pops up. You download it out of curiosity, and for some reason, it takes 20 seconds to boot up the first time, and 10 seconds every time after that. What are you gonna do - keep forcing yourself to go through that every time you pop another Oreo, or just go back to the scale? &lt;/p&gt;

&lt;p&gt;Sentry tracks App Start performance to help you prevent your users from burning unnecessary calories uninstalling your apps out of frustration. We consider two key metrics here: cold starts &amp;amp; warm starts. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cold starts: You never get a second chance at a first impression. Sentry tracks cold starts independently so you can make sure your latest patch doesn’t crush your first impressions.&lt;/li&gt;
&lt;li&gt;Warm starts: Memory management on iOS &amp;amp; Android is complicated and often suspends some tasks while keeping your application ‘warm’ in the background. Pulling up your application from the recent apps selector should be lightning-fast, too. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s not just the top-level metric that matters. For warm starts, Sentry auto-instruments &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing" rel="noopener noreferrer"&gt;iOS&lt;/a&gt; and &lt;a href="https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/#app-start-instrumentation" rel="noopener noreferrer"&gt;Android&lt;/a&gt; apps with great granular detail. With iOS for instance, you can see pre-runtime, UIKit, frame renders and a bunch of &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/#app-start-tracing" rel="noopener noreferrer"&gt;other native tasks&lt;/a&gt; independently and work to fix what’s bottlenecking your app load performance. &lt;/p&gt;

&lt;p&gt;Clicking into a screen shows you an overview for all your most recent app loads leading up to that screen, which you can then drill into and see what sorts of tasks may be bottlenecking.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftthpdq7oqzxcow7o8p16.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftthpdq7oqzxcow7o8p16.png" alt="A Sentry UI screenshot showing App Cold Starts and App Warm Starts highlighted with " width="777" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2: Screen Load Performance
&lt;/h3&gt;

&lt;p&gt;If it takes 5 seconds every time you want to load the comments page, nobody’s going to leave comments. It’s really that simple. &lt;/p&gt;

&lt;p&gt;Sentry helps you cut down on UX bloat by tracking &lt;a href="https://docs.sentry.io/product/insights/mobile/screen-loads/" rel="noopener noreferrer"&gt;TTID and TTFD&lt;/a&gt; for every screen load:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Time to Initial Display (TTID):&lt;/strong&gt; This tracks the time it takes for a screen to produce its first frame. Often, that’s just a background or a wall of text, but for a user, this is an important metric as it makes the app ‘feel’ fast. Sentry tracks this automatically by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time to Full Display (TTFD):&lt;/strong&gt; More importantly is the time it takes for a screen to be fully loaded and functional. This is similar to the LCP core web vital on browser loads. TTFD can include content that’s lazy- or async-loaded after getting its first content, making it a more useful metric for a lot of your mobile screens. This isn’t enabled by default, but it’s super easy to set up. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Just like with App Starts, you can drill into Screen Loads by clicking into a screen and drilling into the full trace of the user session, or even dive into a mobile CPU profile. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.arcade.software/share/tYVh6rDXcj9UQNOESjOY" rel="noopener noreferrer"&gt;Check out a 10-second interactive video here. &lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3: Screen Rendering Performance
&lt;/h3&gt;

&lt;p&gt;Your favorite solitaire clone ships a new patch, and suddenly you find your framerate dropping like a brick. You just got your phone a few years ago… is it time to upgrade? Nah, you’ll just throw out a 1-star review, uninstall, and go right back to Wordle.&lt;/p&gt;

&lt;p&gt;We surface 3 key &lt;a href="https://docs.sentry.io/product/insights/mobile/#app-start" rel="noopener noreferrer"&gt;screen rendering performance metrics&lt;/a&gt; by default: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Slow frames:&lt;/strong&gt; any frame that takes longer than a framecycle to render (16.7ms, or 1/60th of a second at 60hz) is considered ‘slow’, causing laggy UI responses and jittery animations. Sentry calculates the % of slow frames for each screen, so you can quickly find the conditions that lead to laggy or dropped frames. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frozen frames:&lt;/strong&gt; Even worse is a frame that’s stuck for a longer period of time. . Sentry considered any frame render that takes &amp;gt;700ms ‘frozen’. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame delay:&lt;/strong&gt; This is the total ‘hang’ time accumulated on a screen due to slow &amp;amp; frozen frames. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In our own testing, we found these 3 metrics super useful for debugging the clunkiest-feeling parts of our mobile application. By combining these with mobile session replays, we were able to quickly recreate conditions that led to clunkiness for our users and get down to the brass tax. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://demo.arcade.software/O1ly5WbccGBhqKMS2fxq" rel="noopener noreferrer"&gt;Check out a 10-second interactive video here.&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why does this matter?
&lt;/h2&gt;

&lt;p&gt;We’re not narcissistic enough to claim that this is as impactful as Core Web Vitals was for the internet - but it’s a start. For mobile apps, it’s extremely difficult to know what to track, let alone how to track it. So, our mobile SDK team took the step to create an awesome new way to get your mobile application performance on track by monitoring a few vital metrics simply &amp;amp; scalably. &lt;/p&gt;

&lt;p&gt;More than just monitoring, Mobile Vitals gives you the why behind slow screens—not just the when. Every insight is backed by a trace, so you can see if the culprit is slow code on your main thread or a deeper issue like sluggish database queries or too complex layouts. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frluzzs943tt5w7fpy3dv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frluzzs943tt5w7fpy3dv.png" alt=" " width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A few of our own, Lazar &amp;amp; Salma, recorded an awesome workshop about how to use Distributed Tracing to debug frontend issues with backend solutions. If you have slow screen loads on your mobile devices but aren’t able to find a root cause on the device itself, this is worth a watch. &lt;/p&gt;

&lt;p&gt;In short, Mobile Vitals is a tool to: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitor mobile top level performance metrics&lt;/li&gt;
&lt;li&gt;Easily identify key areas to improve&lt;/li&gt;
&lt;li&gt;Deep dive from a top level metric, to a specific performance issue on a particular screen, to a concrete &lt;a href="https://docs.sentry.io/platforms/apple/tracing/trace-propagation/" rel="noopener noreferrer"&gt;distributed trace&lt;/a&gt;, allowing you to debug and understand an issue across the full-stack&lt;/li&gt;
&lt;li&gt;Connect your metrics with &lt;a href="https://docs.sentry.io/product/explore/session-replay/mobile/" rel="noopener noreferrer"&gt;session replay&lt;/a&gt; and &lt;a href="https://docs.sentry.io/product/explore/profiling/" rel="noopener noreferrer"&gt;profiling&lt;/a&gt;, giving you full insights on what the user was experiencing and which code was running&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What numbers do I need to shoot for?
&lt;/h2&gt;

&lt;p&gt;Goodhart’s law - &lt;em&gt;when a metric becomes a target, it ceases to be a good metric&lt;/em&gt; - still rings true. That said, Google and Apple both openly state that their app store algorithms prioritize app quality, user engagement, app uninstalls and other key factors that are directly related to app performance. &lt;a href="https://x.com/LongTimeHistory/status/1892600734488617138" rel="noopener noreferrer"&gt;Google&lt;/a&gt; in particular recommends cold starts of &amp;lt; 5s, and warm starts of &amp;lt; 2s, but these are just starting points. &lt;/p&gt;

&lt;p&gt;Rather than just setting a target, mobile vitals monitoring is useful for objectively assessing the current state of your mobile app, deciding whether you want to try and improve it, and continuously evaluating your vitals metrics to make sure you’re not accidentally shipping a major slowdown. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsqifc1um87h7kvdt386.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdsqifc1um87h7kvdt386.png" alt=" " width="295" height="321"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Credit: &lt;a href="https://xkcd.com/2899/" rel="noopener noreferrer"&gt;xkcd&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get started monitoring my mobile vitals?
&lt;/h3&gt;

&lt;p&gt;Most of the Mobile Vitals metrics are instrumented automatically, so as long as you’ve set up the Sentry SDK in your mobile app, you should be good to go! TTFD is the only exception - you’ll need to call the API to let Sentry know when a screen is fully rendered. Here’s the setup for our most popular SDK’s:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;iOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/android/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Android&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/react-native/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;React Native&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.sentry.io/platforms/flutter/tracing/instrumentation/automatic-instrumentation/" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to go the extra mile, you can also track custom &lt;a href="https://docs.sentry.io/platforms/apple/tracing/instrumentation/performance-metrics/" rel="noopener noreferrer"&gt;performance metrics&lt;/a&gt;, add &lt;a href="https://docs.sentry.io/platforms/apple/tracing/instrumentation/custom-instrumentation/" rel="noopener noreferrer"&gt;custom spans&lt;/a&gt;, and attach span attributes that you can use to calculate and monitor &lt;a href="https://blog.sentry.io/find-and-fix-performance-bottlenecks-with-sentrys-trace-explorer/" rel="noopener noreferrer"&gt;span metrics&lt;/a&gt; for your most critical user experiences. &lt;/p&gt;

&lt;p&gt;If you’re a mobile developer who cares about your user experiences, tracking Mobile Vitals is a great way to gauge how your mobile app releases impact device performance beyond just the top level metrics. Mobile Vitals is available to all paying Sentry customers - just turn it on in your SDK and give it a shot.&lt;/p&gt;

&lt;p&gt;Questions? Join our passionate community of mobile devs in the &lt;a href="https://discord.com/invite/sentry" rel="noopener noreferrer"&gt;Sentry Discord&lt;/a&gt;. &lt;/p&gt;

</description>
    </item>
    <item>
      <title>Sentry can’t fix React hydration errors, but it can really help you debug them</title>
      <dc:creator>Salma Alam-Naylor</dc:creator>
      <pubDate>Thu, 26 Sep 2024 09:02:11 +0000</pubDate>
      <link>https://forem.com/sentry/sentry-cant-fix-react-hydration-errors-but-it-can-really-help-you-debug-them-40aa</link>
      <guid>https://forem.com/sentry/sentry-cant-fix-react-hydration-errors-but-it-can-really-help-you-debug-them-40aa</guid>
      <description>&lt;p&gt;&lt;em&gt;Hydration failed because the initial ui does not match what was rendered on the server.&lt;/em&gt; Don’t you just &lt;del&gt;love&lt;/del&gt; hate it when that happens?&lt;/p&gt;

&lt;p&gt;If you’re building server-rendered pages with Next.js or any React-based meta-framework, you know hydration errors suck and are usually difficult to debug. Hydration in React is the process that happens when a React application that was rendered as HTML on the server is made interactive in the browser. Hydration errors happen when the markup rendered by React on the client doesn’t match the initial server-rendered HTML, or when invalid HTML was sent by the server, and React couldn’t fix it. Sometimes this is unavoidable, for example, with dates and localization. You can suppress errors for these unavoidable differences in your React code, but most hydration errors would indicate that you’ve got some bugs in your app.&lt;/p&gt;

&lt;p&gt;This article is not about &lt;a href="https://sentry.io/answers/hydration-error-nextjs/" rel="noopener noreferrer"&gt;how to fix hydration errors&lt;/a&gt; but how to debug them using &lt;a href="https://sentry.io/welcome/" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt;. When hydration errors happen in development, your JavaScript framework of choice will usually show you a large error message with some details about the code that triggered the error. However, &lt;strong&gt;hydration errors aren’t usually visible or obvious in production&lt;/strong&gt;, and your average real-world users probably won’t be able to provide useful screenshots with error reports (and probably won’t think to look in the browser console for errors). What’s more, the &lt;em&gt;automatic&lt;/em&gt; error issue created by Sentry won’t be very useful, either.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0frv6m0v5zhtt2k3nqd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg0frv6m0v5zhtt2k3nqd.png" alt="Error issue in Sentry created by a hydration issue. The error message is not useful and there isn’t much context associated with the issue." width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  But wait, Session Replay has a superpower
&lt;/h2&gt;

&lt;p&gt;If you’re using Sentry’s &lt;a href="https://docs.sentry.io/product/explore/session-replay/" rel="noopener noreferrer"&gt;Session Replay&lt;/a&gt; to get reproductions of user sessions when a user triggers a hydration error:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sentry will automatically create a specific &lt;a href="https://docs.sentry.io/platforms/javascript/session-replay/issue-types/#configuring-hydration-errors" rel="noopener noreferrer"&gt;Hydration Error Issue&lt;/a&gt; for you (for free!),&lt;/li&gt;
&lt;li&gt;group all the same errors together,&lt;/li&gt;
&lt;li&gt;capture both the server-rendered and client-rendered markup that existed when the error happened,&lt;/li&gt;
&lt;li&gt;and show you the diffs in both visual and raw HTML modes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Just make sure you’re using Sentry’s JavaScript SDK, version 7.87.0 or above, and you’ve got Session Replay enabled to get the good stuff.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here’s what a grouped Hydration Error looks like in Sentry:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It is named “Hydration Error,” so you can find it easily in the issue list view.&lt;/li&gt;
&lt;li&gt;Choose which event to view (recommended, latest, oldest) and navigate between events to compare contextual information.&lt;/li&gt;
&lt;li&gt;View a sliding diff between the server and the client (open the diff viewer to also view a side by side visual diff and HTML diff).&lt;/li&gt;
&lt;li&gt;Click the “Resolving Hydration Errors” link to get more help on solving hydration errors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotjigjmrtufzjpl4kync.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fotjigjmrtufzjpl4kync.png" alt="Grouped hydration error issue view showing how to navigate between grouped events, highlighting the diff viewer, and pointing to a link to get more info about how to solve hydration errors.&amp;lt;br&amp;gt;
" width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Debugging hydration errors
&lt;/h2&gt;

&lt;p&gt;Given that the standard hydration error message states that the server-rendered HTML did not match the client-rendered HTML, the diff viewer is really helpful in finding those differences. The “Before” view is the server-rendered version, and the “After” view is what React rendered on the client.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6nqw4tf429fmr2803bm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk6nqw4tf429fmr2803bm.png" alt="Hydration error side by side visual diff viewer, showing that the server rendered page is the same as the client rendered page." width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What you might notice in this example, however, is that the “Before” and “After” versions look the same visually. When inspecting the HTML diff, we see a blank page rendered on the server and HTML rendered on the client. This isn’t correct. HTML is definitely being rendered on the server; I checked in my browser's network tab.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fliegd4cy2z09tc7uhgjp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fliegd4cy2z09tc7uhgjp.png" alt="Hydration error HTML diff viewer, showing a blank page on the server and invalid HTM on the client." width="800" height="515"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The real issue in the code causing the hydration error is not that the server-rendered and client-rendered HTML don’t match, it’s that my HTML is invalid (a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt; is contained within a &lt;code&gt;&amp;lt;p&amp;gt;&lt;/code&gt;) and React threw an error. This is why hydration errors suck: they don’t always make sense.&lt;/p&gt;

&lt;p&gt;So why does there appear to be no HTML rendered on the server according to the diff? Given that React threw a nonsensical hydration error for invalid HTML, Sentry can only make a best-effort guess as to what the problem is. Usually, if one event has a diff that doesn’t make sense, another event may be more helpful. The bottom line is that when React throws a hydration error, there’s a problem that needs fixing. And because React doesn’t tell us the details needed to fix the problem in production, Sentry fills in the blanks on the hydration error diff tools as much as possible so you can better debug the problem.&lt;/p&gt;
&lt;h2&gt;
  
  
  Does creating hydration error issues and diffs increase my Sentry bill?
&lt;/h2&gt;

&lt;p&gt;No. If you’re already using Session Replay, you get automatic grouped hydration error issues for free. Hydration error issues in Sentry are generated from Replays and their associated data, so have no impact on your error quota, either. Additionally, you’ll also get alerted on hydration errors by default.&lt;/p&gt;
&lt;h2&gt;
  
  
  Bonus tip: level-up your debugging by unmasking non-sensitive data in replays
&lt;/h2&gt;

&lt;p&gt;If you’re using Session Replay, you might have noticed that it masks all text content with * and blocks all media elements on the client by default, before it is sent to Sentry. In this article, the examples shown in the hydration errors images show text rather than asterisks, due to how the &lt;a href="https://docs.sentry.io/product/explore/session-replay/web/" rel="noopener noreferrer"&gt;Session Replay SDK&lt;/a&gt; was configured.&lt;/p&gt;

&lt;p&gt;If you're working on a content-based website that's free of personally identifiable information (PII), you can disable the default text masking and image blocking by configuring the &lt;code&gt;maskAllText&lt;/code&gt;  and &lt;code&gt;blockAllMedia&lt;/code&gt; configuration options in the Session Replay initialization.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replayIntegration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;maskAllText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;blockAllMedia&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This can be really helpful in debugging &lt;strong&gt;fake&lt;/strong&gt; hydration errors (that are actually invalid HTML errors) like the one demonstrated in this article.&lt;br&gt;
Note: Only use this if your site has no sensitive data or if you've already set up other options for masking or blocking actual sensitive data. &lt;a href="https://docs.sentry.io/platforms/javascript/session-replay/privacy/#privacy-configuration" rel="noopener noreferrer"&gt;Read more about Session Replay privacy configuration on the Sentry docs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Debuggability is key
&lt;/h2&gt;

&lt;p&gt;Hydration errors in production have always been a bit of a mystery, without any practical way to deal with and debug them. With the Sentry Replay SDK, you now get the HTML diffs and as much context as possible with each hydration error, helping you to debug and fix things faster. And whilst this ultimately helps you as a developer, it benefits everyone else as well: stakeholders, clients, and most importantly — your users. Now, go fix those bugs.&lt;/p&gt;

</description>
      <category>nextjs</category>
      <category>react</category>
      <category>javascript</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>The best way to debug slow web pages</title>
      <dc:creator>Salma Alam-Naylor</dc:creator>
      <pubDate>Mon, 01 Jul 2024 14:31:29 +0000</pubDate>
      <link>https://forem.com/sentry/the-number-1-best-way-to-debug-slow-web-pages-41kj</link>
      <guid>https://forem.com/sentry/the-number-1-best-way-to-debug-slow-web-pages-41kj</guid>
      <description>&lt;p&gt;Tools like Page Speed Insights and Google Lighthouse are great for providing advice for front end performance issues. But what these tools can’t do, is evaluate performance across your entire stack of distributed services and applications. &lt;/p&gt;

&lt;p&gt;This is why you need &lt;a href="https://sentry.io/for/tracing/?utm_source=devto&amp;amp;utm_medium=paid-community&amp;amp;utm_campaign=tracing-fy25q2-debuggingtracing&amp;amp;utm_content=video-ad-salma-learnmore" rel="noopener noreferrer"&gt;tracing from Sentry&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>performance</category>
      <category>tracing</category>
      <category>sentry</category>
    </item>
    <item>
      <title>How We Reduced Replay SDK Bundle Size by 35%</title>
      <dc:creator>Matt</dc:creator>
      <pubDate>Thu, 16 Nov 2023 18:02:51 +0000</pubDate>
      <link>https://forem.com/sentry/how-we-reduced-replay-sdk-bundle-size-by-35-2g0f</link>
      <guid>https://forem.com/sentry/how-we-reduced-replay-sdk-bundle-size-by-35-2g0f</guid>
      <description>&lt;p&gt;&lt;a href="https://blog.sentry.io/js-browser-sdk-bundle-size-matters/"&gt;Bundle Size matters&lt;/a&gt; - this is something we SDK engineers at Sentry are acutely aware of. In an ideal world, you'd get all the functionality you want with no additional bundle size - oh, wouldn't that be nice? Sadly, in reality any feature we add to the JavaScript SDK results in additional bundle size for the SDK - there is always a trade off to be made.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://docs.sentry.io/product/session-replay/"&gt;Session Replay&lt;/a&gt;, this is especially challenging. Session Replay allows you to capture what's going on in a users' browsers, which can help developers debug errors or other problems the user is experiencing. While this can be incredibly helpful, there is also a considerable amount of JavaScript code required to actually make this possible - thus leading to an increased bundle size.&lt;/p&gt;

&lt;p&gt;In version 7.73.0 of the JavaScript SDKs, we updated the underlying &lt;a href="https://github.com/getsentry/rrweb"&gt;rrweb&lt;/a&gt; package from v1 to v2. While this brought a host of improvements, it also came with a considerable increase in bundle size. This tipped us over the edge to declare a bundle size emergency, and focus on bringing the additional size Session Replay adds to the SDK down as much as possible.&lt;/p&gt;

&lt;p&gt;We're very happy to say that our efforts have been successful, and we managed to reduce the minified &amp;amp; gzipped bundle size compared to the rrweb 2.0 baseline by 23% (~19 KB), and by up to 35% (~29 KB) with maximum tree shaking configuration enabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffodd4ig20rv6z9arxnkl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffodd4ig20rv6z9arxnkl.png" alt="Image description" width="800" height="296"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps we took to reduce bundle size
&lt;/h2&gt;

&lt;p&gt;In order to achieve these bundle size improvements, we took a couple of steps ranging from removing unused code to build time configuration and improved tree shaking:&lt;/p&gt;

&lt;p&gt;Made it possible to remove iframe &amp;amp; shadow DOM support via a build-time flag&lt;br&gt;
Removed canvas recording support by default (users can opt-in via a config option, &lt;a href="https://github.com/getsentry/sentry-javascript/issues/6519"&gt;support is coming&lt;/a&gt;)&lt;br&gt;
Removed unused code from our rrweb fork&lt;br&gt;
Removed unused code in Session Replay itself&lt;br&gt;
Made it possible to remove the included compression worker in favor of hosting it yourself&lt;br&gt;
Moved to a different compression library with a smaller footprint&lt;/p&gt;
&lt;h2&gt;
  
  
  Primer: rrweb
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/getsentry/rrweb"&gt;rrweb&lt;/a&gt; is the underlying tool we use to make the recordings for Session Replay. While we try to contribute to the main rrweb repository as much as possible, there are some changes that are very specific to our needs at Sentry, which is why we also maintain a &lt;a href="https://github.com/getsentry/rrweb"&gt;forked version&lt;/a&gt; of rrweb with some custom changes.&lt;/p&gt;
&lt;h2&gt;
  
  
  Primer: Tree Shaking
&lt;/h2&gt;

&lt;p&gt;Tree shaking allows a JavaScript bundler to remove unused code from the final bundle. If you're not familiar with how it works and the advantages tree shaking brings, you can &lt;a href="https://docs.sentry.io/platforms/javascript/configuration/tree-shaking/"&gt;learn more about it in our docs&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Made it possible to remove iframe &amp;amp; shadow DOM support via a build-time flag
&lt;/h2&gt;

&lt;p&gt;While rrweb allows you to capture more or less everything that happens on your page, some of the things it can capture may not be necessary for some users. For these cases, we now allow users to manually remove certain parts of the rrweb codebase they may not need at build time, reducing the bundle size.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/getsentry/sentry-javascript/pull/9274"&gt;getsentry/sentry-javascript#9274&lt;/a&gt; &amp;amp; &lt;a href="https://github.com/getsentry/rrweb/pull/114"&gt;getsentry/rrweb#114&lt;/a&gt; we implemented the ground work to allow for tree shaking iframe and shadow DOM recordings. This means that if, for example, you don't have any iframes on your page, you can safely opt-in to remove this code from your application.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://github.com/getsentry/sentry-javascript-bundler-plugins/pull/428"&gt;getsentry/sentry-javascript-bundler-plugins#428&lt;/a&gt; we implemented an easy way to implement these optimizations in your app. If you are using one of our bundler plugins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@sentry/webpack-plugin"&gt;@sentry/webpack-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@sentry/vite-plugin"&gt;@sentry/vite-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@sentry/rollup-plugin"&gt;@sentry/rollup-plugin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.npmjs.com/package/@sentry/esbuild-plugin"&gt;@sentry/esbuild-plugin&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can just update to its latest version, and add this configuration to the plugin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sentryPlugin({
  bundleSizeOptimizations: {
    excludeDebugStatements: true,
    excludeReplayIframe: true,
    excludeReplayShadowDom: true,
  },
})

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will save you about 5 KB gzipped of bundle size!&lt;/p&gt;

&lt;p&gt;How we implemented build-time tree shaking flags&lt;br&gt;
We already had some build-time flags for tree shaking implemented in the JavaScript SDK itself (&lt;code&gt;__SENTRY_DEBUG__&lt;/code&gt; and &lt;code&gt;__SENTRY_TRACING__&lt;/code&gt;). We followed the same structure for rrweb:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// General tree shaking flag example
if (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) {
  console.log('log a debug message!')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, this code will result in &lt;code&gt;log a debug message&lt;/code&gt;! being logged. However, if you replace the &lt;code&gt;__SENTRY_DEBUG__&lt;/code&gt; constant at build time with &lt;code&gt;false&lt;/code&gt;, this will result in the following code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (typeof false === 'undefined' || false) {
  console.log('log a debug message!')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which bundlers will optimize to the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (false) {
  console.log('log a debug message!')
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in turn, since the code inside of &lt;code&gt;if (false)&lt;/code&gt; will definitely never be called, it will be completely tree shaken away.&lt;/p&gt;

&lt;p&gt;For rrweb, we used the same approach to allow you to remove certain recording managers:&lt;/p&gt;

&lt;p&gt;In order to avoid touching all the parts of the code that may use a manager, we added new dummy managers following the same interface but doing nothing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;interface ShadowDomManagerInterface {
  init(): void
  addShadowRoot(shadowRoot: ShadowRoot, doc: Document): void
  observeAttachShadow(iframeElement: HTMLIFrameElement): void
  reset(): void
}

class ShadowDomManagerNoop implements ShadowDomManagerInterface {
  public init() {}
  public addShadowRoot() {}
  public observeAttachShadow() {}
  public reset() {}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, in the place where the &lt;code&gt;ShadowDomManager&lt;/code&gt; is usually initialized, we can do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const shadowDomManager =
  typeof __RRWEB_EXCLUDE_SHADOW_DOM__ === 'boolean' &amp;amp;&amp;amp; __RRWEB_EXCLUDE_SHADOW_DOM__
    ? new ShadowDomManagerNoop()
    : new ShadowDomManager()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This means that by default, the regular &lt;code&gt;ShadowDomManager&lt;/code&gt; is used. However, if you replace &lt;code&gt;__RRWEB_EXCLUDE_SHADOW_DOM__&lt;/code&gt; at build time with &lt;code&gt;true&lt;/code&gt;, the &lt;code&gt;ShadowDomManagerNoop&lt;/code&gt; will be used, and the &lt;code&gt;ShadowDomManager&lt;/code&gt; will thus be tree shaken away.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removed canvas recording support by default
&lt;/h2&gt;

&lt;p&gt;Since we currently &lt;a href="https://github.com/getsentry/sentry-javascript/issues/6519"&gt;do not support replaying captured canvas elements&lt;/a&gt;, and because the canvas capturing code makes up a considerable amount of the rrweb codebase, we decided to remove this code by default from our rrweb fork, and instead allow you to opt-in to use this by passing a canvas manager into the rrweb &lt;code&gt;record()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;We implemented this in &lt;a href="https://github.com/getsentry/rrweb/pull/122"&gt;getsentry/rrweb#122&lt;/a&gt;, where we started to export a new &lt;code&gt;getCanvasManager&lt;/code&gt; function, as well as accepting such a function in the &lt;code&gt;record()&lt;/code&gt; method. With this, we can successfully tree-shake the unused canvas manager out, leading to smaller bundle size by default, unless users manually import &amp;amp; pass the &lt;code&gt;getCanvasManager&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Once we fully support capturing &amp;amp; replaying canvas elements in Session Replay &lt;a href="https://github.com/getsentry/sentry-javascript/issues/6519"&gt;(coming soon)&lt;/a&gt;, we will add a configuration option to new &lt;code&gt;Replay()&lt;/code&gt; to opt-in to canvas recording.&lt;/p&gt;

&lt;h2&gt;
  
  
  Removed unused code from rrweb
&lt;/h2&gt;

&lt;p&gt;Another step we took to reduce bundle size was to remove &amp;amp; streamline some code in our rrweb fork. rrweb can be configured in a lot of different ways and is very flexible. However, due to its flexibility, a lot of the code is not tree shakeable, because it depends on runtime configuration.&lt;/p&gt;

&lt;p&gt;For example, consider code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { large, small } from './my-code'

function doSomething(useLarge) {
  return useLarge ? large : small
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this code snippet, even if we know we only ever call this as &lt;code&gt;doSomething(false)&lt;/code&gt;, it is impossible to tree shake the &lt;code&gt;large&lt;/code&gt; code away, because statically we cannot know at build time that &lt;code&gt;useLarge&lt;/code&gt; will always be &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Because of this, we ended up fully removing certain parts of rrweb from our fork:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;hooks&lt;/code&gt; related code &lt;a href="https://github.com/getsentry/rrweb/pull/126"&gt;getsentry/rrweb#126&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;plugins&lt;/code&gt; related code getsentry/rrweb#123&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove some functions on &lt;code&gt;record&lt;/code&gt; that we don't need &lt;a href="https://github.com/getsentry/rrweb/pull/113"&gt;getsentry/rrweb#113&lt;/a&gt;&lt;br&gt;
In addition, we also made some general small improvements which we also contributed upstream to the main rrweb repository:&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid unnecessary cloning of objects or arrays &lt;a href="https://github.com/getsentry/rrweb/pull/125"&gt;getsentry/rrweb#125&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Avoid cloning events to add timestamp &lt;a href="https://github.com/getsentry/rrweb/pull/124"&gt;getsentry/rrweb#124&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Removed unused code in Session Replay
&lt;/h2&gt;

&lt;p&gt;In addition to rrweb, we also identified &amp;amp; removed some unused code in Session Replay itself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clean up some logs and internal checks &lt;a href="https://github.com/getsentry/sentry-javascript/pull/9392"&gt;getsentry/sentry-javascript#9392&lt;/a&gt;, &lt;a href="https://github.com/getsentry/sentry-javascript/pull/9391"&gt;getsentry/sentry-javascript#9391&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Remove unused function &lt;a href="https://dev.togetsentry/sentry-javascript#9393"&gt;getsentry/sentry-javascript#9393&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Updated library used for compression
&lt;/h2&gt;

&lt;p&gt;We used to compress replay payloads with &lt;a href="https://github.com/nodeca/pako"&gt;pako&lt;/a&gt;, which, while it worked well enough, turned out to be a rather large (bundle-size wise) library for compression. We switched over to use &lt;a href="https://github.com/101arrowz/fflate"&gt;fflate&lt;/a&gt; in &lt;a href="https://github.com/getsentry/sentry-javascript/pull/9436"&gt;getsentry/sentry-javascript#9436&lt;/a&gt; instead, which reduced bundle size by a few KB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Made it possible to host compression worker
&lt;/h2&gt;

&lt;p&gt;We use a web worker to compress Session Replay recording data. This helps to send less data over the network, and reduces the performance overhead for users of the SDK. However, the code for the compression worker makes up about 10 KB gzipped of our bundle size - a considerable amount!&lt;/p&gt;

&lt;p&gt;Additionally, since we have to load the worker from an inlined string due to CORS restrictions, the included worker does not work for certain environments, because it requires a more lax &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP"&gt;CSP&lt;/a&gt; setting which some applications cannot comply with.&lt;/p&gt;

&lt;p&gt;In order to both satisfy stricter CSP environments, as well as allowing to optimize the bundle size of the SDK, we added a way to tree shake the included compression worker, and instead provide a URL to a self-hosted web worker.&lt;/p&gt;

&lt;p&gt;Implemented in &lt;a href="https://github.com/getsentry/sentry-javascript/pull/9409"&gt;getsentry/sentry-javascript#9409&lt;/a&gt;, we added an example web worker that users can host on their own server, and then pass in a custom &lt;code&gt;workerUrl&lt;/code&gt; to &lt;code&gt;new Replay({})&lt;/code&gt;. With this setup, users save 10 KB gzipped of their bundle size, and can serve the worker as a separate asset that can be cached independently.&lt;/p&gt;

</description>
      <category>sentry</category>
      <category>frontend</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Performance Monitoring for Every Developer: Web Vitals &amp; Function Regression Issues</title>
      <dc:creator>Matt</dc:creator>
      <pubDate>Wed, 15 Nov 2023 20:30:49 +0000</pubDate>
      <link>https://forem.com/sentry/performance-monitoring-for-every-developer-web-vitals-function-regression-issues-26j2</link>
      <guid>https://forem.com/sentry/performance-monitoring-for-every-developer-web-vitals-function-regression-issues-26j2</guid>
      <description>&lt;p&gt;Extracting relevant insights from your performance monitoring tool can be frustrating. You often get back more data than you need, making it difficult to connect that data back to the code you wrote. Sentry’s Performance monitoring product lets you cut through the noise by detecting real problems, then quickly takes you to the exact line of code responsible. The outcome: Less noise, more actionable results.&lt;/p&gt;

&lt;p&gt;Today, we’re announcing two new features to help web, mobile, and backend developers discover and solve performance problems in their apps: Web Vitals and Function Regression Issues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Vitals: From performance scores to slow code
&lt;/h2&gt;

&lt;p&gt;Web Vitals unify the measurement of page quality into a handful of useful metrics like loading performance, interactivity, and visual stability. We used these metrics to develop the Sentry Performance Score, which is a normalized score out of 100 calculated using the weighted averages of Web Vital metrics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo5xgt7k3p3qark57nx5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpo5xgt7k3p3qark57nx5.png" alt="Image description" width="800" height="246"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Sentry Performance Score is similar to Google’s Lighthouse performance score, with one key distinction: Sentry collects data from real user experiences, while Lighthouse collects data from a controlled lab environment. We modeled the score to be as close to Lighthouse as possible while excluding components that were only relevant in a lab environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Web Vitals identify the biggest opportunities for improvement
&lt;/h2&gt;

&lt;p&gt;To improve your overall performance score, you should start by identifying individual key pages that need performance improvements. To simplify this and help you cut to the chase, we rank pages by Opportunity, which indicates the impact of a single page on the overall performance score.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frz376g8gqzf2ffm9u1e5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frz376g8gqzf2ffm9u1e5.png" alt="Image description" width="800" height="121"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this example, the highest opportunity page in Sentry’s own web app is our Issue Details page. Since this is the most commonly accessed page in our product, improving its performance would significantly improve the overall experience of using Sentry.&lt;/p&gt;

&lt;p&gt;After identifying a problematic page, the next step is to find example events where users had a subpar experience. Below, you’ll see events that represent real users loading our Issue Details page, with many experiencing poor or mediocre performance:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxa18boiieyfvqzcvp19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmxa18boiieyfvqzcvp19.png" alt="Image description" width="800" height="292"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the above screenshot, you’ll see a user that experienced a performance score of 9 out of 100 (Poor), primarily driven by a 10+ second Largest Contentful Paint (LCP). Ouch. These worst-case events highlight performance problems not evident during local development or under ideal conditions (e.g. when users have a fast network connection, a high-spec device, etc.).&lt;/p&gt;

&lt;p&gt;You’ll notice some of these events have an associated ▶️ Replay. When available, these let you see a video-like reproduction of a user’s real experience with that page. When optimizing your app’s performance, these replays can help you understand where users have a subpar experience–for example, when they struggle with 10-second load times.&lt;/p&gt;

&lt;h2&gt;
  
  
  Span waterfalls highlight your most expensive operations
&lt;/h2&gt;

&lt;p&gt;To find out what caused the slow LCP, you can click the event’s Transaction button, which provides a detailed breakdown of the operations that occurred during page load. We call these operations spans.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4va4e449o2pp39512n3j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4va4e449o2pp39512n3j.png" alt="Image description" width="800" height="394"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The most relevant spans are the ones that occur before the red LCP marker, as these spans are potentially LCP-blocking. Spans that occur after the LCP marker still impact overall page performance, but do not impact the initial page load.&lt;/p&gt;

&lt;p&gt;The first span that looks like a clear performance bottleneck is the app.page.bundle-load span, which measures how long it takes to load the JavaScript bundle. In this case, loading the bundle alone takes almost 6 seconds or about 60% of our total LCP duration.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtqn6uy4rjp89kv507q8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtqn6uy4rjp89kv507q8.png" alt="Image description" width="800" height="288"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The JavaScript bundle load time depends primarily on its size; reducing the bundle size would significantly improve page load speed. But, even if we reduced bundle load time by 50%, LCP would only drop from 12 to 7 seconds — which means we need to look for additional optimization opportunities.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevwwrhfj5r72wekg04gd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fevwwrhfj5r72wekg04gd.png" alt="Image description" width="800" height="220"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The next clear opportunity is this ui.long-task.app-init span taking almost 1 second. Long task spans represent operations over 50 milliseconds where the browser is executing JavaScript code and blocking the UI thread. Since this is a pure JavaScript operation, let’s go deeper and find out what’s going on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Browser Profiling takes you to the line of code responsible
&lt;/h2&gt;

&lt;p&gt;Identifying the code causing a long task has traditionally been difficult, as you must reproduce the issue in a development environment with access to a profiler. To solve this, Sentry has launched new support for collecting browser JavaScript profiles in production (on Chromium-based browsers). This lets you debug real user issues and collect a wide range of sample profiles across your user base.&lt;/p&gt;

&lt;p&gt;In this example, we can open the profile associated with the page load event and see the code that executed during the ~1-second long task span:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwmtuznruau4qy8tn088.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgwmtuznruau4qy8tn088.png" alt="Image description" width="800" height="503"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The EChartsReactCore.prototype.componentDidMount function takes 558 ms to execute, which is over half the duration of the long task span. This function is linked to a React component that renders a chart, provided by the open-source ECharts visualization library. This looks exactly like where we should focus our attention if we hope to bring our Issues Details pageload times down.&lt;/p&gt;

&lt;p&gt;In summary, we first identified a page with a poor performance score, then determined that reducing the JavaScript bundle size and optimizing a specific React component could significantly improve the performance of our Issue Details page. By finding pages with high opportunity scores, breaking down page load events, and diving deep into JavaScript performance with profiles, you can enhance your product’s overall user experience.&lt;/p&gt;

&lt;p&gt;Web Vitals are available to Sentry customers today. Profiling for Browser JavaScript is also now available in beta.&lt;/p&gt;

&lt;h2&gt;
  
  
  Function Regression Issues: more than just alerts
&lt;/h2&gt;

&lt;p&gt;Recently, we launched the ability to view the slowest and most regressed functions across your application. Now, we can help you debug function-level regressions with a new type of Performance Issue. Function Regression Issues notify you when a function in your application regresses, but they do more than just detect the regression— they use profiling data to give you essential context about what changed so you can solve the problem.&lt;/p&gt;

&lt;p&gt;Function Regression Issues can be detected on any platform that supports Sentry Profiling. Below, we’ll walk through a backend example in a Python project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn9jiezikcg2pv0czu7a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn9jiezikcg2pv0czu7a.png" alt="Image description" width="800" height="779"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The screenshot above is a real Function Regression Issue that identified a slowdown in Sentry’s live-running server code. It triggered because the duration of a function that checks customer rate limits stored in Redis regressed by nearly 50%.&lt;/p&gt;

&lt;p&gt;The top chart shows how function duration changed over time, while the bottom chart shows the number of invocations (throughput). You’ll notice that throughput also appears to increase during the slowdown period. This suggests that increased load might be one of the causes of this regression.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftirrbfcy6tubpynus58e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftirrbfcy6tubpynus58e.png" alt="Image description" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the screenshot above, you’ll see that the same issue gives us other essential information like which API endpoints were impacted by the regression and how much they regressed. This data reveals that the rate-limiting function was widely used and called by many of our endpoints, resulting in a significant regression in overall backend performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Jump from Regression Issues to Profiles to find the root cause
&lt;/h2&gt;

&lt;p&gt;The Function Regression Issue makes it easy to see the example profiles captured before and after the regression — they’re displayed right under the “Most Affected” endpoints. Comparing these profiles gives the most crucial context, revealing (at a code level) the change in runtime behavior that caused the regression.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidu1tcdurb46xw3rp33e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidu1tcdurb46xw3rp33e.png" alt="Image description" width="800" height="545"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, we looked at an example profile event that was captured before the regression occurred. We noticed that our regressed function calls into two functions within the third-party redis module: ConnectionPool.get_connection and ConnectionPool.release.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkkhn43gr21v914q5rjq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftkkhn43gr21v914q5rjq.png" alt="Image description" width="800" height="607"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We compared this to a profile collected after the regression and noticed that one of those two functions, ConnectionPool.get_connection, was taking significantly longer than before. Each function frame in a profile offers source context for where the function was defined and the executed line number. In this case, opening this source location in the redis module yielded the following line:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcjg09jyjbv3w22koshge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcjg09jyjbv3w22koshge.png" alt="Image description" width="800" height="489"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This line attempts to acquire a lock — and the significant wall-time duration increase when executing this line suggests that multiple processes or threads are trying to acquire the lock simultaneously. This lock contention is the code-level reason for the regression.&lt;/p&gt;

&lt;p&gt;This lock contention issue also aligns with what we saw earlier in the throughput graph – increased throughput makes contention more likely. Through additional investigation, we discovered that the increased throughput for this function corresponded to an increase in the number of Redis connections starting around the time of the regression; our next step is to isolate the source of the additional connections.&lt;/p&gt;

&lt;p&gt;Using this example, we’ve illustrated how Function Regression Issues can help you link performance regressions directly to the code causing the regression with profiling data. While this specific example focused on a backend use case, this capability works on any platform that supports Sentry Profiling.&lt;/p&gt;

&lt;p&gt;Function Regression issues are available today to Early Adopters and will become generally available over the next 2 weeks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;A high-performance bar is critical for building differentiated products that people want to use. With Web Vitals and Function Regression Issues, we’ve provided more ways for all developers to solve performance problems by connecting them to code.&lt;/p&gt;

&lt;p&gt;Tune in for more &lt;a href="https://sentry.io/events/launch-week/" rel="noopener noreferrer"&gt;exciting product announcements&lt;/a&gt; with Sentry Launch Week. To set up Sentry Performance today, check out this &lt;a href="https://docs.sentry.io/product/performance/getting-started/" rel="noopener noreferrer"&gt;guide&lt;/a&gt;. You can also drop us a line on &lt;a href="https://twitter.com/getsentry" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; or &lt;a href="https://discord.com/invite/sentry" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;, or share your Web Vitals feedback with us on &lt;a href="https://github.com/getsentry/sentry/discussions/59620" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;And, if you’re new to Sentry, you can &lt;a href="https://sentry.io/signup/" rel="noopener noreferrer"&gt;try it for free&lt;/a&gt; or &lt;a href="https://sentry.io/demo/" rel="noopener noreferrer"&gt;request a demo&lt;/a&gt; to get started.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>opensource</category>
      <category>frontend</category>
    </item>
    <item>
      <title>What’s the difference between API Latency and API Response Time?</title>
      <dc:creator>Lazar Nikolov</dc:creator>
      <pubDate>Mon, 13 Nov 2023 15:28:32 +0000</pubDate>
      <link>https://forem.com/sentry/whats-the-difference-between-api-latency-and-api-response-time-1198</link>
      <guid>https://forem.com/sentry/whats-the-difference-between-api-latency-and-api-response-time-1198</guid>
      <description>&lt;p&gt;Your app’s networking directly affects the user experience of your app. Imagine having to wait a few seconds for the page to load. Or even worse, imagine waiting for a few seconds every time you perform an action. It would be infuriating! Before you go on a fixing adventure, it’s a good idea to understand what causes that waiting time. So let’s do that!&lt;/p&gt;

&lt;h2&gt;
  
  
  The difference between Latency vs Response Time
&lt;/h2&gt;

&lt;p&gt;The total client-server communication time is called &lt;strong&gt;API Response Time&lt;/strong&gt;. That’s from the moment the client makes a request, until it receives a response back. A slow request means a very long response time. Anything that contributes to the slowness affects the response time.&lt;br&gt;
The response time consists of the &lt;strong&gt;latency&lt;/strong&gt; and the &lt;strong&gt;processing time&lt;/strong&gt;. The latency is how long it takes to transfer data between the client and the server, or the “processing component”. A few factors contribute to the latency:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the network speed&lt;/li&gt;
&lt;li&gt;the server load&lt;/li&gt;
&lt;li&gt;the performance of the load balancer&lt;/li&gt;
&lt;li&gt;the size of the data we’re sending&lt;/li&gt;
&lt;li&gt;and the API design&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="//images.ctfassets.net/em6l9zw4tzag/3HSe1PeCYHhJ3XzXY4Waka/c1512a223d3988396c4718a84dccdc03/response-time-latency.png" class="article-body-image-wrapper"&gt;&lt;img src="//images.ctfassets.net/em6l9zw4tzag/3HSe1PeCYHhJ3XzXY4Waka/c1512a223d3988396c4718a84dccdc03/response-time-latency.png" alt="API Response Time"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For example, the client sends a data request to the API and gets a response back in 3 seconds. The total response time is 3 seconds. The latency might be 300ms, which leaves 2500ms, or 2.5 seconds, for processing. Just to have a number in mind, high-performing APIs are considered to have between 0.1 and 1 second average response time. At 2 seconds the delay is noticeable. At 5 seconds the delay is so significant that the users are starting to abandon the application/website.&lt;/p&gt;

&lt;p&gt;The response time is one of the most important metrics we need to keep our eyes on. If not, it can significantly hurt the user experience, and even the business itself. Back in 2008, &lt;a href="https://news.ycombinator.com/item?id=273900" rel="noopener noreferrer"&gt;Amazon reported&lt;/a&gt; that every 100ms latency costs 1% of their profit. &lt;a href="http://glinden.blogspot.com/2006/11/marissa-mayer-at-web-20.html" rel="noopener noreferrer"&gt;Google also reported&lt;/a&gt; that in an experiment that increased the number of search results they noticed that half a second delay caused a 20% drop in traffic. These are significant numbers. Amazon makes $19.4 billion per month. 1% of those sales is $194 million. Add up that number for every 100ms latency and you’ll see the damage. As you can see, learning how to monitor and optimize your API response time is a very good investment.&lt;/p&gt;

&lt;p&gt;Just like any other web performance metric, the response time should be measured and monitored in production. Your computer and internet speed might be fast, but your users’ computers and internet are probably not as fast. That results in much less favorable results than what you’ll see if you measured your API’s response time locally. But working with production data also means that you’re working with outliers. The Average Response Time is not always a good metric. Let’s learn how to properly measure the API response time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Measuring response time
&lt;/h2&gt;

&lt;p&gt;First let’s talk about why ART (Average Response Time) is not a good metric. There are always the edge cases where a small number of your users are reporting the worst response times imaginable. That could be because of a really outdated computer, or they’re trying to access your web app with a bad internet connection (subway, remote locations, etc…), or your API experienced a brief downtime because of a bug or your deployment. You don’t care about those cases because there is usually nothing you can do about them. Calculating an average on that data will take those outliers into account as well, and you don’t want that. You want to exclude those data points from your data. Enter: &lt;strong&gt;percentiles&lt;/strong&gt;.&lt;br&gt;
Percentiles provide you with a different view of your performance data. The data is sorted in a descending order and cut at specific % points. The most commonly used percentiles are p50, p75, p95, p99 and p100.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;P50, also called the Median&lt;/strong&gt;, is the value below which 50% of the data falls. This would be the typical performance of your API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P75&lt;/strong&gt; is the value where 75% of the data falls. This percentile is good for frontend applications because it includes more variable data, which mirrors the variety of the user conditions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P95&lt;/strong&gt; is more valuable for backend applications where the data is more uniform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P99, also called Peak Response Time&lt;/strong&gt;, is also more valuable for backend applications, and it marks the upper limit of the performance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;P100&lt;/strong&gt; is the maximum observed value. This is the worst measured performance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you want to get into more detail about percentiles, read our &lt;a href="https://blog.sentry.io/choosing-the-right-metric-a-guide-to-percentiles-perf-monitoring/#percentiles-understanding-variability" rel="noopener noreferrer"&gt;“Choosing the Right Metric” article&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Another important metric is the Error/Failure Rate of your API. The Error/Failure Rate is a value that indicates the % of requests that resulted with a non-200 status code. Having this data can also help you figure out if things are wrong with your API or documentation. If you’re seeing more “400s” status codes, it might mean that clients don’t use your API properly, or don’t understand it well. If you’re seeing more “500s” status codes then it means you have issues with your code.&lt;/p&gt;
&lt;h2&gt;
  
  
  Monitoring API response time in production
&lt;/h2&gt;

&lt;p&gt;To monitor your API’s response time (and other metrics as well) in production, you need to implement a mechanism that will continuously take those measurements, save them in a database, and provide you with tools to query and visualize the data. Sentry is an &lt;a href="https://sentry.io/for/performance/" rel="noopener noreferrer"&gt;application performance monitoring tool&lt;/a&gt; that you can use to monitor all of your &lt;a href="https://sentry.io/for/frontend/" rel="noopener noreferrer"&gt;frontend&lt;/a&gt;, &lt;a href="https://sentry.io/for/backend/" rel="noopener noreferrer"&gt;backend&lt;/a&gt;, and &lt;a href="https://sentry.io/for/mobile/" rel="noopener noreferrer"&gt;mobile application&lt;/a&gt; projects—not just your API. And don’t worry, Sentry probably has an &lt;a href="https://sentry.io/platforms/" rel="noopener noreferrer"&gt;SDK&lt;/a&gt; for whatever framework you’re using.&lt;/p&gt;

&lt;p&gt;Getting started is easy. After installing and configuring Sentry’s SDK for your framework of choice, you’ll get automatic instrumentation, which means a bigger part of your backend is already set up with performance monitoring. Depending on the size of your application, this could be enough to get started. If you need more detailed performance data, you can add &lt;a href="https://docs.sentry.io/platforms/node/performance/instrumentation/custom-instrumentation/" rel="noopener noreferrer"&gt;custom instrumentations&lt;/a&gt; in certain places.&lt;/p&gt;

&lt;p&gt;When you start getting performance data in your Sentry account, identifying what makes your API slow is quick. Getting to the root cause of the slow performance is even quicker. Sentry’s tracing data makes the performance bottlenecks obvious. Here’s a short arcade that shows you how you can identify performance bottlenecks in your application and figure out what causes them:&lt;/p&gt;


&lt;div class="crayons-card c-embed text-styles text-styles--secondary"&gt;
    &lt;div class="c-embed__content"&gt;
        &lt;div class="c-embed__cover"&gt;
          &lt;a href="https://app.arcade.software/share/DsIzAmoLxA4bW8nqsiOZ" class="c-link align-middle" rel="noopener noreferrer"&gt;
            &lt;img alt="" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fapp.arcade.software%2Fog%2FDsIzAmoLxA4bW8nqsiOZ" height="630" class="m-0" width="1200"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="c-embed__body"&gt;
        &lt;h2 class="fs-xl lh-tight"&gt;
          &lt;a href="https://app.arcade.software/share/DsIzAmoLxA4bW8nqsiOZ" rel="noopener noreferrer" class="c-link"&gt;
            Backend Performance Monitoring | Arcade
          &lt;/a&gt;
        &lt;/h2&gt;
          
        &lt;div class="color-secondary fs-s flex items-center"&gt;
            &lt;img alt="favicon" class="c-embed__favicon m-0 mr-2 radius-0" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.arcade.software%2Fimages%2Fbutton-logo-128.png" width="128" height="128"&gt;
          app.arcade.software
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;p&gt;A good resource on improving the performance of your API using Sentry is Lincoln Loop’s “&lt;a href="https://lincolnloop.com/insights/optimizing-response-time-19x-faster/" rel="noopener noreferrer"&gt;19x faster response time&lt;/a&gt;” article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So, a quick recap. API latency is the time it takes for the data to be transmitted between the client and the backend. The API response time is the latency + the time it takes for the backend to process the request and return the result. Factors like network speed, load balancer, data size, server load, API design, and our code affect the response time.&lt;br&gt;
To properly monitor the performance of your backend, you need to use a tool that will monitor your application while in production. Sentry can help you with that, head to &lt;a href="https://sentry.io/signup" rel="noopener noreferrer"&gt;https://sentry.io/signup&lt;/a&gt; to get started.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>performance</category>
    </item>
    <item>
      <title>Getting Started with Jetpack Compose</title>
      <dc:creator>Lazar Nikolov</dc:creator>
      <pubDate>Fri, 03 Mar 2023 21:04:28 +0000</pubDate>
      <link>https://forem.com/sentry/getting-started-with-jetpack-compose-cpb</link>
      <guid>https://forem.com/sentry/getting-started-with-jetpack-compose-cpb</guid>
      <description>&lt;p&gt;Recently, we wrote about the &lt;a href="https://blog.sentry.io/2022/12/07/mobile-the-future-is-declarative/" rel="noopener noreferrer"&gt;demonstrative move to declarative UI&lt;/a&gt;. With &lt;a href="http://d.android.com/compose" rel="noopener noreferrer"&gt;Jetpack Compose&lt;/a&gt;, Android is joining the declarative trends. &lt;/p&gt;

&lt;p&gt;Jetpack Compose, a new declarative UI toolkit by Google made for building native Android apps, is rapidly gaining traction. In fact, as announced at the &lt;a href="https://www.youtube.com/watch?t=1069&amp;amp;v=Awi4J5-tbW4&amp;amp;feature=youtu.be" rel="noopener noreferrer"&gt;Android Dev Summit last year&lt;/a&gt; last year, 160 of the top 1,000 Android apps already use Jetpack Compose. In contrast to the traditional XML Views, Jetpack Compose allows you to build UIs using composable functions that describe how the UI should look and behave. &lt;/p&gt;

&lt;p&gt;The main advantage of using Jetpack Compose is that it allows you to write UI code that is more concise and easier to understand. This leads to improved maintainability and reduced development time. &lt;/p&gt;

&lt;p&gt;The main disadvantage of using Jetpack Compose is that it’s relatively new, so its ecosystem is limited and the number of available libraries, tools, and resources is lower than the traditional ecosystem. &lt;/p&gt;

&lt;p&gt;Despite that, we believe that learning Jetpack Compose is worth the learning curve and challenges. Here are some tips we've found helpful as you are getting started. &lt;/p&gt;

&lt;h2&gt;
  
  
  How to start using Jetpack Compose
&lt;/h2&gt;

&lt;p&gt;The recommended IDE for working with Jetpack Compose is &lt;a href="https://developer.android.com/studio" rel="noopener noreferrer"&gt;Android Studio&lt;/a&gt;. After downloading and installing Android Studio, you’ll get the option to create a new project. To create a new Jetpack Compose application, you need to select either the &lt;code&gt;Empty Compose Activity&lt;/code&gt; (which uses Material v2), or &lt;code&gt;Empty Compose Activity (Material3)&lt;/code&gt; (which uses the Material v3 which is in version 1.0 as of last year). You can see both options in the top right of this screenshot: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc211jlseavdxdgw7e2s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzc211jlseavdxdgw7e2s.png" alt="Creating a new Jetpack Compose app from Android Studio" width="800" height="452"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This is the easiest way to get started with Jetpack Compose. If you’d like to enable Jetpack Compose into an existing Android application, here’s what you need to do:&lt;/p&gt;

&lt;p&gt;1. Add the following build configurations in your app’s &lt;code&gt;build.gradle&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;android&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;buildFeatures&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// this flag enables Jetpack Compose&lt;/span&gt;
    &lt;span class="n"&gt;compose&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;

  &lt;span class="n"&gt;composeOptions&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// the compiler version should match&lt;/span&gt;
    &lt;span class="c1"&gt;// your project's Kotlin version&lt;/span&gt;
    &lt;span class="n"&gt;kotlinCompilerExtensionVersion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"1.3.2"&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Add the Compose BOM (&lt;a href="https://developer.android.com/jetpack/compose/bom/bom" rel="noopener noreferrer"&gt;Bill of Materials&lt;/a&gt;) and the subset of Compose dependencies to your dependencies:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight groovy"&gt;&lt;code&gt;&lt;span class="n"&gt;dependencies&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kt"&gt;def&lt;/span&gt; &lt;span class="n"&gt;composeBom&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;platform&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'androidx.compose:compose-bom:2023.01.00'&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="n"&gt;composeBom&lt;/span&gt;
  &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt; &lt;span class="n"&gt;composeBom&lt;/span&gt;

  &lt;span class="c1"&gt;// Choose one of the following:&lt;/span&gt;
  &lt;span class="c1"&gt;// Material Design 3&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.material3:material3'&lt;/span&gt;
  &lt;span class="c1"&gt;// or Material Design 2&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.material:material'&lt;/span&gt;
  &lt;span class="c1"&gt;// or skip Material Design and build directly on top of foundational components&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.foundation:foundation'&lt;/span&gt;
  &lt;span class="c1"&gt;// or only import the main APIs for the underlying toolkit systems,&lt;/span&gt;
  &lt;span class="c1"&gt;// such as input and measurement/layout&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.ui:ui'&lt;/span&gt;       

  &lt;span class="c1"&gt;// Android Studio Preview support&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.ui:ui-tooling-preview'&lt;/span&gt;
  &lt;span class="n"&gt;debugImplementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.ui:ui-tooling'&lt;/span&gt;

  &lt;span class="c1"&gt;// UI Tests&lt;/span&gt;
  &lt;span class="n"&gt;androidTestImplementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.ui:ui-test-junit4'&lt;/span&gt;
  &lt;span class="n"&gt;debugImplementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.ui:ui-test-manifest'&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional - Included automatically by material, only add when you need&lt;/span&gt;
  &lt;span class="c1"&gt;// the icons but not the material library (e.g. when using Material3 or a&lt;/span&gt;
  &lt;span class="c1"&gt;// custom design system based on Foundation)&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.material:material-icons-core'&lt;/span&gt;
  &lt;span class="c1"&gt;// Optional - Add full set of material icons&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.material:material-icons-extended'&lt;/span&gt;
  &lt;span class="c1"&gt;// Optional - Add window size utils&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.material3:material3-window-size-class'&lt;/span&gt;

  &lt;span class="c1"&gt;// Optional - Integration with activities&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.activity:activity-compose:1.5.1'&lt;/span&gt;
  &lt;span class="c1"&gt;// Optional - Integration with ViewModels&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1'&lt;/span&gt;
  &lt;span class="c1"&gt;// Optional - Integration with LiveData&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.runtime:runtime-livedata'&lt;/span&gt;
  &lt;span class="c1"&gt;// Optional - Integration with RxJava&lt;/span&gt;
  &lt;span class="n"&gt;implementation&lt;/span&gt; &lt;span class="s1"&gt;'androidx.compose.runtime:runtime-rxjava2'&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How do you build UI in Jetpack Compose?
&lt;/h2&gt;

&lt;p&gt;Jetpack Compose uses Composables to define the view hierarchy, and modifier to apply visual appearance and behavior changes to the composables they’re added to.&lt;/p&gt;

&lt;h3&gt;
  
  
  Composable functions
&lt;/h3&gt;

&lt;p&gt;Composable functions (or just Composables) are ordinary Kotlin functions that are annotated with &lt;code&gt;@Composable&lt;/code&gt;, can be nested within another composable functions, and return a hierarchy of other composables in order to define their UI. Let’s see a simple composable that defines a contact row UI that contains a user photo, and a name and phone number:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ContactRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Row&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Image&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="n"&gt;painter&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;painterResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;R&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;drawable&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="n"&gt;contentDescription&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"A photo of a user"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nc"&gt;Column&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;phone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;Row&lt;/code&gt; composable is a layout composable that renders its children one next to another. The &lt;code&gt;Image&lt;/code&gt; composable is the first child which is going to render the &lt;code&gt;user&lt;/code&gt; drawable. Then we have the &lt;code&gt;Column&lt;/code&gt; composable which, similar to the &lt;code&gt;Row&lt;/code&gt;, is a layout composable, but it renders its children one below another. The children of the &lt;code&gt;Column&lt;/code&gt; composable are two &lt;code&gt;Text&lt;/code&gt; composables that render the user’s name and phone number.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modifiers
&lt;/h3&gt;

&lt;p&gt;Modifiers are used to change the visual appearance and behavior of the composables they’re added to. We use modifiers when we want to change UI elements such as the size of the composable (width, height), the padding, background, or alignment.&lt;/p&gt;

&lt;p&gt;Modifiers can also be stacked on top of each other, allowing us to modify multiple visual properties. Here’s an example of how we can set the padding and max width of the Contact row from the previous snippet:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;ContactRow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxWidth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How do you interact with data in Jetpack Compose?
&lt;/h2&gt;

&lt;p&gt;There are multiple ways to keep data within your Jetpack Compose app: &lt;a href="https://volcano-bovid-81c.notion.site/Getting-Started-with-Jetpack-Compose-7187d91a2f0c4e56969db56c51c91ec1" rel="noopener noreferrer"&gt;MutableState&lt;/a&gt;, &lt;a href="https://volcano-bovid-81c.notion.site/Getting-Started-with-Jetpack-Compose-7187d91a2f0c4e56969db56c51c91ec1" rel="noopener noreferrer"&gt;LiveData&lt;/a&gt;, and &lt;a href="https://volcano-bovid-81c.notion.site/Getting-Started-with-Jetpack-Compose-7187d91a2f0c4e56969db56c51c91ec1" rel="noopener noreferrer"&gt;StateFlow&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  MutableState
&lt;/h3&gt;

&lt;p&gt;In Jetpack Compose, state management can be accomplished by using the &lt;code&gt;remember&lt;/code&gt; API to store an object in memory, and the &lt;code&gt;mutableStateOf&lt;/code&gt; to declare a state variable. We can store both mutable and immutable objects. The &lt;code&gt;mutableStateOf&lt;/code&gt; creates an observable &lt;a href="https://developer.android.com/reference/kotlin/androidx/compose/runtime/MutableState" rel="noopener noreferrer"&gt;&lt;code&gt;MutableState&amp;lt;T&amp;gt;&lt;/code&gt;&lt;/a&gt;, which is an observable type.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;MutableState&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;State&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="py"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any changes to &lt;code&gt;value&lt;/code&gt; schedules a recomposition (re-rendering) of any composable functions that read it.&lt;br&gt;
There are three ways to declare a &lt;code&gt;MutableState&lt;/code&gt; object:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;val mutableState = remember { mutableStateOf(0) }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;var value by remember { mutableStateOf(false) }&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;val (value, setValue) = remember { mutableStateOf("Hello, Compose!") }&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  LiveData
&lt;/h3&gt;

&lt;p&gt;LiveData is a data holder class that can be observed within a given lifecycle, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This ensures LiveData only updates observers that are in an active lifecycle state, which also ensures no memory leaks happen within your app.&lt;/p&gt;

&lt;p&gt;Let’s see an example of working with LiveData:&lt;br&gt;
1. You need to create an instance of the &lt;code&gt;LiveData&lt;/code&gt; class to hold a certain type of data, which is usually done within your &lt;a href="https://developer.android.com/reference/androidx/lifecycle/ViewModel" rel="noopener noreferrer"&gt;ViewModel&lt;/a&gt; class (use &lt;code&gt;MutableLiveData&lt;/code&gt; if you’d like to update the value at some point):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a MutableLiveData instance that keeps a string&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableLiveData&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Obtain the value in your composable by calling the &lt;code&gt;observeAsState&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;HomeScreen&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create an observer of the state of userName&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;observeAsState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Use the value in your UI&lt;/span&gt;
  &lt;span class="nc"&gt;Column&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. To update &lt;code&gt;userName&lt;/code&gt;'s value (also usually done in the view model), create a function that sets the new value to its &lt;code&gt;value&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newName&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. You’d use the new function in your Compose file as &lt;code&gt;viewModel.updateUserName("...")&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  StateFlow
&lt;/h3&gt;

&lt;p&gt;StateFlow is a newer alternative to LiveData. Both have similarities, and both are observable. Here’s how you can work with StateFlow in Jetpack Compose:&lt;br&gt;
1. Create an instance of &lt;code&gt;StateFlow&lt;/code&gt; to hold a certain type of data (use MutableStateFlow if you’d like to update the value at some point)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a MutableStateFlow instance that keeps a string&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;2. Obtain the value in your composable by calling the &lt;code&gt;collectAsState&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;HomeScreen&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create an observer to collect the state of userName&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;viewModel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;collectAsState&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Use the value in your UI&lt;/span&gt;
  &lt;span class="nc"&gt;Column&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;3. To update &lt;code&gt;userName&lt;/code&gt;'s value (also usually done in the view model), create a function that sets the new value to its &lt;code&gt;value&lt;/code&gt; property:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="n"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newName&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;4. You’d use the new function in your Compose file as &lt;code&gt;viewModel.updateUserName("...")&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What are the best practices for Jetpack Compose?
&lt;/h2&gt;

&lt;p&gt;Aside from the &lt;a href="https://developer.android.com/jetpack/compose/performance/bestpractices" rel="noopener noreferrer"&gt;official best practices&lt;/a&gt; documentation, we’ve got a few additional tips that would make your codebase safer and easier to work in.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code organization
&lt;/h3&gt;

&lt;p&gt;Every developer or organization has their own opinions on how a project should be structured. There is no “right” or “wrong” way to do it. Okay, maybe it’s wrong to put every file in one single directory 😅. Here’s an example structure to help you get started, which you can modify and evolve as your project grows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;.
├─ 📁 &lt;span class="gs"&gt;**ui**&lt;/span&gt; (to keep all your UI related things)
|  ├─ 📁 &lt;span class="gs"&gt;**screens**&lt;/span&gt; (where you define your screens composables and their corresponding view models)
|  |  └─ 📁 &lt;span class="gs"&gt;**home**&lt;/span&gt;
|  |     ├─ 📝 &lt;span class="gs"&gt;**HomeScreen.kt**&lt;/span&gt; (the UI for the Home screen)
|  |     └─ 📝 &lt;span class="gs"&gt;**HomeViewModel.kt**&lt;/span&gt; (the view model for the Home screen)
|  ├─ 📁 &lt;span class="gs"&gt;**components**&lt;/span&gt; (where you define components that are shared across multiple screens)
|  |  └─ 📝 &lt;span class="gs"&gt;**UserList.kt**&lt;/span&gt;
|  └─ 📁 &lt;span class="gs"&gt;**theme**&lt;/span&gt; (where you keep your theme definition and design tokens)
|     ├─ 📝 &lt;span class="gs"&gt;**Colors.kt**&lt;/span&gt;
|     ├─ 📝 &lt;span class="gs"&gt;**Shapes.kt**&lt;/span&gt;
|     ├─ 📝 &lt;span class="gs"&gt;**Theme.kt**&lt;/span&gt;
|     └─ 📝 &lt;span class="gs"&gt;**Typography.kt**&lt;/span&gt;
├─ 📁 &lt;span class="gs"&gt;**utils**&lt;/span&gt; (where you keep your various utility functions, like data converters etc...)
|  └─ 📝 &lt;span class="gs"&gt;**DateUtils.kt**&lt;/span&gt; 
└─ 📝 &lt;span class="gs"&gt;**MainActivity.kt**&lt;/span&gt; (this is your default MainActivity)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Avoid creating “god” files
&lt;/h3&gt;

&lt;p&gt;“God” files are a big no-no. They’re files that contain all code associated with them: UI, domain, business logic, utility functions etc… It might be easier putting everything into one file, but maintaining that would get harder and harder as you add functionalities. The solution to this is using a proper architecture in your Jetpack Compose app.&lt;/p&gt;

&lt;p&gt;There are multiple architectures that you can use, all with their own pros and cons. The most common one in Jetpack Compose is MVVM, abbreviated from Model-View-ViewModel, because Jetpack Compose has a first-class &lt;a href="https://developer.android.com/topic/libraries/architecture/viewmodel" rel="noopener noreferrer"&gt;&lt;code&gt;ViewModel&lt;/code&gt;&lt;/a&gt; implementation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stay true to the MVVM
&lt;/h3&gt;

&lt;p&gt;As you saw from the previous examples, Jetpack Compose has a first-class &lt;a href="https://developer.android.com/topic/libraries/architecture/viewmodel" rel="noopener noreferrer"&gt;&lt;code&gt;ViewModel&lt;/code&gt;&lt;/a&gt; implementation. The MVVM, or Model-View-ViewModel, is a software design pattern that is structured to separate business logic from the UI. That means, your UI should not handle state updates, but it should let the view model do that by sending it user actions.&lt;/p&gt;

&lt;p&gt;Let’s explore that with an example. Remember the &lt;code&gt;MutableStateFlow&lt;/code&gt; example from before? That example was oversimplified on purpose, but in a real-world project you would never expose a &lt;code&gt;MutableStateFlow&lt;/code&gt; from your &lt;code&gt;ViewModel&lt;/code&gt;, but just a &lt;code&gt;StateFlow&lt;/code&gt;. In order to make that work, you should define a private &lt;code&gt;MutableStateFlow&lt;/code&gt; variable and a public &lt;code&gt;StateFlow&lt;/code&gt; variable that returns the mutable flow by invoking the &lt;code&gt;asStateFlow()&lt;/code&gt; method.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Create a private MutableStateFlow instance that keeps a string&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Create a public StateFlow that returns the MutableStateFlow as immutable&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_userName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asStateFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this simple change, we’re preventing the UI from being able to change the state. But, how do we actually change the state? We’ll expose a function from the view model that does that!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_userName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asStateFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// Create a public function that updates the private MutableStateFlow value&lt;/span&gt;
  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;setUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_userName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newName&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So now the UI has an immutable &lt;code&gt;StateFlow&lt;/code&gt; that it can observe, and a function to update its value. The business logic lives inside of the view model, while the Composable is only responsible to react to state changes and send user actions to the view model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don’t create a thousand flows
&lt;/h3&gt;

&lt;p&gt;So you’ve learned how to create state flows. Great! Would you repeat the same for every state variable you need in your UI? Please don’t 😅 To avoid that, you can create a &lt;code&gt;data class&lt;/code&gt; that keeps all of the values of your state, and create a single flow that uses it.&lt;/p&gt;

&lt;p&gt;Let’s learn this with an example. If we wanted to also keep the user’s phone number, email and address, we can create a data class called &lt;code&gt;HomeScreenState&lt;/code&gt; that contains all those values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;data class&lt;/span&gt; &lt;span class="nc"&gt;HomeScreenState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userPhone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;userAddress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we would refactor our view model to use the new &lt;code&gt;HomeScreenState&lt;/code&gt; instead of a &lt;code&gt;String&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_uiState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeScreenState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeScreenState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asStateFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we can use all of the values in our composable by &lt;code&gt;viewModel.uiState.userName&lt;/code&gt;. If we also wanted to be able to update all those values, we would create functions for each of them in our view model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;HomeViewModel&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ViewModel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;_uiState&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MutableStateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeScreenState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
  &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;uiState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;StateFlow&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;HomeScreenState&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;asStateFlow&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateUserName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;userName&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newName&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;updateUserEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;newEmail&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="n"&gt;_uiState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="n"&gt;userEmail&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;newEmail&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Keep a close eye on your errors and performance in production
&lt;/h3&gt;

&lt;p&gt;As you're getting acclimated to Jetpack Compose, an error and performance monitoring tool can be really helpful to reduce your learning curve and ensure that your app is bug-free. Jetpack Compose does a lot of heavy lifting for developers – as a declarative toolkit, developers need to write less code to describe their UI, and Jetpack Compose takes care of the rest. But it does abstract away a lot of code, making it difficult to identify errors.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://sentry.io/for/android/?utm_medium=website&amp;amp;utm_source=jetpack-compose-getting-started&amp;amp;utm_campaign=android&amp;amp;utm_content=blog&amp;amp;utm_term=" rel="noopener noreferrer"&gt;Sentry&lt;/a&gt; offers an out-of-the-box integration that can help you build a better Jetpack Compose app. The integration gives precise context to reduce troubleshooting time with transactions and breadcrumbs. Keep an eye on all the issues and crashes your app is experiencing in production, with a lot of context as to why the issue happened, the exact line of code that triggered it, and all sorts of hardware and software info of the device it ran.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzowc7j7wb86pi5wn876.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqzowc7j7wb86pi5wn876.png" alt="Android app monitored in Sentry" width="800" height="459"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I’d totally understand if you’re feeling overwhelmed by now, but let’s do a quick recap! We’ve learned how to create a new Jetpack Compose project, and that Jetpack Compose uses Composables and Modifiers to define the view hierarchy and apply visual changes. Data in Jetpack Compose can be handled either with a &lt;code&gt;MutableState&lt;/code&gt;, &lt;code&gt;LiveData&lt;/code&gt;, or &lt;code&gt;StateFlow&lt;/code&gt;, which make the composables that observe it re-render when the value changes, making our UI dynamic. We also learned how to keep our projects tidy, and how to write maintainable composables and view models.&lt;/p&gt;

&lt;p&gt;Even though it’s a relatively new technology, Jetpack Compose’s ecosystem is steadily growing, so we can expect to see a lot of libraries pop up that make it easier to create Jetpack Compose apps. With companies like Lyft, Twitter, Airbnb, Square, Reddit, and Firefox putting their trust into it, more and more developers will follow along and create apps, libraries and resources for Jetpack Compose.&lt;/p&gt;

</description>
      <category>android</category>
      <category>kotlin</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Mobile: The Future is Declarative</title>
      <dc:creator>Lazar Nikolov</dc:creator>
      <pubDate>Fri, 30 Dec 2022 10:55:05 +0000</pubDate>
      <link>https://forem.com/sentry/mobile-the-future-is-declarative-1bc9</link>
      <guid>https://forem.com/sentry/mobile-the-future-is-declarative-1bc9</guid>
      <description>&lt;p&gt;The mobile development ecosystem has always been very diverse, arguably more diverse than the web development ecosystem. While it seems like every day there are more frameworks and tools for web developers, a lot of them are built on top of JavaScript and implement similar patterns to each other. The mobile ecosystem, on the other hand, has a core set of languages that make the differences between mobile tools and frameworks much easier to identify.&lt;/p&gt;

&lt;p&gt;Two of the leading native mobile platforms are native Android and iOS, both of which have had interesting innovations recently. With the introductions of Jetpack Compose and SwiftUI, developing native apps looks very similar to developing React Native or Flutter apps. Both React Native and Flutter have a declarative approach from the start, but with Android and iOS now joining the declarative bandwagon, we can see that the future of mobile development is declarative.&lt;/p&gt;

&lt;h2&gt;
  
  
  A little history
&lt;/h2&gt;

&lt;p&gt;While React Native and Flutter have always taken a declarative approach, Android and iOS started completely different.&lt;/p&gt;

&lt;p&gt;Android utilized (and still does) Views. Views are XML files where we define our user interfaces using widgets like Button, TextView, LinearLayout and where we assign IDs to those widgets. Those IDs are then used to reference widgets in our Java files where we develop the functionality and behavior. Here’s an example View file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;LinearLayout&lt;/span&gt;
  &lt;span class="na"&gt;xmlns:android=&lt;/span&gt;&lt;span class="s"&gt;"http://schemas.android.com/apk/res/android"&lt;/span&gt;
  &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"match_parent"&lt;/span&gt;
  &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"match_parent"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;TextView&lt;/span&gt;
    &lt;span class="na"&gt;android:id=&lt;/span&gt;&lt;span class="s"&gt;"@+id/text_view_id"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_height=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
    &lt;span class="na"&gt;android:layout_width=&lt;/span&gt;&lt;span class="s"&gt;"wrap_content"&lt;/span&gt;
    &lt;span class="na"&gt;android:text=&lt;/span&gt;&lt;span class="s"&gt;"@string/hello"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/LinearLayout&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we would create a reference to the widget like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;Activity&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Bundle&lt;/span&gt; &lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;super&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onCreate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;setContentView&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;R&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;layout&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;activity_main&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Create a reference to the TextView using its ID&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;TextView&lt;/span&gt; &lt;span class="n"&gt;helloTextView&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;TextView&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;findViewById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;R&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;text_view_id&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Change the TextView's text&lt;/span&gt;
    &lt;span class="n"&gt;helloTextView&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setText&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;R&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;user_greeting&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
  &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;iOS did have some declarative features, like Auto Layout, UIAppearance, the Objective-C @property declarations, KVC collection operators, and Combine, but it still required writing some level of imperative code.&lt;/p&gt;

&lt;p&gt;For example, iOS had (and still has) Storyboards. Storyboards is a graphical tool we use to build our UIs. It is actually an XML file under the hood, but the developers almost never touch the XML code itself. Here’s how we added UI elements in our Storyboards, and created references and actions:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/pTTpjeSARZw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Becoming declarative
&lt;/h2&gt;

&lt;p&gt;To refresh our memory, the &lt;strong&gt;imperative&lt;/strong&gt; approach is when you provide step-by-step instructions until you achieve the desired UI. The &lt;strong&gt;declarative&lt;/strong&gt; approach is when you describe how the final state of the desired UI should look.&lt;/p&gt;

&lt;p&gt;Android’s new Jetpack Compose is written in Kotlin, which is a programming language as opposed to XML, which is a markup language. The previous XML View example would look something like this in Jetpack Compose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MainActivity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ComponentActivity&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Bundle&lt;/span&gt;&lt;span class="p"&gt;?)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;savedInstanceState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// Start defining the UI&lt;/span&gt;
    &lt;span class="nf"&gt;setContent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nc"&gt;BasicsCodelabTheme&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Add a Row (alternative to LinearLayout)&lt;/span&gt;
        &lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxSize&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="c1"&gt;// Add a Text (alternative to TextView)&lt;/span&gt;
          &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello, Sentry!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, building UIs with this approach requires a lot less code. And, since we stay in a programming language context, we can directly use variables and callbacks instead of creating references to widgets and attaching logic to those widgets. Any change in the values will trigger a recomposition (rerender), so our UI is always up to date.&lt;/p&gt;

&lt;p&gt;iOS’s SwiftUI is pretty much the same, just built with Swift instead. The previous Storyboard example would look something like this in SwiftUI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// handle onClick&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Button"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, much less code. And we’re in a programming language, so we can directly define the Button’s label and provide an &lt;code&gt;onClick&lt;/code&gt; callback without creating a reference.&lt;/p&gt;

&lt;p&gt;One issue we stumbled upon when working with the imperative UIKit is activating constraints of a view before adding it to the view hierarchy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;subview&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UIView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;subview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leadingAnchor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;constraint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;equalTo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leadingAnchor&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isActive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="c1"&gt;// ^^^ it's going to crash on this line with the error:&lt;/span&gt;
&lt;span class="c1"&gt;//&lt;/span&gt;
&lt;span class="c1"&gt;// Unable to activate constraint with anchors *** because they have&lt;/span&gt;
&lt;span class="c1"&gt;// no common ancestor. Does the constraint or its anchors reference&lt;/span&gt;
&lt;span class="c1"&gt;// items in different view hierarchies?  That's illegal.&lt;/span&gt;

&lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addSubview&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subview&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swapping the &lt;code&gt;subview.leadingAnchor.constraint(...)&lt;/code&gt; line with the &lt;code&gt;view.addSubview(view)&lt;/code&gt; line will work without an error. Admittedly, it took me a while to understand what was going on when I first encountered this error, and I triggered this error a few more times until it became a muscle memory for me. But with the new declarative approach we’re not going to encounter this issue. (Muscle memory in coding is good, but it’s better to improve the developer experience).&lt;/p&gt;

&lt;p&gt;This shift towards declarative in Android and iOS is a huge step towards better developer experience and faster development. Defining your UI in a declarative way using a programming language solves a lot of the pain points that Android and iOS developers have.&lt;/p&gt;

&lt;p&gt;Here are some of the benefits of the declarative approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Theming becomes easier and more dynamic.&lt;/li&gt;
&lt;li&gt;State Management feels natural and actually plays a &lt;strong&gt;crucial role&lt;/strong&gt; in the new declarative approach.&lt;/li&gt;
&lt;li&gt;The composition approach allows us to compose our UI by nesting components, which is inferred from the nesting of the code’s block scope.&lt;/li&gt;
&lt;li&gt;Dynamic layouts and conditional rendering are now straightforward because we’re building our UIs with programming languages that have control structures and branching logic.&lt;/li&gt;
&lt;li&gt;It’s easier to &lt;code&gt;grep&lt;/code&gt; through Swift/Kotlin code than through XML.&lt;/li&gt;
&lt;li&gt;We can more uniformly refactor our code using the same tools that apply to the rest of the programming language.&lt;/li&gt;
&lt;li&gt;The code diffs in PRs are easier to understand.&lt;/li&gt;
&lt;li&gt;Since our UI elements are built with actual data structures (i.e. functions, classes, structs) as opposed to markup language, we have the possibility of doing unit tests on our Views as well (not available for Jetpack Compose at the time of writing this blog post).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course, there are always some drawbacks to be aware of. It’s worth mentioning that both SwiftUI and Jetpack Compose are only 2-3 years old. A short research on their drawbacks will reveal the lack of documentation, smaller community, some performance issues (for example Android’s lazy columns), not all components from the previous frameworks are supported, and there are limitations when it comes to building more complex UIs. Though they are still evolving and improving, be mindful of the pain points if you decide to start working with them today.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example UI
&lt;/h2&gt;

&lt;p&gt;Let’s look at how the same example UI is built in the declarative approach for both iOS and Android:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabevkardb0amesq5js7g.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fabevkardb0amesq5js7g.jpeg" alt="An example UI on both iOS and Android platforms" width="800" height="390"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;iOS (SwiftUI):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;//: An example Modal UI built with SwiftUI&lt;/span&gt;

&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;SwiftUI&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;PlaygroundSupport&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;@State&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;emailAddress&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;VStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="kt"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;systemName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"envelope.fill"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;largeTitle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Sign up to our newsletter!"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;font&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fontWeight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bold&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Since you love our content so much, why not get them every day in your inbox?"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multilineTextAlignment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;center&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;secondary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="kt"&gt;HStack&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;spacing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"john@doe.xyz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;$emailAddress&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;leading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;border&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kt"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="c1"&gt;// Handle onClick logic&lt;/span&gt;
                &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Subscribe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;foregroundColor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;}&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;vertical&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;black&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// Present the view controller in the Live View window&lt;/span&gt;
&lt;span class="kt"&gt;PlaygroundPage&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setLiveView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;a href="https://gist.github.com/nikolovlazar/4cbfa053b0d866f9e7a47ee51157357a" rel="noopener noreferrer"&gt;View iOS gist here&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Android (Jetpack Compose):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Composable&lt;/span&gt;
&lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;Modal&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;horizontalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterHorizontally&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;40&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;Icon&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;Icons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Filled&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;tint&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;52&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;246&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;// similar to the iOS one&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;size&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Sign up to our newsletter!"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;fontWeight&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FontWeight&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fontSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="s"&gt;"Since you love our content so much, why not get them every day in your inbox?"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;textAlign&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextAlign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Center&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;color&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Gray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;fontSize&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nc"&gt;Row&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;verticalAlignment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Alignment&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CenterVertically&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;height&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;TextField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;onValueChange&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
                &lt;span class="n"&gt;placeholder&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"john@doe.xyz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TextFieldDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textFieldColors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;White&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;border&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BorderStroke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;background&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;White&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nc"&gt;Button&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;onClick&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* handle onClick logic */&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="n"&gt;colors&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ButtonDefaults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buttonColors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="n"&gt;backgroundColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Black&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;contentColor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;White&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;contentPadding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;PaddingValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;horizontal&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;vertical&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;shape&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RoundedCornerShape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;modifier&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Modifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fillMaxHeight&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Subscribe"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(&lt;a href="https://gist.github.com/nikolovlazar/93f786e34cbf62d429908d0dd6c00d60" rel="noopener noreferrer"&gt;View Android gist here&lt;/a&gt;)&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The declarative approach of building UIs brings us a ton of benefits and eliminates a lot of the “pain points” we had in the imperative approach, but it also introduces new ones. Building UIs requires a lot less time with the declarative approach, and a lot less code. It’s also easier to achieve dynamic layouts and conditional rendering.&lt;/p&gt;

&lt;p&gt;Since the native platforms are taking this approach, it also sets up the whole ecosystem for creating new frameworks on top of the native ones that will bring a lot more features and possibilities. It might be a bit early to call them “the industry standard” yet, but the mobile development world is about to get even more productive.&lt;/p&gt;

</description>
      <category>deno</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Measuring application performance in Swift using transactions</title>
      <dc:creator>Lazar Nikolov</dc:creator>
      <pubDate>Tue, 22 Nov 2022 21:43:00 +0000</pubDate>
      <link>https://forem.com/sentry/measuring-application-performance-in-swift-using-transactions-22dp</link>
      <guid>https://forem.com/sentry/measuring-application-performance-in-swift-using-transactions-22dp</guid>
      <description>&lt;p&gt;So you're building a mobile app that’s performing big data requests; or crunching big data. But now you're asking yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How will my app perform in production?&lt;/li&gt;
&lt;li&gt;How will it perform on a lower-tier phone?&lt;/li&gt;
&lt;li&gt;Is there a scenario where the function's execution time is unbearably long?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Sentry’s &lt;a href="https://docs.sentry.io/platforms/apple/guides/ios/performance/instrumentation/custom-instrumentation/?utm_medium=website&amp;amp;utm_source=blog&amp;amp;utm_campaign=mobile-swift&amp;amp;utm_content=&amp;amp;utm_term=" rel="noopener noreferrer"&gt;Custom Instrumentation&lt;/a&gt; you can keep an eye on those big data-handling functions. Let’s see how you can implement them in your Storyboard and SwiftUI projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;First, you need to setup &lt;a href="https://docs.sentry.io/product/performance/?utm_medium=website&amp;amp;utm_source=blog&amp;amp;utm_campaign=mobile-swift&amp;amp;utm_content=&amp;amp;utm_term=" rel="noopener noreferrer"&gt;performance monitoring&lt;/a&gt;. With performance monitoring, Sentry tracks application performance, measures metrics like throughput and latency, and displays the impact of errors across multiple services. &lt;/p&gt;

&lt;p&gt;To get your app setup with performance monitoring, you need to configure the traces sample rate in your &lt;code&gt;Swift.UI.App&lt;/code&gt; file's &lt;code&gt;init()&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Sentry&lt;/span&gt;

&lt;span class="c1"&gt;// This should be in your SwiftUI.App file's init() method&lt;/span&gt;
&lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dsn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"[YOUR_DSN_HERE]"&lt;/span&gt;

  &lt;span class="c1"&gt;// Example uniform sample rate: capture 100% of transactions&lt;/span&gt;
  &lt;span class="c1"&gt;// In Production you will probably want a smaller number such as 0.5 for 50%&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracesSampleRate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;

  &lt;span class="c1"&gt;// OR if you prefer, determine traces sample rate based on the&lt;/span&gt;
  &lt;span class="c1"&gt;// sampling context&lt;/span&gt;
  &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracesSampler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
    &lt;span class="c1"&gt;// Don't miss any transactions for VIP users &lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;?[&lt;/span&gt;&lt;span class="s"&gt;"vip"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Bool&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt; 
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; 
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mf"&gt;0.25&lt;/span&gt; &lt;span class="c1"&gt;// 25% for everything else&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementing custom transactions
&lt;/h2&gt;

&lt;p&gt;Now that you’ve got performance monitoring enabled, you can start setting up custom transactions in the functions you’d like to measure. The measurement starts when you start a new transaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// don't forget to import Sentry's SDK at the top&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Sentry&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;syncPersonDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="c1"&gt;// Give the transaction a descriptive name&lt;/span&gt;
    &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"transaction-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Give the operation a descriptive name&lt;/span&gt;
    &lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"transaction-operation"&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="c1"&gt;// perform the operation&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For example, let’s say you want to measure how long it takes to sync a database of all people within a company; the “person database”. The name and operation could be &lt;code&gt;"Sync person database"&lt;/code&gt; and &lt;code&gt;"data.update"&lt;/code&gt; respectfully.&lt;/p&gt;

&lt;p&gt;When you’re done with the operation you want to measure, you can call the &lt;code&gt;finish()&lt;/code&gt; method of the transaction. Finishing the transaction will stop the measurement and send it to your Sentry project.&lt;/p&gt;

&lt;p&gt;The final structure of your function should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// don't forget to import Sentry's SDK at the top&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Sentry&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;syncPersonDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sync person database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// give it a name&lt;/span&gt;
    &lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"data.update"&lt;/span&gt; &lt;span class="c1"&gt;// and a name for the operation&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="c1"&gt;// perform the operation&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;

  &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// stop the measurement and report it to Sentry&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Monitoring the performance
&lt;/h2&gt;

&lt;p&gt;So far you’ve set up the performance measuring mechanism of your &lt;code&gt;syncPersonDatabase&lt;/code&gt; function. Now you run your app...&lt;/p&gt;

&lt;p&gt;You run it again...&lt;/p&gt;

&lt;p&gt;Maybe you run it one more time for good measure...&lt;/p&gt;

&lt;p&gt;Okay, that should have kicked off a transaction. You visit your Sentry dashboard and open the Performance tab and see the new custom transaction appear at the bottom:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsaxyh11b9thrpyqyyiw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxsaxyh11b9thrpyqyyiw.png" alt="Transactions Table" width="800" height="85"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the transaction and then into the “Suspect Span” found in the second table, you will see a detailed representation of the span operation:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m7rl1b67wi5571g842m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4m7rl1b67wi5571g842m.png" alt="Span Summary" width="800" height="404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's break this view down a bit.&lt;/p&gt;

&lt;p&gt;The first thing that you’ll see is the Self Time Breakdown chart. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1k7idnl9j9n62lp3ynb0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1k7idnl9j9n62lp3ynb0.png" alt="Span Summary Breakdown" width="800" height="130"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This chart visualises the p50, p75, p95 and p99 metrics, which are called “latency percentiles”. p50 means that 50% of the function executions are faster than the p50 value (let’s say 0.47ms). To learn more about the latency percentiles, check out the &lt;a href="https://docs.sentry.io/product/performance/metrics/#latency" rel="noopener noreferrer"&gt;Latency section&lt;/a&gt; in the Metrics documentation. &lt;/p&gt;

&lt;p&gt;In the top right corner you can see the Self Time Percentiles widgets. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9h8359y6hrv3sfd1ivxl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9h8359y6hrv3sfd1ivxl.png" alt="Span Summary Self Time Percentiles" width="633" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you don’t want to look at the chart, these values will give you an insight on the average “p” values.&lt;/p&gt;

&lt;p&gt;Below the chart is the events table. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xp3eu4yaoef77ufnabw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5xp3eu4yaoef77ufnabw.png" alt="Span Summary Events Table" width="800" height="205"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here if you notice an event that took longer than you anticipated, you can click on it to get more details about it, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The device that the user was using when the transaction occurred&lt;/li&gt;
&lt;li&gt;The device’s memory at the time of the transaction&lt;/li&gt;
&lt;li&gt;How long the user had been using the app prior to the transaction&lt;/li&gt;
&lt;li&gt;The version of the app&lt;/li&gt;
&lt;li&gt;The breadcrumbs (bits of events and interactions that led up to that transaction)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you enough information on the context and environment to be able to discover potential ways to refactor the function to improve its performance. And, you can get even more granular if your function has multiple steps of execution. You can measure each steps individually and still keep them under the umbrella of the main function transaction. &lt;/p&gt;

&lt;p&gt;Enter: &lt;strong&gt;child spans&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More granular measurement with child spans
&lt;/h2&gt;

&lt;p&gt;Child spans are like mini transactions that are attached to the main transaction. And child spans can have their own child spans as well. This feature allows you to measure your function’s performance in a greater detail, by starting a child span for every step of the function.&lt;/p&gt;

&lt;p&gt;Let’s say your function performs the following steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="p"&gt;1.&lt;/span&gt; Gather changed person properties
&lt;span class="p"&gt;2.&lt;/span&gt; Pass each of them through a validation mechanism
&lt;span class="p"&gt;3.&lt;/span&gt; Perform the updates to the database
&lt;span class="p"&gt;4.&lt;/span&gt; Save the current timestamp as the "lastUpdated"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can measure each step individually using child spans like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="c1"&gt;// don't forget to import Sentry's SDK at the top&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Sentry&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;syncPersonDatabase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sync person database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// give it a name&lt;/span&gt;
    &lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"data.update"&lt;/span&gt; &lt;span class="c1"&gt;// and a name for the operation&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// span the first child&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;gatherSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"gather-changed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// perform the gather operation&lt;/span&gt;
  &lt;span class="n"&gt;gatherSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// span the second child&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;validationSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"validate-changed"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// perform the validation&lt;/span&gt;
  &lt;span class="n"&gt;validationSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// span the third child&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;updateSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"update-database"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// perform the update&lt;/span&gt;
  &lt;span class="n"&gt;updateSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="c1"&gt;// span the last child&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;timestampSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"update-timestamp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="c1"&gt;// perform the timestamp update&lt;/span&gt;
  &lt;span class="n"&gt;timestampSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember: Only finished spans will be sent along with the transaction. So you need to make sure that you’re finishing each child span before finishing the main transaction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitoring the performance of child spans
&lt;/h2&gt;

&lt;p&gt;Alright, you have set up child spans and you're ready to run your app again with the latest changes...&lt;/p&gt;

&lt;p&gt;You run it again...&lt;/p&gt;

&lt;p&gt;Maybe you run it one more time for good measure...&lt;/p&gt;

&lt;p&gt;Done! You head back to your Sentry dashboard and notice that the Suspect Spans table provides more information now. You can see the child spans, and how long they took to execute.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzg6kb5di7clgbnk4htl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frzg6kb5di7clgbnk4htl.png" alt="Suspect Spans Table" width="800" height="203"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clicking on the “View All Spans” button you can see and sort all of them.&lt;/p&gt;

&lt;p&gt;But you realize that your function has conditional steps that don’t always execute. Or maybe you have dynamically generated child spans whose name contains a unique id. To see the actual order of the child spans, you have to pick a specific event from the Events Table above and a new page will open with more details around that specific event.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnie7owzawf8r253wenc8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnie7owzawf8r253wenc8.png" alt="Event Details" width="800" height="458"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can see from the Gantt chart the order of execution of the child spans. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6ynm1wuayvao2l6cw44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy6ynm1wuayvao2l6cw44.png" alt="Event Details Gantt Chart" width="800" height="224"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can see that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;data.update&lt;/code&gt; encapsulates all child spans&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;gather-changed&lt;/code&gt; started executing first and took 16.02ms&lt;/li&gt;
&lt;li&gt;Then the &lt;code&gt;validate-changed&lt;/code&gt; ran for 5.64ms&lt;/li&gt;
&lt;li&gt;Then the &lt;code&gt;update-database&lt;/code&gt; took its 121.02ms to run&lt;/li&gt;
&lt;li&gt;And finally the &lt;code&gt;update-timestamp&lt;/code&gt; flashed for 5.63ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can identify which step contributes the most to the performance of your function with this. You can now turn our whole focus to improving the performance of your &lt;code&gt;update-database&lt;/code&gt; function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using a transaction in multiple functions
&lt;/h2&gt;

&lt;p&gt;Now that you have a hang of measuring straightforward functions, you're ready to tackle something a bit more complex. You realize that you want to measure performance on multiple nested functions. You do not need to pass the transaction as an argument. Instead, you can bind the transaction to the current scope:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sync person database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// give it a name&lt;/span&gt;
  &lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"data.update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// and a name for the operation&lt;/span&gt;
  &lt;span class="nv"&gt;bindToScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// bind the transaction to the curent scope&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can access this transaction without having to pass it as an argument while you're in the current scope. You could have a function &lt;code&gt;gatherChangedProperties&lt;/code&gt;, that calls another function &lt;code&gt;filterProperties&lt;/code&gt;. And to gain access to the transaction within the &lt;code&gt;filterProperties&lt;/code&gt;, you can directly reference &lt;code&gt;SentrySDK.span&lt;/code&gt;, and if no transaction exists yet, you can create it. Your structure should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;transaction&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nv"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"Sync person database"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// give it a name&lt;/span&gt;
  &lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"data.update"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// and a name for the operation&lt;/span&gt;
  &lt;span class="nv"&gt;bindToScope&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="c1"&gt;// bind the transaction to the curent scope&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;properties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;gatherChangedProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;

&lt;span class="n"&gt;transaction&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;gatherChangedProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;filteredProperties&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;filterProperties&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;filterProperties&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// obtain the transaction&lt;/span&gt;
  &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;span&lt;/span&gt;

  &lt;span class="c1"&gt;// if non existing, recreate it&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;span&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;SentrySDK&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTransaction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// use it like a normal transaction&lt;/span&gt;
  &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;filterSpan&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;span&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;operation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"filter-properties"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;//...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Recap
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;You can now create custom transactions to measure long running functions in your app with &lt;code&gt;SentrySDK.startTransaction(...)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You can measure more granularly by starting child spans to our transaction for each logical step in our function with &lt;code&gt;transaction.startChild(...)&lt;/code&gt; to zero in on the slowest part of the function and improve it. &lt;/li&gt;
&lt;li&gt;If you’re measuring a more complex function with multiple branches, you can bind the transaction to the current scope by setting the &lt;code&gt;bindToScope&lt;/code&gt; property to &lt;code&gt;true&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Time to measure all the things! 🙌&lt;/p&gt;

</description>
      <category>swift</category>
      <category>sentry</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Performance Monitoring and more updates to Sentry for Electron</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Fri, 25 Mar 2022 22:18:22 +0000</pubDate>
      <link>https://forem.com/sentry/performance-monitoring-and-more-updates-to-sentry-for-electron-17ni</link>
      <guid>https://forem.com/sentry/performance-monitoring-and-more-updates-to-sentry-for-electron-17ni</guid>
      <description>&lt;p&gt;For those who aren’t that familiar with it, Electron is an open-source framework that allows developers to build cross-platform desktop applications in JavaScript. Some of the most popular desktop applications like VS Code, Slack, Discord, and Atom, are all built in Electron.&lt;/p&gt;

&lt;p&gt;While Electron makes it easy to build cross-platform applications, maintaining them is a whole other thing. You need to be aware of OS-specific challenges, the machines your applications are running on, and their configurations. &lt;a href="https://docs.sentry.io/platforms/javascript/guides/electron/"&gt;Version 3.0 of our Electron SDK&lt;/a&gt; helps you get a handle on all these unknowns with more context on every error, insight into when a release starts to degrade, and enable &lt;a href="https://sentry.io/features/distributed-tracing/"&gt;distributed tracing&lt;/a&gt; to easily see where the most time in a transaction is spent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Install
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Using&lt;/span&gt; &lt;span class="nx"&gt;yarn&lt;/span&gt;
&lt;span class="nx"&gt;yarn&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;sentry&lt;/span&gt;&lt;span class="sr"&gt;/electro&lt;/span&gt;&lt;span class="err"&gt;n
&lt;/span&gt;
&lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="nx"&gt;Using&lt;/span&gt; &lt;span class="nx"&gt;npm&lt;/span&gt;
&lt;span class="nx"&gt;npm&lt;/span&gt; &lt;span class="nx"&gt;install&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;sentry&lt;/span&gt;&lt;span class="sr"&gt;/electro&lt;/span&gt;&lt;span class="err"&gt;n
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Configure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;Sentry&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@sentry/electron&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;Sentry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;init&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;dsn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://examplePublicKey@o0.ingest.sentry.io/0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;You need to call init in the main process and every renderer process you spawn.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;With V3.0 of Sentry for Electron, you can automatically monitor application performance without any additional configuration. As well as, track crash-free sessions by release, and unlock more information for every issue like CPU details, display data, memory status, language details, and more. Check out the release notes &lt;a href="https://github.com/getsentry/sentry-electron/releases"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron Performance Monitoring
&lt;/h2&gt;

&lt;p&gt;Errors are only half the story. Electron developers also need to know if their app responds quickly to user input on any machine or operating system. This type of visibility helps you see if objects load quickly when called—and, if not, then provide you with tools to solve what’s urgent, faster.&lt;/p&gt;

&lt;p&gt;Sentry’s performance monitoring for Electron surfaces conditions that cause bottlenecks or latency issues for Electron renderer processes, along with a breakdown of operations within each transaction. So you can easily see which span is taking the longest and save yourself the frustration of clicking into every trace and span by getting operation details and insights on a silver platter.&lt;/p&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/schaefferarnold11/embed/bGaqqWO?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron Release Health
&lt;/h2&gt;

&lt;p&gt;Version 3.0 automatically captures session data to help you better understand how each app release is performing. Sentry calculates crash-free sessions, crash-free users, and version adoption by release. If a release inexplicably tanks your crash-free users, you can see the moment a release starts to degrade and get a list of new issues and failed transactions. Plus, we also provide a list of commit authors so you can skip &lt;code&gt;git blame&lt;/code&gt; and just Slack your teammates who are best suited to fix the problem (that they created).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--R2qukM2R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eknud5rc5kaxb20antl7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R2qukM2R--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eknud5rc5kaxb20antl7.png" alt="Image description" width="880" height="669"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But hey, don’t know why there was a dramatic dip in crash-free sessions? This is fine. Click “Open in Discover” from any release details page; then, Sentry will automatically build and run a query for events by release to help you get to the root of any problem.&lt;/p&gt;

&lt;p&gt;Explore whether multiple conditions contribute to an issue: does this only happen on a specific machine or operating system? Or does it affect all Linux machines? Or all Windows 11 users? Answer it quickly in Discover.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--2R1i-umM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilj9sly3bx5cst4xzs4z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--2R1i-umM--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilj9sly3bx5cst4xzs4z.png" alt="Image description" width="880" height="618"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Discover also includes pre-built searches that help answer common questions about all your events, unique errors, and clients. And if you don’t like these thoughtful, hand-crafted, pre-built searches, you can modify them and save them to a custom dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  Electron Error Monitoring
&lt;/h2&gt;

&lt;p&gt;Understanding issues across various devices with access to different metadata, latencies, network issues, and upgrade cadences is hard and getting exponentially harder. Sentry for Electron 3.0 helps developers see what actually matters, solve issues faster with richer context, and learn continuously about their applications.&lt;/p&gt;

&lt;p&gt;Not only will you see the user impact of each issue, but you’ll also get all the information you need to know about the machines the error happened on, like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU (processor count, machine architecture)&lt;/li&gt;
&lt;li&gt;Screen (density, resolution, height/width)&lt;/li&gt;
&lt;li&gt;Memory&lt;/li&gt;
&lt;li&gt;Language Details (locale details)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;iframe height="600" src="https://codepen.io/schaefferarnold11/embed/GRyWWEd?height=600&amp;amp;default-tab=result&amp;amp;embed-version=2"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  To wrap it up
&lt;/h2&gt;

&lt;p&gt;Developers need visibility into when a user writes to disk, fetches media, loads an object in a given viewport—or, simply when a user tries to launch the app. Without this data, untreated performance problems and serious errors inevitably result in a poor user experience.&lt;/p&gt;

&lt;p&gt;Update your Electron SDK to immediately know everything about the hardware, OS, screen orientation, network conditions, and device language settings for every issue caught and reported by Sentry. Solve issues with context and confidence and save time by not looking for obscure machines to recreate the problem. With the latest Electron SDK and Sentry, you can easily uncover the who, what, when, where, and why behind every issue.&lt;/p&gt;

&lt;p&gt;Get started with &lt;a href="https://sentry.io/for/electron/"&gt;Sentry for Electron&lt;/a&gt; and drop us a line on &lt;a href="https://github.com/getsentry/sentry-javascript"&gt;GitHub&lt;/a&gt;, &lt;a href="https://twitter.com/getsentry?ref_src=twsrc%5Egoogle%7Ctwcamp%5Eserp%7Ctwgr%5Eauthor"&gt;Twitter&lt;/a&gt;, or our &lt;a href="https://discord.com/invite/sentry"&gt;Discord&lt;/a&gt;. And if you’re new to Sentry, you can try it for &lt;a href="https://sentry.io/orgredirect/try-business/"&gt;free&lt;/a&gt; today or write to &lt;a href="mailto:sales@sentry.io"&gt;sales@sentry.io&lt;/a&gt; to get started.&lt;/p&gt;

</description>
      <category>electron</category>
      <category>javascript</category>
      <category>performance</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Deprecation From U2F API to WebAuthn</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Wed, 19 Jan 2022 01:48:03 +0000</pubDate>
      <link>https://forem.com/sentry/deprecation-from-u2f-api-to-webauthn-1fkg</link>
      <guid>https://forem.com/sentry/deprecation-from-u2f-api-to-webauthn-1fkg</guid>
      <description>&lt;p&gt;By: &lt;a href="https://blog.sentry.io/authors/richard-ma"&gt;Richard Ma&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you’re using the U2F API for registration and authentication of your U2F devices, you will notice a dire situation is coming soon: Google Chrome will no longer support U2F API after February 2022:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s---Ln37zk2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4qpfv4rugc9b6mmpicv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s---Ln37zk2--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y4qpfv4rugc9b6mmpicv.png" alt="Image description" width="512" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For anyone using U2F API in their web apps, like us at Sentry, their users who had 2FA enabled with U2F devices would not be able to sign in. To remedy this, there’s a shiny new specification written by the World Wide Web Consortium (W3C) and the Fast IDentity Online Alliance (FIDO) that will solve all our problems.&lt;/p&gt;

&lt;p&gt;In this blog post, we will go into the weeds on migrating from U2F API to WebAuthn.&lt;/p&gt;

&lt;h2&gt;
  
  
  WebAuthn
&lt;/h2&gt;

&lt;p&gt;WebAuthn is an API that allows web services to seamlessly integrate strong authentication into applications. With WebAuthn, web services can offer the user a choice of authenticators, such as security keys (Yubikeys, Titan Keys, for example) or built-in platform authenticators (biometric readers). In addition, it is supported by all the leading browsers — including Safari, which U2F API was not — and web platforms, which standardizes the integration of strong authentication. You can read more on &lt;a href="https://webauthn.io/"&gt;WebAuthn’s&lt;/a&gt; website.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating to WebAuthn From U2F API
&lt;/h2&gt;

&lt;p&gt;Now, here’s the part you’re all here for, the migration to WebAuthn. Let’s break this down into two main parts:&lt;/p&gt;

&lt;p&gt;Part 1: Authenticating existing U2F and new WebAuthn devices with WebAuthn&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Generating the challenge and state&lt;/li&gt;
&lt;li&gt;Step 2: Creating PublicKeyCredential data&lt;/li&gt;
&lt;li&gt;Step 3: Verifying the device&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part 2: Registering new devices with WebAuthn&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Step 1: Generating the PublicKeyCredentialRpEntity and state&lt;/li&gt;
&lt;li&gt;Step 2: Creating PublicKeyCredential data&lt;/li&gt;
&lt;li&gt;Step 3: Registering the device&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 1: Authentication
&lt;/h2&gt;

&lt;p&gt;Let’s start with authentication so existing users can continue to log in. This is also important because newly registered WebAuthn devices can’t log in without a working WebAuthn login.&lt;/p&gt;

&lt;p&gt;To understand the authentication flow, we can look at the following diagrams:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--q7mdm5hN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5obxfn5utpslmyby3hp.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--q7mdm5hN--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5obxfn5utpslmyby3hp.jpg" alt="Image description" width="880" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Source left][12] [Source right][13]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We’ll be using Python for the backend APIs. The &lt;a href="https://github.com/Yubico/python-u2flib-server"&gt;U2F API&lt;/a&gt; sequence (left) is very similar to the &lt;a href="https://github.com/Yubico/python-fido2"&gt;WebAuthn&lt;/a&gt; sequence (on the right). We simply have to replace three API calls: &lt;code&gt;u2f.start_authentication()&lt;/code&gt; and &lt;code&gt;u2f.finish_authentication()&lt;/code&gt; in the backend, and &lt;code&gt;u2f.sign()&lt;/code&gt; in the frontend.&lt;/p&gt;

&lt;p&gt;Let’s start with &lt;code&gt;u2f.start_authentication()&lt;/code&gt;, which takes in the browser’s application ID and the currently registered devices.&lt;/p&gt;

&lt;p&gt;The U2F API authentication process starts with the backend generating a challenge, an example of which is shown below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
  "challenge": "VwmGI-4…",
  "registeredKeys": [
      {
          "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
          "keyHandle": "cxSl4oQ…",
          "publicKey": "BP4Q8MR…",
          "transports": [
                "usb"
          ],
          "version": "U2F_V2"
      }
  ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This challenge is sent to the browser where &lt;code&gt;u2f.sign()&lt;/code&gt; takes the challenge as an input and returns a promise that is the result of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verifying the application identity of the caller&lt;/li&gt;
&lt;li&gt;creating a client data object and using the client data&lt;/li&gt;
&lt;li&gt;the application ID&lt;/li&gt;
&lt;li&gt;the key handle&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a raw authentication request message and sends it to the U2F device. The result of the promise should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "keyHandle": "cxSl4oQ…",
    "clientData": "eyJ0eXA…",
    "signatureData": "AQAAAQ4…"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the result of the promise is sent to the server, we call &lt;code&gt;U2f.complete_authentication()&lt;/code&gt; with the following two parameters: the original challenge data and the newly generated client data object passed in. This method will verify the device with the parameters and return the device info if it succeeded. From there, the server can allow the user to pass through the 2FA process.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Generating the challenge and state
&lt;/h3&gt;

&lt;p&gt;To start the migration process, let’s first replace &lt;code&gt;u2f.start_authentication()&lt;/code&gt; with its counterpart. The data types that the &lt;a href="https://github.com/Yubico/python-fido2"&gt;WebAuthn API&lt;/a&gt; takes are not quite the same ones used in U2F API. In fact, one of the main pain points was converting the necessary fields into the correct data type.&lt;/p&gt;

&lt;p&gt;We want to authenticate users on legacy U2F API and WebAuthn, so we will create an authentication server first. The following will create an authentication server using WebAuthn that is backwards compatible with U2F API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;webauthn_authentication_server = U2FFido2Server(
    app_id=u2f_app_id, 
    rp={
        "id": “sentry-webauthn.io”, 
        "name": "Sentry with WebAuthn"}
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;app_id&lt;/code&gt; will be the same value as before. The &lt;code&gt;rp&lt;/code&gt;, or Relying Party, is an object that contains an ID, which is the hostname of the URL, and the name of your Relying Party.&lt;/p&gt;

&lt;p&gt;Next, we need to generate a list of credentials, which is the same as the list of devices for U2F API. Keep in mind that the list of credentials will contain both WebAuthn and U2F API registered devices and that list needs to be manipulated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;credentials = []

for device in self.get_u2f_devices():
    if type(device) == AuthenticatorData:
        credentials.append(device.credential_data)
    else:
        credentials.append(create_credential_object(device))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The devices that are registered with WebAuthn have the type AuthenticatorData. For devices registered with U2F API, we need to create an AttestedCredentialData object for them to be compatible with WebAuthn. The following is the function we wrote that decodes the necessary parameters and creates the credential data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def create_credential_object(registeredKey):
    return base.AttestedCredentialData.from_ctap1(
        websafe_decode(registeredKey["keyHandle"]),
        websafe_decode(registeredKey["publicKey"]),
    )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;[Source of function][16]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;With that, we can begin the registration process by calling &lt;a href="https://developers.yubico.com/WebAuthn/"&gt;&lt;code&gt;register_begin()&lt;/code&gt;&lt;/a&gt; on the WebAuthn server that we created earlier, with credentials as its parameter. This will return a challenge and state.&lt;/p&gt;

&lt;p&gt;The challenge is needed for the browser to perform authentication, but we will only use the PublicKey object within the challenge. In addition, you should store the state in your sessions, as it will be needed later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;challenge, state = self.webauthn_authentication_server.authenticate_begin(
    credentials=credentials
)
request.session["webauthn_authentication_state"] = state
return ActivationChallengeResult(
    challenge=cbor.encode(challenge["publicKey"])
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We also encoded the challenge using the [FIDO2 CBOR][18] library, as we will be sending it to the frontend using JSON, which does not handle binary representation well on its own. On the frontend, we convert the JSON string back into a byte array and decode it to return the challenge to its original form.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Creating PublicKeyCredential for authentication
&lt;/h3&gt;

&lt;p&gt;To replace &lt;code&gt;u2f.sign()&lt;/code&gt;, we can call its WebAuthn equivalent &lt;code&gt;navigator.credentials.get()&lt;/code&gt; with the challenge data. This library is now native to modern browsers, so don’t worry about importing any libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const challengeArray = base64urlToBuffer(
    challengeData.webAuthnAuthenticationData
);
const challenge = cbor.decodeFirst(challengeArray);

challenge.then(data =&amp;gt; {
    webAuthnSignIn(data);
}).catch(err =&amp;gt; {
    const failure = 'DEVICE_ERROR';
    Sentry.captureException(err);
    this.setState({
        deviceFailure: failure,
        hasBeenTapped: false,
    });
});

function webAuthnSignIn(publicKeyCredentialRequestOptions) {
    return navigator.credentials.get({
        publicKey: publicKeyCredentialRequestOptions,
    }).then(data =&amp;gt; {
        // Send to backend
    })
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the promise is resolved after calling &lt;code&gt;navigator.credentials.get()&lt;/code&gt;, we need to send the appropriate data to the backend to finish authentication. To convert the PublicKeyCredential that was obtained from &lt;code&gt;navigator.credentials.get()&lt;/code&gt;, we can run it through the following function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;getU2FResponse(data) {
    if (data.response) {
        const authenticatorData = {
          keyHandle: data.id,
          clientData: bufferToBase64url(data.response.clientDataJSON),
          signatureData: bufferToBase64url(data.response.signature),
          authenticatorData: bufferToBase64url(data.response.authenticatorData),
        };
        return JSON.stringify(authenticatorData);
    }

    return JSON.stringify(data);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Verifying the device
&lt;/h3&gt;

&lt;p&gt;For the final step, we can pass the original challenge and this new response to the backend. We need to create a list of credentials to validate the device, then call &lt;code&gt;authenticate_complete&lt;/code&gt; on the authentication server that was made earlier with the following parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;state: the value which we stored in session from start_authentication&lt;/li&gt;
&lt;li&gt;credentials: list which we just generated&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A websafe_decode for the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;credential_id&lt;/strong&gt;: a “keyHandle” of the response object&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;client_data&lt;/strong&gt;: a “clientData” of the response object passed through fido2.client.ClientData&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;auth_data&lt;/strong&gt;: an “authenticatorData” of the response object passed through fido2.ctap2.authenticatorData&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;signature&lt;/strong&gt;: a “signatureData” of the response object
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;self.webauthn_authentication_server.authenticate_complete(
    state=request.session["webauthn_authentication_state"],
    credentials=credentials,
    credential_id=websafe_decode(response["keyHandle"]),
    client_data=ClientData(websafe_decode(response["clientData"])),
    auth_data=AuthenticatorData(websafe_decode(response["authenticatorData"])),
    signature=websafe_decode(response["signatureData"]),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If this function returns true, you are now fully authenticated and good to go!&lt;/p&gt;

&lt;p&gt;For our deployment, this feature was behind a flag to manage the rollout and for error monitoring. (We recommend using Sentry 😉.) We deployed this feature independently from registration because the area of effect is limited in the event of an incident.&lt;/p&gt;

&lt;p&gt;Congrats! The authentication part of the migration is finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Registration
&lt;/h2&gt;

&lt;p&gt;Similar to authentication, first, let’s take a look at the flow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--4GQvOu6v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n00ro6pwq2x4k9j665yb.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--4GQvOu6v--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n00ro6pwq2x4k9j665yb.jpeg" alt="Image description" width="880" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;[Source left][12] [Source right][20]&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The flow of registration is almost identical to that of the authentication process. The three components we need to deprecate in order to migrate to WebAuthn are &lt;code&gt;u2f.begin_registration()&lt;/code&gt; and &lt;code&gt;u2f.complete_registration()&lt;/code&gt; in the backend, and &lt;code&gt;u2f.register()&lt;/code&gt; in the frontend.&lt;/p&gt;

&lt;p&gt;Once again, we will start with &lt;code&gt;u2f.begin_registration()&lt;/code&gt;. This API call takes in the u2f application ID and list of registered devices. This results in the following data being sent to the browser to begin the registration process:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "appId": "https://your-webauthn-app/2fa/u2fappid.jso",
    "registerRequests": [
        {
            "challenge": "uexgFSl…",
            "version": "U2F_V2"
        }
    ],
    "registeredKeys": []
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar to &lt;code&gt;u2f.sign()&lt;/code&gt;, &lt;code&gt;u2f.register()&lt;/code&gt; will take the previously generated results and return a promise that will look like the following if the device is registrable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "registrationData": "BQQ1xlC…",
    "version": "U2F_V2",
    "challenge": "Jkh_Tfo…",
    "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
    "clientData": "eyJ0eXA…"
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Along with the previously generated challenge, this result is sent to the backend where &lt;code&gt;u2f.complete_registration()&lt;/code&gt; takes in both parameters and generates the following data object:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "appId": "https://your-webauthn-app.io/2fa/u2fappid.json",
    "keyHandle": "SnllNGC…",
    "publicKey": "BIs-gsW…",
    "transports": [
        "usb"
    ],
    "version": "U2F_V2"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can save this dictionary and name of the device. They will be used for authentication.&lt;/p&gt;

&lt;p&gt;Just like before, let’s replace &lt;code&gt;u2f.begin_registration()&lt;/code&gt; with its counterpart. We need to create a FIDO2Server and import it from &lt;code&gt;fido2.server&lt;/code&gt;. We don’t need to make it backward compatible, as all new devices will be registered with WebAuthn.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Generating the PublicKeyCredentialRpEntity and state
&lt;/h3&gt;

&lt;p&gt;We start with importing the &lt;a href="https://github.com/Yubico/python-fido2"&gt;&lt;code&gt;fido2.webauthn&lt;/code&gt; library&lt;/a&gt; to create a PublicKeyCredentialRpEntity. To create the entity, we need to pass in the Relying Party’s ID and name. With the entity, we pass it into Fido2Server to set things up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from fido2.server import Fido2Server
from fido2.webauthn import PublicKeyCredentialRpEntity

rp = PublicKeyCredentialRpEntity(rp_id, "Sentry")
webauthn_registration_server = Fido2Server(rp) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the server is created, we can optionally pass in a list of registered devices to avoid duplicate registrations.&lt;/p&gt;

&lt;p&gt;Next, we call &lt;code&gt;register_begin()&lt;/code&gt; with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;user&lt;/strong&gt;: dictionary with the user’s id, name, and display name&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;credentials&lt;/strong&gt;: the list we just generated&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;user_verification&lt;/strong&gt;: normally defaulted to discouraged&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should get a result similar to this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  "publicKey": {
    "authenticatorSelection": {
      "userVerification": &amp;lt;UserVerificationRequirement.DISCOURAGED: "discouraged"&amp;gt;},
      "challenge": b"\xe9)#\x86\xfa.\xa9\x82r\x86\xf7\x15e\xb5m\xdc"
                   b"\x1dR\xc4\x1b\xdb\xab\x94\x88\xb8\x94\xf43"
                   b"b\x03\xab\n",
      "excludeCredentials": [],
      "pubKeyCredParams": [
        {"alg": -7,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;},
        {"alg": -8,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;},
        {"alg": -37,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;},
        {"alg": -257,
         "type": &amp;lt;PublicKeyCredentialType.PUBLIC_KEY: "public-key"&amp;gt;}],
      "rp": {"id": "&amp;lt;$YOUR_APP&amp;gt;",
      "name": "Sentry"},
      "user": {"displayName": "&amp;lt;$YOUR_NAME&amp;gt;",
      "id": b"\x00",
      "name": "&amp;lt;$YOUR_APP&amp;gt;"
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The registration data as seen above will be returned. Encode the registration data with the &lt;code&gt;cbor.encode()&lt;/code&gt; method and base64 encode that to a string.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;publicKeyCredentialCreate = cbor.encode(registration_data)
return b64encode(publicKeyCredentialCreate)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set the state for later use in the session.&lt;br&gt;
Interestingly, the data from WebAuthn is exactly the same from U2F API, despite looking different on first glance. WebAuthn sets the challenge in a byte array and the clientData in a COSE key object (which is a &lt;a href="https://www.jacobcasper.com/u2f2webauthn.html"&gt;CBOR map&lt;/a&gt;), while U2F API uses an encoded string.&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 2: Creating PublicKeyCredential for registration
&lt;/h3&gt;

&lt;p&gt;Once the registration data is received by the browser, we convert the string into a buffer and decode it with &lt;a href="https://github.com/hildjj/node-cbor/tree/main/packages/cbor-web"&gt;this library&lt;/a&gt;. This gives us the data that will be used as the input parameter of &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/CredentialsContainer/create"&gt;&lt;code&gt;navigator.credentials.create()&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;challenge, state = self.webauthn_authentication_server.authenticate_begin(
    credentials=credentials
)
request.session["webauthn_authentication_state"] = state

return ActivationChallengeResult(challenge=cbor.encode(challenge["publicKey"]))

webAuthnRegister(publicKey) {
    const promise = navigator.credentials.create({publicKey});
    this.submitU2fResponse(promise);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Registering the device
&lt;/h3&gt;

&lt;p&gt;We have reached the final step where we need to extract some data from the response from &lt;code&gt;navigator.credentials.create()&lt;/code&gt;. The following are needed for &lt;code&gt;register_complete()&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;state&lt;/strong&gt;: from user sessions, set earlier after &lt;code&gt;begin_registration()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;client_data&lt;/strong&gt;: from decoding the data’s cliendDataJSON and creating a ClientData Object with it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AttestationObject&lt;/strong&gt;: from decoding the data’s attestationObject and creating an AttestationObject Object with it
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;data = json.loads(response_data)
client_data = ClientData(
    websafe_decode(data["response"]["clientDataJSON"])
)
att_obj = base.AttestationObject(
    websafe_decode(data["response"]["attestationObject"])
)

binding = webauthn_registration_server.register_complete(
    state, client_data, att_obj
) 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ClientData should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "type": "webauthn.create",
    "challenge": "_Uas89Y…",
    "origin": "https://&amp;lt;$YOUR_APP&amp;gt;",
    "crossOrigin": false
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AttestationObject should look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AttestationObject(
    fmt: 'none', 
    auth_data: AuthenticatorData(
        rp_id_hash: h'74cb1ce…5',
        flags: 0x41, 
        counter: 281, 
        credential_data: AttestedCredentialData(
            aaguid: h'0000000…', 
            credential_id: h'63af2c9…', 
            public_key: {...}
        ), 
        att_statement: {}, 
        ep_attr: None, 
        large_blob_key: None
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Registered device data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AuthenticatorData(
    rp_id_hash: h'74cb1ce…',
    flags: 0x41, 
    counter: 281, 
    credential_data: AttestedCredentialData(
        aaguid: h'0000000…', 
        credential_id: h'63af2c9…', 
        public_key: {...}
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that, you can save the registered device data. The registration process is complete.&lt;br&gt;
Just like authentication, the deployment of this feature was behind a feature flag to manage the rollout. There were no database migrations needed as WebAuthn is backward compatible with U2F API.&lt;/p&gt;

&lt;h2&gt;
  
  
  That’s a wrap
&lt;/h2&gt;

&lt;p&gt;With that, WebAuthn should be set up and you can purge U2F API from your codebase. If you have made it this far, we hope that this guide was useful to you. With some planning, you will make it in time before Chrome locks out users from your application. All the best!&lt;/p&gt;

&lt;p&gt;—&lt;/p&gt;

&lt;p&gt;Everything we do at &lt;a href="//www.sentry.io"&gt;Sentry&lt;/a&gt; is built in the open. Find us on &lt;a href="https://github.com/getsentry/sentry-mobile"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>news</category>
    </item>
    <item>
      <title>Bytecode Transformations: The Android Gradle Plugin</title>
      <dc:creator>Rahul Chhabria</dc:creator>
      <pubDate>Tue, 14 Dec 2021 23:05:18 +0000</pubDate>
      <link>https://forem.com/sentry/bytecode-transformations-the-android-gradle-plugin-4l6o</link>
      <guid>https://forem.com/sentry/bytecode-transformations-the-android-gradle-plugin-4l6o</guid>
      <description>&lt;p&gt;By: &lt;a href="https://github.com/romtsn/"&gt;Roman Zavarnitsyn&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is the first part of a blog post series about bytecode transformations on Android. In this part we’ll cover different approaches to bytecode manipulation in Java as well as how to make it work with Android and the Android Gradle plugin. In the next two parts we’ll dive into the actual bytecode, bytecode instructions and how we can modify the bytecode and inject our own instructions, using &lt;a href="https://developer.android.com/jetpack/androidx/releases/room"&gt;Room&lt;/a&gt; as an example. In the last part we’ll see how we can test our transformations and how it can influence Gradle build speed.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Detecting the slow spots in your app without having to write a single line of code is an intriguing idea, but is also not that easy to implement. At Sentry we wanted to provide the capability for Android users to automatically measure the execution time of database queries because oftentimes they can become a hidden bottleneck for app performance. &lt;a href="https://developer.android.com/jetpack/androidx/releases/room"&gt;Room&lt;/a&gt; is a widely-adopted ORM solution built by Google and is a go-to library for persistence for the majority of the Android developers, so it was an obvious choice for us to start with.&lt;/p&gt;

&lt;p&gt;If we want database operations to show up in Sentry, we need to wrap them into special objects called Spans. In short, Spans are application events that have a start and end time and some metadata like operation name, description, etc. For example, this is how the performance breakdown looks for a popular open-source app &lt;a href="https://github.com/chrisbanes/tivi"&gt;tivi&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--cQo1BoWV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ja26ew7bbptsgaldx5bo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--cQo1BoWV--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ja26ew7bbptsgaldx5bo.png" alt="Image description" width="880" height="432"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the screenshot above, there’s a parent span &lt;code&gt;ui.screen.interaction&lt;/code&gt; that contains multiple child spans, for instance, the network request span with a &lt;code&gt;http.client&lt;/code&gt; operation, duration 225ms and a request url as a description. If you want to learn more about performance @ Sentry, check &lt;a href="https://blog.sentry.io/2021/08/23/mobile-vitals-four-metrics-every-mobile-developer-should-care-about#mobile-performance-monitoring"&gt;this post&lt;/a&gt; out.&lt;/p&gt;

&lt;p&gt;Back to the topic, this all means we need to find a way to inject our code before and after Room executes its queries to measure their execution time. There’s a &lt;a href="https://developer.android.com/reference/androidx/room/RoomDatabase.QueryCallback"&gt;QueryCallback&lt;/a&gt; available in the Room API, but it’s invoked only before a query gets executed, so we couldn’t really utilize it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Options
&lt;/h1&gt;

&lt;p&gt;Since there’s no way to know when a SQL query starts and finishes at runtime, we started looking into compile-time solutions. There are a few well-known options in the JVM world available for bytecode weaving:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/AspectJ"&gt;AspectJ&lt;/a&gt;: an AOP framework, which allows extending methods and plugging into their execution from outside of the target codebase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://asm.ow2.io/"&gt;ASM&lt;/a&gt;: a bytecode manipulation framework, which allows dealing with bytecode directly. For example, it’s used by R8 and D8 on Android for &lt;a href="https://jakewharton.com/digging-into-d8-and-r8/"&gt;optimizing and dexing&lt;/a&gt; the bytecode.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other higher-level abstractions like &lt;a href="https://www.javassist.org/"&gt;Javassist&lt;/a&gt;: are all based on ASM, but have a nicer and easier-to-understand APIs to deal with.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While it would be logical to pick something higher-level, considering we had no expertise in any of those, we’ve decided to look into how we could marry those with the Android Gradle plugin (AGP), as we are aiming to transform Android apps and need to support things like differe build types, flavours and so on. A quick search revealed that we could go with:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.transform.TransformAction.html"&gt;Gradle’s TransformAction&lt;/a&gt;: a plain Gradle API for transforming outputs. This is used, for example, for &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/DexingTransform.kt"&gt;dexing&lt;/a&gt;, &lt;a href="https://cs.android.com/android-studio/platform/tools/base/+/mirror-goog-studio-main:build-system/gradle-core/src/main/java/com/android/build/gradle/internal/dependency/JetifyTransform.kt"&gt;jetifying&lt;/a&gt;, and many other things that the Android Gradle plugin does.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/transform/Transform"&gt;AGP’s Transform&lt;/a&gt;: an old API from AGP that gives a list of inputs to be transformed, depending on the options provided. It also handles full/incremental builds automatically. Now deprecated in favor of the new API.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/variant/Component.html#transformclasseswith"&gt;AGP’s transformClassesWith&lt;/a&gt;: the new API from AGP that allows registering an ASM &lt;a href="https://asm.ow2.io/javadoc/org/objectweb/asm/ClassVisitor.html"&gt;ClassVisitor&lt;/a&gt; for visiting bytecode instructions and instrumenting &lt;code&gt;.class&lt;/code&gt; files. It utilizes the aforementioned &lt;a href="https://docs.gradle.org/current/dsl/org.gradle.api.artifacts.transform.TransformAction.html"&gt;TransformAction&lt;/a&gt; to transform dependencies and provides a Gradle task that handles full/incremental builds automatically. Available from AGP version 4.2.0 and above.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first option would require us to manually hook into the AGP process and deal with its artifacts, so we decided to look into options 2 and 3 and compare them, as they come directly from the vendor.&lt;/p&gt;

&lt;p&gt;Previously, in the old AGP versions (pre-4.2.0), if one would like to instrument compiled classes, they would need to register their own &lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/transform/Transform"&gt;Transform&lt;/a&gt;, traverse the input files and perform instrumentation for each of those files using &lt;a href="https://asm.ow2.io/javadoc/org/objectweb/asm/ClassWriter.html"&gt;ClassWriter&lt;/a&gt; from ASM. For each such &lt;code&gt;Transform&lt;/code&gt; AGP would register a new Gradle task, so if you happen to have 10 transforms instrumenting your application, you would end up with 10 additional Gradle tasks doing almost the same thing - iterating over the changed files, reading the bytecode, applying their own transformations and writing the bytecode back.&lt;/p&gt;

&lt;p&gt;This is horrible for the build speed and most of that can be commonized up until the point of actually instrumenting the bytecode.&lt;/p&gt;

&lt;p&gt;The new &lt;code&gt;transformClassesWith&lt;/code&gt; API tackles exactly that by providing a single API for registering &lt;code&gt;ClassVisitors&lt;/code&gt; and abstracting away file iteration and reading/writing the bytecode. It collects all visitors in a single list and then, for each file, runs all of them in order of registering, meaning there’s just a single Gradle task running for all transformations.&lt;/p&gt;

&lt;p&gt;We’ve decided to go with ASM + &lt;code&gt;transformClassesWith&lt;/code&gt; pack, deliberately supporting only the new versions of AGP.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Note, that if you want to support bytecode transformations in lower AGP versions (below 4.2.0) you still need to use the old Transform API. However, you can perform an AGP version check at runtime and choose either a new or an old API depending on it. An example can be seen in the &lt;a href="https://cs.android.com/android/platform/superproject/+/master:external/dagger2/java/dagger/hilt/android/plugin/src/main/kotlin/dagger/hilt/android/plugin/HiltGradlePlugin.kt;l=69-82"&gt;Hilt&lt;/a&gt; Gradle plugin.&lt;/em&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Using new transform APIs
&lt;/h1&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Registering AsmClassVisitorFactory&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As this post is not about how to create Gradle plugins, I will skip the setup part, but in a nutshell, we have to implement the &lt;code&gt;Plugin&lt;/code&gt; interface from Gradle and override a single method called &lt;code&gt;apply&lt;/code&gt;, which is called when the Gradle plugin is applied to a project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SentryPlugin&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Plugin&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Project&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After that we have to listen when the Android Gradle plugin is applied to the project and retrieve the new &lt;a href="https://developer.android.com/reference/tools/gradle-api/4.2/com/android/build/api/extension/AndroidComponentsExtension"&gt;AndroidComponentsExtension&lt;/a&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pluginManager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withPlugin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"com.android.application"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;androidComponentsExtension&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;extensions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getByType&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AndroidComponentsExtension&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The extension has a special &lt;code&gt;onVariants&lt;/code&gt; method that configures the build variants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;androidComponentsExtension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;onVariants&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;variant&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
    &lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally we can register our custom &lt;code&gt;AsmClassVisitorFactory&lt;/code&gt; for the &lt;code&gt;variant&lt;/code&gt; through &lt;code&gt;transformClassesWith&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="n"&gt;variant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;transformClassesWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nc"&gt;SpanAddingClassVisitorFactory&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nc"&gt;InstrumentationScope&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ALL&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="p"&gt;-&amp;gt;&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracingInstrumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;forceInstrumentDependencies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalidate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDisallowChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;currentTimeMillis&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setDisallowChanges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;extension&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tracingInstrumentation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tmpDir&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmpDir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;transformClassesWith&lt;/code&gt; accepts 3 parameters:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;ClassVisitorFactory&lt;/code&gt;: a factory, which provides a &lt;code&gt;ClassVisitor&lt;/code&gt; implementation and defines whether this visitor is interested in instrumenting a given class&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;InstrumentationScope&lt;/code&gt;: either &lt;code&gt;ALL&lt;/code&gt; or &lt;code&gt;PROJECT&lt;/code&gt;. Defines whether the instrumentation applies only for project files or for project files and their dependencies (e.g. jars). In our case, we were interested in instrumenting all Room queries, regardless of their origin, so we set it to &lt;code&gt;ALL&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;If you’re using &lt;code&gt;InsrumentationScope.ALL&lt;/code&gt;, beware that Gradle will cache the transformed artifacts across builds as long as the &lt;code&gt;InstrumentationParameters&lt;/code&gt; do not change. This may come as a surprise while developing, as some of the classes coming from the dependencies might not show up for instrumentation. We found it useful to have a boolean parameter, which would invalidate the transform caches by simply setting &lt;code&gt;System.currentTimeMillis&lt;/code&gt; and allow us to always receive all classes for instrumentation.&lt;/em&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuration function to be applied before passing the necessary parameters for the &lt;code&gt;ClassVisitorFactory&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://developer.android.com/reference/tools/gradle-api/7.0/com/android/build/api/instrumentation/InstrumentationParameters"&gt;InstrumentationParameters&lt;/a&gt; are the way to pass information from the plugin to the &lt;code&gt;ClassVisitorFactory&lt;/code&gt;. They are being used as Gradle inputs, this means they contribute to the up-to-date checks of the task and should be properly &lt;a href="https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:up_to_date_checks"&gt;annotated&lt;/a&gt;. For example, here we are setting a &lt;code&gt;debug&lt;/code&gt; boolean as well as a &lt;code&gt;tmpDir&lt;/code&gt; to use this information later and stream debug output of instrumentation into a file under the &lt;code&gt;tmpDir&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Implementing AsmClassVisitorFactory&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For the &lt;code&gt;ClassVisitorFactory&lt;/code&gt; it’s necessary to implement 2 methods:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;createClassVisitor&lt;/code&gt; which provides a custom &lt;code&gt;ClassVisitor&lt;/code&gt; from ASM that does the actual visiting of bytecode instructions and transformation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;isInstrumentable&lt;/code&gt; which defines whether a given class is applicable for instrumentation or not&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is also necessary to specify an implementation of &lt;code&gt;InstrumentationParameters&lt;/code&gt; as a type for &lt;code&gt;AsmVisitorFactory&lt;/code&gt; or use &lt;code&gt;InstrumentationParameters.None&lt;/code&gt; in case there are no params.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="k"&gt;abstract&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpanAddingClassVisitorFactory&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nc"&gt;AsmClassVisitorFactory&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;SpanAddingClassVisitorFactory&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;SpanAddingParameters&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

    &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;SpanAddingParameters&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;InstrumentationParameters&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;
        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Optional&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;invalidate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Input&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;debug&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

        &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="k"&gt;get&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nc"&gt;Internal&lt;/span&gt;
        &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tmpDir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;File&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;createClassVisitor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;classContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;nextClassVisitor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassVisitor&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;ClassVisitor&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"If we return true from the isInstrumentable below, we should return a ClassVisitor that will inject our code for measuring the execution time"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;fun&lt;/span&gt; &lt;span class="nf"&gt;isInstrumentable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;ClassData&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nc"&gt;Boolean&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;TODO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Determine if we are interested in instrumenting the given ClassData. For us it would mean a class annotated with @Dao"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the &lt;code&gt;isInstrumentable&lt;/code&gt; method, we determine whether we are interested in instrumenting the given &lt;code&gt;ClassData&lt;/code&gt; and later return our custom &lt;code&gt;ClassVisitor&lt;/code&gt; from the &lt;code&gt;createClassVisitor&lt;/code&gt; method in case we are. Note, however, that it’s always a good practice to fall back to &lt;code&gt;nextClassVisitor&lt;/code&gt; in case there’s no &lt;code&gt;ClassVisitor&lt;/code&gt; for the given class, otherwise the Gradle build will fail.&lt;/p&gt;

&lt;p&gt;Last, let’s look at the &lt;code&gt;ClassData&lt;/code&gt; structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;ClassData&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cm"&gt;/**
     * Fully qualified name of the class.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;className&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * List of the annotations the class has.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;classAnnotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * List of all the interfaces that this class or a superclass of this class implements.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;interfaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="cm"&gt;/**
     * List of all the super classes that this class or a super class of this class extends.
     */&lt;/span&gt;
    &lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;superClasses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It may seem to have everything to help us determine whether a class is suitable for instrumentation or not, but there’s a setback which we’ll cover in the next post.&lt;/p&gt;

&lt;p&gt;Using the new AGP transform APIs with ASM looks like an obvious choice for bytecode manipulation for Android as it affects the build speed almost unnoticeable (we’ll cover that later), handles full/incremental builds on its own, and offers a great API surface via ASM at the same time.&lt;/p&gt;

&lt;p&gt;In the next part, we’ll talk about Room internals, how we collected the methods for instrumentation, and what tools are available out there for dealing with ASM.&lt;/p&gt;

&lt;p&gt;The code is available in the &lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin"&gt;sentry-android-grade-plugin&lt;/a&gt; repo, specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin/blob/37eff955853fd395c0c6bf4c633518ce35dd508c/plugin-build/src/main/kotlin/io/sentry/android/gradle/SentryPlugin.kt#L62-L86"&gt;Registering a ClassVisitorFactory&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin/blob/37eff955853fd395c0c6bf4c633518ce35dd508c/plugin-build/src/main/kotlin/io/sentry/android/gradle/instrumentation/SpanAddingClassVisitorFactory.kt"&gt;ClassVisitorFactory&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the way, if you are already using the &lt;a href="https://docs.sentry.io/platforms/android/proguard/#gradle-configuration"&gt;Sentry Android Gradle plugin&lt;/a&gt;, give this new Room instrumentation a try in version &lt;code&gt;3.0.0-beta.1&lt;/code&gt; , we would appreciate your feedback via &lt;a href="https://github.com/getsentry/sentry-android-gradle-plugin/issues"&gt;GitHub issues&lt;/a&gt;. If not, it’s time to start using Sentry — &lt;a href="https://sentry.io/demo"&gt;request a demo&lt;/a&gt; and &lt;a href="https://sentry.io/signup/"&gt;try it out for free&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>android</category>
      <category>java</category>
      <category>mobile</category>
    </item>
  </channel>
</rss>
