<?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>DEV Community: Jules Sarah</title>
    <description>The latest articles on DEV Community by Jules Sarah (@jules_sarah_0718e958f0d24).</description>
    <link>https://dev.to/jules_sarah_0718e958f0d24</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4004010%2Ff34f3a46-3625-4d59-a933-3e49876494b9.png</url>
      <title>DEV Community: Jules Sarah</title>
      <link>https://dev.to/jules_sarah_0718e958f0d24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jules_sarah_0718e958f0d24"/>
    <language>en</language>
    <item>
      <title>Nine Screens of Pain: The UX of Submitting to the App Store</title>
      <dc:creator>Jules Sarah</dc:creator>
      <pubDate>Fri, 17 Jul 2026 11:51:01 +0000</pubDate>
      <link>https://dev.to/jules_sarah_0718e958f0d24/nine-screens-of-pain-the-ux-of-submitting-to-the-app-store-2f06</link>
      <guid>https://dev.to/jules_sarah_0718e958f0d24/nine-screens-of-pain-the-ux-of-submitting-to-the-app-store-2f06</guid>
      <description>&lt;p&gt;If you've ever submitted your first app, what follows will feel familiar. If you haven't, bookmark this — you're going to want to reference it around week two of the process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The starting state: a fresh App Store Connect account
&lt;/h2&gt;

&lt;p&gt;You've paid the $99. You've verified the DUNS number. You've waited three business days for Apple to enroll you.&lt;/p&gt;

&lt;p&gt;Now you're staring at App Store Connect, trying to figure out how to actually submit the app you've built. The dashboard shows icons for iTunes Connect (deprecated, but still visible), Certificates, TestFlight, Users and Access, Contracts. None of them say "Submit an App." That option lives under "My Apps" — which is one of the icons, but isn't labeled as such.&lt;/p&gt;

&lt;p&gt;You have spent fifteen minutes and made zero progress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screen-by-screen through the actual flow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Screen 1: Bundle ID registration.&lt;/strong&gt; In the developer portal — a different site than App Store Connect. Get this wrong and you can't rename it later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 2: Certificate + provisioning profile.&lt;/strong&gt; Two of each, development and distribution. Apple's UI walks you through it but assumes you already know what a certificate &lt;em&gt;does&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 3: Xcode signing configuration.&lt;/strong&gt; Six dropdowns, three of which look identical. If any one is wrong, the build fails with "no provisioning profile matches," and you go Google the exact error string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 4: Archive + upload.&lt;/strong&gt; Ten minutes of "processing," then usually success. Sometimes an email thirty minutes later: "Your build has one or more issues." No indication of which.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 5: App Information.&lt;/strong&gt; Category, subcategory, content rights, age rating. That last one is a twenty-question survey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 6: Pricing and Availability.&lt;/strong&gt; Choose the countries. Pick the tier. Explain whether the app is free but has in-app purchases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 7: App Privacy.&lt;/strong&gt; Answer questions about data collection. Every "yes" cascades into four to six follow-up questions. Miss one and your submission is rejected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 8: The version you're submitting.&lt;/strong&gt; Description, keywords (100 characters), promotional text, support URL, marketing URL, screenshots (six iPhone sizes minimum), preview video.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Screen 9: Review information.&lt;/strong&gt; Contact info, demo account credentials, review notes. Then, finally, the "Submit for Review" button.&lt;/p&gt;

&lt;p&gt;And that's not counting the TestFlight side, which is another five screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 3am "invalid binary" moment
&lt;/h2&gt;

&lt;p&gt;Every indie dev has this moment. You've finished all nine screens. You upload the build. Twenty minutes later, an email arrives:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The uploaded build has invalid binary architecture.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The provisioning profile does not include the Push Notifications entitlement.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Google says: rebuild with different Xcode settings. You do. Same error. You Google more. It turns out you also had to add a capability in the developer portal. You do that. Build. Upload. Wait twenty minutes. Different error.&lt;/p&gt;

&lt;p&gt;This is where most people give up on their first-month timeline.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this UX pattern persists
&lt;/h2&gt;

&lt;p&gt;Apple has zero pressure to simplify submission. Every indie who quits has ten replacements. And Apple's real customer isn't the indie dev — it's the enterprise studio with a team dedicated to this workflow.&lt;/p&gt;

&lt;p&gt;That isn't going to change from within Apple. But it means the leverage for indie tooling is enormous — whoever solves this UX gap earns loyalty from a badly underserved segment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a good submission tool looks like
&lt;/h2&gt;

&lt;p&gt;A good submission tool would:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ask the App Privacy questions in plain English, once.&lt;/strong&gt; Then fill in App Store Connect automatically.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cross-check your build against the top 20 rejection reasons before upload.&lt;/strong&gt; Fail loudly if any are hit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Show real-time status.&lt;/strong&gt; "In review, 47h in queue, median for your category is 26h" — not "Waiting for Review" for five days.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handle rejections as tickets.&lt;/strong&gt; The rejection reason parsed into a diff you can apply, with a resubmit button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never let you reach a 3am invalid-binary moment.&lt;/strong&gt; Every failure mode should surface &lt;em&gt;before&lt;/em&gt; upload, not twenty minutes after.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's the product I want to use as an indie dev — and it's part of why we built &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=app-store-submission-ux" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; to handle the generate-to-submit path so the nine screens stop being your problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;The App Store submission flow is a UX artifact of Apple's incentives, not a law of nature. Until it improves, treat it like the gauntlet it is: budget a full week, expect one rejection, and surface every failure mode you can before you hit upload.&lt;/p&gt;

&lt;p&gt;Which App Store rejection got you at 3am — invalid binary, missing entitlement, or a privacy label you filled out wrong? Drop it in the comments.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>appstore</category>
      <category>ux</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Auth Is Where User Trust Is Decided — What Indie Founders Miss About Expo + Supabase Flows</title>
      <dc:creator>Jules Sarah</dc:creator>
      <pubDate>Wed, 15 Jul 2026 13:07:33 +0000</pubDate>
      <link>https://dev.to/jules_sarah_0718e958f0d24/auth-is-where-user-trust-is-decided-what-indie-founders-miss-about-expo-supabase-flows-2e9f</link>
      <guid>https://dev.to/jules_sarah_0718e958f0d24/auth-is-where-user-trust-is-decided-what-indie-founders-miss-about-expo-supabase-flows-2e9f</guid>
      <description>&lt;ul&gt;
&lt;li&gt;There's a ~30-second window, usually mid-auth, where a new user decides to lean in or swipe away.&lt;/li&gt;
&lt;li&gt;Four moments break trust: a &lt;strong&gt;magic link that opens Safari instead of the app&lt;/strong&gt;, an &lt;strong&gt;OAuth flow stuck in limbo&lt;/strong&gt;, &lt;strong&gt;error messages that read like stack traces&lt;/strong&gt;, and &lt;strong&gt;sessions silently lost on app resume&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Most fixes don't need a designer — copy error-message tone from apps you love, test the magic-link flow on a real device with the app closed, and add a "check your email" confirmation.&lt;/li&gt;
&lt;li&gt;The single biggest lever: seamless session refresh on resume. Wiring it took install-to-week-1 retention from 32% to 41%.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;I've watched a lot of first-time app installs. There's a moment about 30 seconds in where users either lean forward and start actually using the app, or lean back and swipe it away. That moment is almost always mid-auth-flow — and it's mostly decided by whether the auth flow &lt;em&gt;feels&lt;/em&gt; trustworthy.&lt;/p&gt;

&lt;p&gt;This isn't a marketing insight. It's a UX one. Founders overwhelmingly underweight it, especially indies who see auth as "plumbing." Here's what actually happens in that 30 seconds, and how a good Expo + Supabase setup helps.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 30-second trust window
&lt;/h2&gt;

&lt;p&gt;The user opens your app fresh. They see a splash screen (fine), a welcome screen (fine), and then the sign-up screen. From here they make four judgments in rapid succession:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Do I understand what I'm being asked?&lt;/strong&gt; Email + password? Magic link? Social? All three?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do I trust this with my email?&lt;/strong&gt; Is the design polished? Does it look like a real product?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Does anything obviously break?&lt;/strong&gt; Autocorrect mangling my email, the keyboard covering the CTA, weird focus behavior?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do I get a signal that it worked?&lt;/strong&gt; After I hit sign-up, do I see confirmation, or does the app just… sit there?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Break any of these and you lose the user before they see a single product feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four moments that break trust
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Moment 1: The magic link opens in Safari, not the app.&lt;/strong&gt; The user taps the email link, iOS opens Safari, they see a "redirecting…" page, then get bounced back to the app — but the auth state didn't sync. They tap the link again. Same result. They quit. This happens when Universal Links aren't wired properly, or &lt;code&gt;expo-linking&lt;/code&gt; isn't handling the auth callback URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moment 2: The OAuth flow leaves them in limbo.&lt;/strong&gt; The user taps "sign in with Google," gets the browser sheet, taps back — and the app is stuck on a loading spinner. Flows that don't handle the OAuth-cancelled state leave the user stranded.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moment 3: The error message reads like a stack trace.&lt;/strong&gt; "Invalid credentials" or "Auth session missing!" terrifies non-technical users. "That password doesn't look right. Reset it?" converts them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Moment 4: Session lost after app resume.&lt;/strong&gt; The user signs in, backgrounds the app, comes back an hour later — signed out. &lt;code&gt;supabase-js&lt;/code&gt;'s default token refresh doesn't always fire on iOS resume. Wiring &lt;code&gt;AppState&lt;/code&gt; to &lt;code&gt;supabase.auth.refreshSession()&lt;/code&gt; explicitly solves this.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixes without a designer
&lt;/h2&gt;

&lt;p&gt;Even as a solo founder with no design help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Copy the error-message tone from a well-loved app you use (Linear, Notion, Superhuman).&lt;/li&gt;
&lt;li&gt;Test the magic-link flow yourself on a real device, with the app fully closed, before you launch.&lt;/li&gt;
&lt;li&gt;Add an "account created — check your email" confirmation screen. Users need the feedback.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Run a 20-minute usability test
&lt;/h2&gt;

&lt;p&gt;Grab someone who's never seen your app. Watch them sign up. Don't help.&lt;/p&gt;

&lt;p&gt;Note every place they hesitate for more than two seconds. Those are your trust breakpoints. Fix the top three before you launch.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one feature that changed my install-to-DAU rate
&lt;/h2&gt;

&lt;p&gt;All of the above matters, but the single thing that most moved my numbers: &lt;strong&gt;seamless session refresh on app resume&lt;/strong&gt;, so users never got signed out unexpectedly.&lt;/p&gt;

&lt;p&gt;My install-to-week-1 retention went from 32% to 41%. That's a large lift, and it came from one wired hook.&lt;/p&gt;

&lt;h2&gt;
  
  
  Takeaway
&lt;/h2&gt;

&lt;p&gt;Auth is a UX surface, not a plumbing task. If your Expo + Supabase foundation ships wired session refresh, correct deep-link handling, and a proper OAuth-cancel state, you're buying trust — not just code.&lt;/p&gt;

&lt;p&gt;If you'd rather start from a foundation with these already solved, I keep a set of Expo + Supabase templates wired for exactly this at &lt;a href="https://www.applighter.com/apps?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=auth-trust-window" rel="noopener noreferrer"&gt;Applighter&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Which of the four trust-breakers has cost you the most users? Drop it in the comments.&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>expo</category>
      <category>ux</category>
      <category>indiehackers</category>
    </item>
    <item>
      <title>40 React Native Devs on What They Wish Their Boilerplate Fixed</title>
      <dc:creator>Jules Sarah</dc:creator>
      <pubDate>Wed, 08 Jul 2026 14:36:42 +0000</pubDate>
      <link>https://dev.to/jules_sarah_0718e958f0d24/40-react-native-devs-on-what-they-wish-their-boilerplate-fixed-boe</link>
      <guid>https://dev.to/jules_sarah_0718e958f0d24/40-react-native-devs-on-what-they-wish-their-boilerplate-fixed-boe</guid>
      <description>&lt;ul&gt;
&lt;li&gt;I asked ~40 indie React Native devs one question: &lt;strong&gt;"What do you wish your boilerplate did differently?"&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Three wishes dominated: &lt;strong&gt;less opinionated navigation (28/40)&lt;/strong&gt;, &lt;strong&gt;TypeScript without wrapper-type overhead (24/40)&lt;/strong&gt;, and &lt;strong&gt;real dark-mode wiring, not just a CSS toggle (19/40)&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Nobody asked for more features.&lt;/strong&gt; Not one. Every wish was subtractive.&lt;/li&gt;
&lt;li&gt;The next wave of boilerplates will win by shipping &lt;strong&gt;less&lt;/strong&gt;, cleaner, with better exit points.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Over the past two months I've been talking to indie React Native developers about the boilerplates they use. The question that got the most useful answers wasn't "which boilerplate do you use?" — it was "what do you wish your boilerplate did differently?"&lt;/p&gt;

&lt;p&gt;I've now heard back from about 40 devs across a handful of communities (r/reactnative, the Expo Discord, some Twitter DMs). The pattern is clear enough to be worth writing down.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the survey ran and who answered
&lt;/h2&gt;

&lt;p&gt;Informal, DM-based, ~10 minutes each. Sample skews toward:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Solo devs and 2-3 person teams (35 of 40)&lt;/li&gt;
&lt;li&gt;Shipping paid apps, not free (~28 of 40)&lt;/li&gt;
&lt;li&gt;Using either an off-the-shelf boilerplate or one they'd built themselves (~half and half)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the sample is biased toward serious builders — people whose CI/CD choices matter to their revenue. That's the right audience for a survey about tooling ergonomics.&lt;/p&gt;

&lt;h2&gt;
  
  
  The top three wishes, in order
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Less opinionated navigation (28 of 40 mentioned this, often first)
&lt;/h3&gt;

&lt;p&gt;Most boilerplates ship a navigation shape — tab bar with a stack per tab, a modal presentation pattern, a drawer for settings — and it's the choice devs said they most often had to unwind. The frustration wasn't the specific shape; it was that the boilerplate had committed to one before knowing the product.&lt;/p&gt;

&lt;p&gt;What they wanted: a navigation layer they could rip and replace in an afternoon. What they got: a navigation layer that other screens assumed, so ripping it meant refactoring every screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. TypeScript throughout, without wrapper types getting in the way (24 of 40)
&lt;/h3&gt;

&lt;p&gt;Everyone wants TS. What they don't want is a boilerplate whose author defined 40 custom wrapper types on top of standard SDK types (Supabase, Stripe, Expo APIs) because they wanted stricter typing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ What devs are complaining about&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppUser&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/types/user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AppSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/types/session&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// You have to learn the boilerplate's vocabulary&lt;/span&gt;
&lt;span class="c1"&gt;// before you can find what you need in the SDK docs.&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ What they want instead&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Session&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;// The SDK's own types. No translation layer.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wish is TS-native, TS-first, but using the SDK's own types wherever possible. Wrap only when the SDK types are genuinely painful.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Real dark-mode wiring, not just a CSS toggle (19 of 40)
&lt;/h3&gt;

&lt;p&gt;This one surprised me. Many boilerplates advertise "dark mode included" and what they mean is a theme toggle that swaps a color palette. What devs actually want:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System-color-scheme detection that respects OS-level user preference&lt;/li&gt;
&lt;li&gt;Persistence of user override across app restarts&lt;/li&gt;
&lt;li&gt;Correct behavior for platform-specific dark-mode surfaces (iOS blur, Android edge-to-edge)&lt;/li&gt;
&lt;li&gt;Status bar + navigation bar following the theme&lt;/li&gt;
&lt;li&gt;Charts, images, and third-party components that don't fight the theme&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;"Dark mode included" should mean all of the above. Mostly it means a toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The features nobody asked for
&lt;/h2&gt;

&lt;p&gt;Equally interesting: &lt;strong&gt;nobody asked for more features.&lt;/strong&gt; Not one respondent said "I wish my boilerplate had more built-in screens" or "I wish it included analytics + i18n + AI chat + [something]." Every wish was subtractive: less friction, less coupling, less opinion.&lt;/p&gt;

&lt;p&gt;The implication is unusual for a product category: feature-heavy boilerplates are competing on the wrong axis. The market wants fewer, better, cleaner-seamed layers.&lt;/p&gt;

&lt;h2&gt;
  
  
  What that tells us about product/market fit for starter kits
&lt;/h2&gt;

&lt;p&gt;Boilerplates are a maturing category. The first wave sold on feature counts because that was legible. The current audience has been burned by feature counts enough times that they now audit for extensibility. The next wave of boilerplates will win by having a smaller surface area and cleaner exit points.&lt;/p&gt;

&lt;p&gt;That's a hard product story to tell. "We include less than the competition" doesn't sell landing pages. But it's what devs 6 months into a project actually want.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion: subtractive design wins in tooling
&lt;/h2&gt;

&lt;p&gt;The finding I keep coming back to: developers judge tools by how well they get out of the way, not by how much they do. Boilerplates are no different.&lt;/p&gt;

&lt;p&gt;If you're building a starter kit, the next feature to add is probably a layer to remove. If you're picking one, run the 60-minute strip-down test. Either way, subtractive design is the axis of quality.&lt;/p&gt;

&lt;p&gt;Feature counts are dead. Long live ergonomics.&lt;/p&gt;




&lt;p&gt;What did you wish your React Native boilerplate did differently? Drop it in the comments — I'm collecting responses for a follow-up survey and yours might be the pattern that makes the shortlist next time.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>boilerplate</category>
      <category>indiedev</category>
      <category>expo</category>
    </item>
    <item>
      <title>Stop Hand-Coding React Native Forms. Generate Them Instead.</title>
      <dc:creator>Jules Sarah</dc:creator>
      <pubDate>Wed, 01 Jul 2026 12:20:58 +0000</pubDate>
      <link>https://dev.to/jules_sarah_0718e958f0d24/stop-hand-coding-react-native-forms-generate-them-instead-b31</link>
      <guid>https://dev.to/jules_sarah_0718e958f0d24/stop-hand-coding-react-native-forms-generate-them-instead-b31</guid>
      <description>&lt;ul&gt;
&lt;li&gt;Every mobile app is mostly forms, and every form comes with the same boilerplate tax: library choice, schema, keyboard config, &lt;code&gt;KeyboardAvoidingView&lt;/code&gt;, validation, error/loading/disabled states, cross-platform testing.&lt;/li&gt;
&lt;li&gt;AI generation produces all of that from a plain-English prompt — including the props you always forget (&lt;code&gt;autoCapitalize="none"&lt;/code&gt;, &lt;code&gt;textContentType&lt;/code&gt;, &lt;code&gt;returnKeyType&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Where it still needs you: domain-specific validation, custom UX choices, and swapping the placeholder fetch for your real endpoint.&lt;/li&gt;
&lt;li&gt;The real win is iteration speed: tap a field, describe the change, only that field updates.&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Stop Hand-Coding React Native Forms. Generate Them Instead.
&lt;/h1&gt;

&lt;p&gt;Ask any React Native dev what part of the job they enjoy least, and forms come up fast. This post walks through generating forms with AI instead — what you get out of the box, what still needs your hand, and where the shortcuts hurt.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Forms Are the Worst
&lt;/h2&gt;

&lt;p&gt;Every mobile app is mostly forms with some chrome around them. Signup, checkout, profile edit, "leave a review," "add a task" — all forms. And every form comes with the same tax:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pick a form library (react-hook-form? Formik? roll your own?)&lt;/li&gt;
&lt;li&gt;Wire up a schema library (Zod, Yup)&lt;/li&gt;
&lt;li&gt;Configure the keyboard for each field (&lt;code&gt;keyboardType&lt;/code&gt;, &lt;code&gt;autoCapitalize&lt;/code&gt;, &lt;code&gt;autoComplete&lt;/code&gt;, &lt;code&gt;textContentType&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Fight &lt;code&gt;KeyboardAvoidingView&lt;/code&gt; until the submit button stops hiding under the keyboard&lt;/li&gt;
&lt;li&gt;Handle blur validation, submit validation, error rendering, disabled states, loading states&lt;/li&gt;
&lt;li&gt;Test on both iOS and Android&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Multiply by 15 forms and there goes your sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "AI-Generated Forms" Actually Produces
&lt;/h2&gt;

&lt;p&gt;I've been building with &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-powered-forms-data-entry" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; — an AI mobile app builder that takes plain English and outputs React Native + Expo — and forms are the use case where AI generation feels closest to a superpower. Here's what a prompt like this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A signup screen with email, password, confirm password, and a role picker (founder, designer, developer). Validate all fields on blur. Auto-advance to the next field on return.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;...actually generates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFormData&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;confirmPassword&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setErrors&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;({});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;passwordRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;confirmRef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useRef&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;field&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// per-field validation&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;KeyboardAvoidingView&lt;/span&gt;
    &lt;span class="na"&gt;behavior&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;Platform&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OS&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ios&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;padding&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;height&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;ScrollView&lt;/span&gt; &lt;span class="na"&gt;keyboardShouldPersistTaps&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"handled"&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;TextInput&lt;/span&gt;
        &lt;span class="na"&gt;ref&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;emailRef&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Email"&lt;/span&gt;
        &lt;span class="na"&gt;keyboardType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email-address"&lt;/span&gt;
        &lt;span class="na"&gt;autoCapitalize&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"none"&lt;/span&gt;
        &lt;span class="na"&gt;autoComplete&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"email"&lt;/span&gt;
        &lt;span class="na"&gt;textContentType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"emailAddress"&lt;/span&gt;
        &lt;span class="na"&gt;returnKeyType&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"next"&lt;/span&gt;
        &lt;span class="na"&gt;onSubmitEditing&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;passwordRef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;focus&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onChangeText&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;setFormData&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
        &lt;span class="na"&gt;onBlur&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;validateField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;formData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;style&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;errors&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="cm"&gt;/* password + confirm + picker follow */&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;ScrollView&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;KeyboardAvoidingView&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;Every prop I usually forget on the first try — &lt;code&gt;autoCapitalize="none"&lt;/code&gt;, &lt;code&gt;textContentType="emailAddress"&lt;/code&gt;, &lt;code&gt;returnKeyType="next"&lt;/code&gt;, the &lt;code&gt;KeyboardAvoidingView&lt;/code&gt; wrapper — is there by default. Not because I remembered to ask, but because the generator knows what an email field needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Actually Hard About Mobile Forms
&lt;/h2&gt;

&lt;p&gt;The interesting details, the ones AI generation handles well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard type per field.&lt;/strong&gt; Email → &lt;code&gt;email-address&lt;/code&gt;, phone → &lt;code&gt;phone-pad&lt;/code&gt;, card number → &lt;code&gt;number-pad&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Autofill hints.&lt;/strong&gt; &lt;code&gt;textContentType="emailAddress"&lt;/code&gt; on iOS, &lt;code&gt;autoComplete="email"&lt;/code&gt; on RN. Both required for OS autofill to fire.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Focus chain.&lt;/strong&gt; &lt;code&gt;returnKeyType="next"&lt;/code&gt; + &lt;code&gt;onSubmitEditing&lt;/code&gt; + refs to the next input.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Password fields.&lt;/strong&gt; &lt;code&gt;secureTextEntry&lt;/code&gt; + &lt;code&gt;textContentType="password"&lt;/code&gt; (or &lt;code&gt;newPassword&lt;/code&gt; on signup) so iOS offers to save/generate a password.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keyboard avoidance.&lt;/strong&gt; &lt;code&gt;KeyboardAvoidingView&lt;/code&gt; with different &lt;code&gt;behavior&lt;/code&gt; per platform, wrapped around a &lt;code&gt;ScrollView&lt;/code&gt; with &lt;code&gt;keyboardShouldPersistTaps="handled"&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These are the kind of details that get skipped when a human is tired. They're the kind of details that never get skipped by a generator, because they're pattern-heavy and well-documented.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where AI Generation Still Needs You
&lt;/h2&gt;

&lt;p&gt;Being honest: not everything is a slam dunk.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Very domain-specific validation&lt;/strong&gt; (tax IDs, national ID formats, complex phone number rules) — you'll want to review or specify these explicitly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom UX flows&lt;/strong&gt; where the "correct" pattern is a design choice, not a technical one, still benefit from a human eye.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with your existing backend&lt;/strong&gt; — the generator produces a placeholder fetch you replace with your actual endpoint. Not automatic magic, but a two-line edit.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Iteration Wins
&lt;/h2&gt;

&lt;p&gt;The real unlock isn't the first-generation speed — it's how fast you can iterate. Tap a field in the preview, say "make this a dropdown with 4 options," and only that field changes. Say "the submit button should be disabled until the form is valid," and it wires it up.&lt;/p&gt;

&lt;p&gt;Compare that to: open the file, find the &lt;code&gt;TextInput&lt;/code&gt;, replace with &lt;code&gt;Picker&lt;/code&gt;, update state binding, add options array, restyle, save, reload the preview. Forms live-edited with a chat interface are unreasonably fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Give it a shot: &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=ai-powered-forms-data-entry" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt;. Free to start. Type in a form description and see what falls out.&lt;/p&gt;

&lt;p&gt;What's the form screen that's eaten the most of your time? Drop a comment with what you're building.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>mobile</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Mobile App Analytics in React Native — What to Track in 2026</title>
      <dc:creator>Jules Sarah</dc:creator>
      <pubDate>Tue, 30 Jun 2026 13:50:07 +0000</pubDate>
      <link>https://dev.to/jules_sarah_0718e958f0d24/mobile-app-analytics-in-react-native-what-to-track-in-2026-1393</link>
      <guid>https://dev.to/jules_sarah_0718e958f0d24/mobile-app-analytics-in-react-native-what-to-track-in-2026-1393</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Most app teams track ~40 metrics. They review 4.&lt;/strong&gt; The other 36 fill dashboards and never inform a decision.&lt;/li&gt;
&lt;li&gt;The working set is &lt;strong&gt;12 metrics across 5 layers&lt;/strong&gt;: acquisition, activation, engagement, retention/monetization, technical.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Day-1 retention is the harshest single number&lt;/strong&gt; in your dashboard, and the one most tightly correlated with everything downstream.&lt;/li&gt;
&lt;li&gt;In React Native, 4 instrumentation failure modes make your numbers lie: &lt;strong&gt;deep-link sessions, app-state timeouts, offline-event flushing, and over-eventing.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Pick your core 8–12 metrics by stage. Give each one a named owner. Delete the rest.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The vanity-metric trap
&lt;/h2&gt;

&lt;p&gt;A vanity metric is any number that goes up reliably but doesn't help you decide anything.&lt;/p&gt;

&lt;p&gt;Total downloads is the classic example. Downloads only grow. An app with 1M downloads and 2% Day-30 retention is, by any honest measure, smaller than an app with 50k downloads and 35% Day-30 retention.&lt;/p&gt;

&lt;p&gt;Three questions to run every metric through before you instrument it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;What decision will this number inform?&lt;/strong&gt; If "we'll know how we're doing," it's vanity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Who owns that decision?&lt;/strong&gt; No owner → nobody acts on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;How often will they review it?&lt;/strong&gt; "Occasionally" means you built a screensaver.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A metric that survives all three is worth instrumenting. Almost nothing else is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 12 metrics that actually matter
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Acquisition layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Install conversion rate.&lt;/strong&gt; Of every 100 people who land on your App Store listing, how many install? Baselines: 25–30% organic, 30–40% paid. Below 20% you have a creative problem, not a traffic problem.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. CAC (by channel).&lt;/strong&gt; Useful only as a ratio with LTV. Healthy LTV:CAC is ~3:1 for consumer subscription apps. Below that, growth is a tax on the business.&lt;/p&gt;

&lt;h3&gt;
  
  
  Activation layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;3. Day-1 retention.&lt;/strong&gt; Of users who installed yesterday, how many came back today? Industry average 25–30%. Top decile clears 50%. This is the harshest number in your dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Time to first value (TTFV).&lt;/strong&gt; How long until a new user does the thing your app is for? Highest leverage on Day-1 retention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Engagement layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;5. DAU/MAU stickiness.&lt;/strong&gt; Healthy: 0.20. Daily habit: 0.50+. Productivity tools: 0.15–0.25. Ecommerce often under 0.10 (and that's fine if LTV supports it).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Session length + sessions per user.&lt;/strong&gt; Watch them together. Length down + frequency stable = users achieving the goal faster (good). Frequency down + length stable = retention problem in disguise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Key feature adoption rate.&lt;/strong&gt; Pick 2–3 features that &lt;em&gt;cause&lt;/em&gt; retention. Track the % of new users who reach each in their first week. This turns "improve onboarding" into a specific testable bet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Retention and monetization layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;8. Day-7 and Day-30 retention.&lt;/strong&gt; Day-1 = onboarding worked. Day-7 = value was real. Day-30 = value was &lt;em&gt;recurring&lt;/em&gt;. Track as cohorts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;9. Churn rate.&lt;/strong&gt; Healthy monthly: 5%. Trouble: 10%. Bucket has a hole: 15%+.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10. ARPU and LTV.&lt;/strong&gt; LTV justifies CAC. Be honest about assumptions — if your "12-month LTV" comes from 3 months of data, you are projecting, not measuring.&lt;/p&gt;

&lt;h3&gt;
  
  
  Technical performance layer
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;11. Crash-free sessions.&lt;/strong&gt; Google Play flags &amp;gt;1.09% crash rate as "bad behavior." Aim for 99.5%+ as the floor.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;12. Cold start time.&lt;/strong&gt; Cold-starts &amp;gt;2.5s correlate measurably with worse first-session retention. Most under-tracked, highest-leverage metric on the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pick your core 8–12 by stage
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stage&lt;/th&gt;
&lt;th&gt;Pre-launch (≤1k users)&lt;/th&gt;
&lt;th&gt;Growth (1k–100k)&lt;/th&gt;
&lt;th&gt;Scale (100k+)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Acquisition&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Install conv rate&lt;/td&gt;
&lt;td&gt;CAC by channel&lt;/td&gt;
&lt;td&gt;LTV:CAC ratio&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Activation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Time to first value&lt;/td&gt;
&lt;td&gt;Day-1 retention&lt;/td&gt;
&lt;td&gt;Day-1 by source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Engagement&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Key feature adoption&lt;/td&gt;
&lt;td&gt;DAU/MAU&lt;/td&gt;
&lt;td&gt;Session length + freq&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Retention/Money&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Day-7 retention&lt;/td&gt;
&lt;td&gt;Day-7, Day-30 cohorts&lt;/td&gt;
&lt;td&gt;Churn, ARPU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Technical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Crash-free sessions&lt;/td&gt;
&lt;td&gt;Cold start time&lt;/td&gt;
&lt;td&gt;All + ANR rate&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rule: &lt;strong&gt;max 2 metrics per layer at any one time, each with a named owner.&lt;/strong&gt; No owner → delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  React Native instrumentation: the 4 failure modes
&lt;/h2&gt;

&lt;p&gt;This is where most apps get the numbers wrong.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The deep-link problem
&lt;/h3&gt;

&lt;p&gt;When a user opens your app from a push notification or marketing URL, your analytics SDK may register a fresh session &lt;em&gt;before&lt;/em&gt; your deep-link handler decides which screen to show.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Wrong: tracking from componentDidMount / useEffect on each screen&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;screen_view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;home&lt;/span&gt;&lt;span class="dl"&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;// ✅ Right: track from navigation library's listener&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useNavigationState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@react-navigation/native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useNavigationState&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;useEffect&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;?.[&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;screen_view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;screen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;route&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;params&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="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Otherwise your funnel will be off by one step on every deep-linked session, and you'll spend a quarter optimizing the wrong screen.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The app-state problem
&lt;/h3&gt;

&lt;p&gt;A user backgrounds your app for 90 seconds to check a text, then comes back. Most SDKs default to a 30s session timeout, so your dashboard now reports 2 sessions where the user experienced 1.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Be explicit about your session definition&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;mixpanel&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;mixpanel-react-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;mixpanel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;TOKEN&lt;/span&gt;&lt;span class="dl"&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;// Document this next to every "sessions" chart you publish&lt;/span&gt;
  &lt;span class="na"&gt;trackAutomaticEvents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Mixpanel defaults to 30s — bump to 5 min for habit apps,&lt;/span&gt;
  &lt;span class="c1"&gt;// keep tight for transactional flows&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decide your session definition explicitly and document it next to every "sessions" chart you publish.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The offline-events problem
&lt;/h3&gt;

&lt;p&gt;A user logs a workout on the subway. Your analytics SDK queues the event and flushes it when connectivity returns — 30 minutes later.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Wrong: using only event_timestamp&lt;/span&gt;
&lt;span class="nx"&gt;SELECT&lt;/span&gt; &lt;span class="nf"&gt;date_trunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;hour&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;event_timestamp&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;count&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="nx"&gt;FROM&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="nx"&gt;WHERE&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workout_completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="nx"&gt;GROUP&lt;/span&gt; &lt;span class="nx"&gt;BY&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Right: store BOTH timestamps, pick per query&lt;/span&gt;
&lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;workout_completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;workout_type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;cardio&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;client_timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// when user did it&lt;/span&gt;
  &lt;span class="c1"&gt;// server adds event_timestamp on receipt&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Then query by client_timestamp for "when did users do this"&lt;/span&gt;
&lt;span class="c1"&gt;// and event_timestamp for "when did our system see it"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you build dashboards off &lt;code&gt;event_timestamp&lt;/code&gt; instead of &lt;code&gt;client_timestamp&lt;/code&gt;, your "workouts per hour" chart will spike at the times people emerge from underground.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. The over-eventing problem
&lt;/h3&gt;

&lt;p&gt;It's tempting to track every tap. Don't.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Start with this minimal schema, expand only when justified&lt;/span&gt;
&lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;screen_view&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;screen&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;feature_action&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;feature_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;analytics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;conversion&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each unnecessary event adds payload, drains battery, costs money at scale (most tools charge per event), and dilutes the events that matter. Start tight. Add events only when you can answer "what decision will this inform."&lt;/p&gt;

&lt;h2&gt;
  
  
  The default RN stack in 2026
&lt;/h2&gt;

&lt;p&gt;For most React Native apps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://firebase.google.com/docs/analytics" rel="noopener noreferrer"&gt;Firebase Analytics&lt;/a&gt;&lt;/strong&gt; for free baseline + Crashlytics for crash reporting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One&lt;/strong&gt; product-analytics tool: Mixpanel, Amplitude, or PostHog (pick one, instrument it well)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attribution SDK&lt;/strong&gt; (AppsFlyer, Adjust) only if running paid acquisition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Don't run three product-analytics tools at once. Pick one and instrument it well.&lt;/p&gt;

&lt;h2&gt;
  
  
  The point
&lt;/h2&gt;

&lt;p&gt;The best mobile app analytics setup isn't the one with the most dashboards. It's the one with the fewest dashboards that drive real decisions.&lt;/p&gt;

&lt;p&gt;Pick the 12 metrics above as a starting menu. Narrow to the 8–12 that match your stage. Give each one an owner. Instrument them carefully enough that the numbers your team argues over are actually the numbers your users experienced.&lt;/p&gt;

&lt;p&gt;For the longer write-up — including the full FAQ and the "why AI-built apps make analytics non-optional from day one" angle — see &lt;a href="https://www.rapidnative.com/blogs?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=mobile-app-analytics-what-to-track" rel="noopener noreferrer"&gt;the RapidNative blog&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;What's the metric you removed from your dashboard that you should have removed sooner? Drop it in the comments — I'm collecting the screensaver metrics indie devs ship by default for a follow-up.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>analytics</category>
      <category>mobile</category>
      <category>expo</category>
    </item>
  </channel>
</rss>
