<?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: RapidNative</title>
    <description>The latest articles on DEV Community by RapidNative (rapidnative-ai).</description>
    <link>https://dev.to/rapidnative-ai</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%2Forganization%2Fprofile_image%2F14099%2Fdceef4b9-6cec-42d3-8992-e0b83e1fc562.png</url>
      <title>DEV Community: RapidNative</title>
      <link>https://dev.to/rapidnative-ai</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rapidnative-ai"/>
    <language>en</language>
    <item>
      <title>The Complete Guide to Push Notifications in React Native (2026)</title>
      <dc:creator>Mark F A</dc:creator>
      <pubDate>Wed, 19 Aug 2026 05:16:15 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/the-complete-guide-to-push-notifications-in-react-native-2026-5bi0</link>
      <guid>https://dev.to/rapidnative-ai/the-complete-guide-to-push-notifications-in-react-native-2026-5bi0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;expo-notifications&lt;/code&gt; in 2026, even in a bare React Native workflow. CNG makes the managed/bare distinction mostly irrelevant.&lt;/li&gt;
&lt;li&gt;The token identifies a &lt;strong&gt;device plus install&lt;/strong&gt;, not a user. Plan for rotation from day one.&lt;/li&gt;
&lt;li&gt;On Android you must create a notification channel &lt;strong&gt;before&lt;/strong&gt; requesting permission, or notifications silently never display.&lt;/li&gt;
&lt;li&gt;Android 13+ needs the &lt;code&gt;POST_NOTIFICATIONS&lt;/code&gt; runtime permission, and only if your target SDK is 33 or higher.&lt;/li&gt;
&lt;li&gt;Never call the system permission prompt cold. It's one-shot. Put a screen you own in front of it.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;react-native-push-notification&lt;/code&gt; (Zo0r) is dead. Don't start a new project on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Push notifications are the single most reliable way to bring a user back into a mobile app. Localytics data pegs day-90 retention at roughly 190 percent higher for apps that use them well, and Airship's 2024 benchmarks show median direct-open rates north of 4 percent across most verticals. Yet notifications are also the feature most React Native teams postpone the longest, because the surface spans two operating systems, two transport providers (APNs and FCM), a set of iOS entitlements that require an Apple Developer account, an Android notification-channel model that changed twice in the last five years, and at least three distinct app states you have to handle differently.&lt;/p&gt;

&lt;p&gt;This guide is the version I wish I'd had when I first shipped push in a React Native app. It walks through the full stack (permissions, tokens, sending, receiving, deep linking, rich content, and testing) using the Expo Notifications SDK, which works for both Expo-managed and bare React Native projects on modern Expo SDK versions. Every code sample runs. Every gotcha is one I've actually hit in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "push notifications" actually means
&lt;/h2&gt;

&lt;p&gt;A push notification is a message your &lt;strong&gt;server&lt;/strong&gt; hands to a &lt;strong&gt;push service&lt;/strong&gt; (APNs for Apple, FCM for Google) which then delivers it to a specific device using a device-specific token. The device wakes up even if your app is killed, OS-level code decodes the payload, and either displays a system UI or fires an event into your app process.&lt;/p&gt;

&lt;p&gt;Three things follow from that definition, and they cause most of the confusion:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You cannot send a push directly from your React Native app to another user.&lt;/strong&gt; You need a server.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The token identifies a device plus app install&lt;/strong&gt;, not a user. When a user reinstalls, logs into a second device, or clears app data, you get a new token. Tokens also rotate for other reasons and can be invalidated by the push service.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What runs inside your app is only half the story.&lt;/strong&gt; The other half is Apple's or Google's OS-level notification presentation logic, which you configure through payload keys, notification channels, and iOS entitlements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Local notifications, the kind you schedule from inside the app without a server, use most of the same APIs but skip the token and the push service. This guide covers both, since real apps almost always need both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2026 landscape: which library
&lt;/h2&gt;

&lt;p&gt;There are three real options for React Native in 2026, and the honest recommendation is: &lt;strong&gt;use &lt;code&gt;expo-notifications&lt;/code&gt;&lt;/strong&gt;, even if you're not on Expo Go and even if you use a bare React Native workflow.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Trade-offs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;expo-notifications&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Almost every app&lt;/td&gt;
&lt;td&gt;First-class support in both Expo-managed and bare RN via CNG (Continuous Native Generation). Handles APNs and FCM transparently. Actively maintained.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;@react-native-firebase/messaging&lt;/code&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Apps that already use other Firebase products (Firestore, Auth, Analytics) heavily&lt;/td&gt;
&lt;td&gt;Google-only transport story; you still need a separate iOS setup for APNs credentials.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;code&gt;react-native-notifications&lt;/code&gt; (Wix)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Advanced native-side customization needs&lt;/td&gt;
&lt;td&gt;Smaller community; more manual native config.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The legacy &lt;code&gt;react-native-push-notification&lt;/code&gt; package (Zo0r) is unmaintained and should not be used in a new project.&lt;/p&gt;

&lt;p&gt;The rest of this guide uses &lt;code&gt;expo-notifications&lt;/code&gt;. Because Expo now supports prebuild (&lt;code&gt;npx expo prebuild&lt;/code&gt;) and CNG, you get the same experience whether your project is Expo-managed or bare: the config plugin generates the correct native code for both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing and configuring
&lt;/h2&gt;

&lt;p&gt;Assuming an Expo SDK 52+ project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx expo &lt;span class="nb"&gt;install &lt;/span&gt;expo-notifications expo-device expo-constants
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;expo-device&lt;/code&gt; is used to skip token registration on simulators. Apple's push service does not deliver to the iOS simulator on Xcode versions below 14, and even on newer Xcode you need a paid developer account and the Simulator's push testing tools. &lt;code&gt;expo-constants&lt;/code&gt; gives you access to &lt;code&gt;easConfig.projectId&lt;/code&gt;, which the Expo Push Service needs to route tokens.&lt;/p&gt;

&lt;p&gt;Add the config plugin in &lt;code&gt;app.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"expo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"plugins"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"expo-notifications"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./assets/notification-icon.png"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"color"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#111827"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"sounds"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"./assets/notification-sound.wav"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ios"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"bundleIdentifier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.yourco.yourapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"infoPlist"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"UIBackgroundModes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"remote-notification"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"android"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"package"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"com.yourco.yourapp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"googleServicesFile"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./google-services.json"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The notification icon must be a &lt;strong&gt;monochrome PNG with a transparent background&lt;/strong&gt;. Android's icon guidelines are strict, and a full-color icon renders as a white square.&lt;/li&gt;
&lt;li&gt;On iOS, you need to enable the &lt;strong&gt;Push Notifications capability&lt;/strong&gt; and the &lt;strong&gt;Background Modes to Remote notifications&lt;/strong&gt; capability in your Apple Developer account. If you're using EAS Build, &lt;code&gt;expo-notifications&lt;/code&gt; and the &lt;code&gt;UIBackgroundModes&lt;/code&gt; config above handle this automatically.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're standing up a new project rather than retrofitting an existing one, starting from a config that already builds saves an afternoon. &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=react-native-push-notifications-complete-guide-2026" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; generates Expo projects with &lt;code&gt;app.json&lt;/code&gt;, the bundle identifier, and the Android package name already in place, so you're editing a working config instead of assembling one from an empty file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Requesting permission the right way
&lt;/h2&gt;

&lt;p&gt;This is where a lot of implementations lose would-be opt-ins. Apple's system prompt is a one-shot event: if the user taps "Don't Allow," you cannot re-prompt from your app. They would have to go into Settings.&lt;/p&gt;

&lt;p&gt;The pattern that works is a &lt;strong&gt;pre-permission screen&lt;/strong&gt;: a screen you own, explaining the value, with a "Turn on notifications" button. Only when the user taps that button do you call the system API.&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="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;Notifications&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;expo-notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&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;Device&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;expo-device&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;Constants&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;expo-constants&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&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;Platform&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-native&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;registerForPushNotifications&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;Device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDevice&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;warn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Push notifications require a physical device.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&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;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;android&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setNotificationChannelAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&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;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;default&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;importance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AndroidImportance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;vibrationPattern&lt;/span&gt;&lt;span class="p"&gt;:&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="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;lightColor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#111827&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;existing&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getPermissionsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;finalStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;existing&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;existing&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;granted&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;status&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;requestPermissionsAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nx"&gt;finalStatus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="p"&gt;;&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;finalStatus&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;granted&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&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;projectId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expoConfig&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;extra&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;eas&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;projectId&lt;/span&gt;
    &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;Constants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;easConfig&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;projectId&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;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getExpoPushTokenAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;projectId&lt;/span&gt; &lt;span class="p"&gt;})).&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;token&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;A few details that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Android 13+ (API 33) requires runtime permission&lt;/strong&gt; via &lt;code&gt;POST_NOTIFICATIONS&lt;/code&gt;. &lt;code&gt;expo-notifications&lt;/code&gt; handles this for you when you call &lt;code&gt;requestPermissionsAsync()&lt;/code&gt;, but only if your target SDK is 33 or higher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On Android, you must create a notification channel&lt;/strong&gt; before you request permission. Otherwise notifications will silently fail to display, even though your token is valid. Channels group notifications so users can mute categories independently in system settings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want the Expo push token, not the native APNs or FCM token&lt;/strong&gt;, unless you're skipping Expo's push service. The Expo token is a single string of the form &lt;code&gt;ExponentPushToken[xxxxxxxxxx]&lt;/code&gt; that Expo's service translates to either APNs or FCM at send time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;The setup above gets you a valid token and a permission prompt users actually accept. The next layer is the one that decides whether push works in production: sending from your server, handling the payload in all three app states (foreground, background, killed), routing a tap to the right screen, and testing all of it on real hardware.&lt;/p&gt;

&lt;p&gt;What broke first when you shipped push? My money is on the Android channel. Drop yours in the comments.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>javascript</category>
      <category>mobile</category>
    </item>
    <item>
      <title>How AI Is Making Mobile Accessibility Easier Than Ever</title>
      <dc:creator>Famitha M A</dc:creator>
      <pubDate>Mon, 17 Aug 2026 08:27:47 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/how-ai-is-making-mobile-accessibility-easier-than-ever-25cl</link>
      <guid>https://dev.to/rapidnative-ai/how-ai-is-making-mobile-accessibility-easier-than-ever-25cl</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;95.9% of the top million sites have detectable a11y failures. Mobile apps are not better, they're just harder to audit.&lt;/li&gt;
&lt;li&gt;Four things changed: on-device assistive AI, ML-assisted testing, generated alt text and captions, and AI-assisted code generation.&lt;/li&gt;
&lt;li&gt;On-device AI raises the floor, not the ceiling. VoiceOver guessing "Button, likely a heart" is not a label.&lt;/li&gt;
&lt;li&gt;Rule-based tools catch 30 to 40% of WCAG failures. AI pushes that up, nowhere near 100%.&lt;/li&gt;
&lt;li&gt;The real fix is at generation time: if the model writes the JSX, it can write &lt;code&gt;accessibilityLabel&lt;/code&gt; in the same pass, for free.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gap between apps shipping and apps shipping &lt;em&gt;accessibly&lt;/em&gt; has been widening for a decade. What changed recently is that AI shows up on both sides of the shipping fence: in the runtime layer (the phone), in the tooling layer (audits), and at the code-generation layer, where the app is actually being written.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why mobile a11y is harder than the web
&lt;/h2&gt;

&lt;p&gt;Native apps don't share a common semantic model. On the web you have HTML, and HTML has a built-in accessibility contract. On mobile you have UIAccessibility on iOS, AccessibilityNodeInfo on Android, two screen readers with different gesture conventions, and a framework translation layer (React Native, Flutter, SwiftUI) sitting on top of both.&lt;/p&gt;

&lt;p&gt;That means a label that works on one platform can be silently dropped on the other. Here's the version everyone writes first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Ships. Announces as "Button". Useless.&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt; &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleFavorite&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;Icon&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isFavorite&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heart-filled&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="s2"&gt;heart&lt;/span&gt;&lt;span class="dl"&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="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the version that actually works with VoiceOver and TalkBack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;
  &lt;span class="na"&gt;onPress&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;toggleFavorite&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;accessibilityRole&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"button"&lt;/span&gt;
  &lt;span class="na"&gt;accessibilityLabel&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Add to favorites"&lt;/span&gt;
  &lt;span class="na"&gt;accessibilityHint&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="s"&gt;"Saves this item to your favorites list"&lt;/span&gt;
  &lt;span class="na"&gt;accessibilityState&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="na"&gt;selected&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;isFavorite&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
  &lt;span class="na"&gt;hitSlop&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;12&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;Icon&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;isFavorite&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;heart-filled&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="s2"&gt;heart&lt;/span&gt;&lt;span class="dl"&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="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;Pressable&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four extra props. Nobody argues they're hard. They just never get written, because they're invisible in the simulator and they're the last-day task that gets cut.&lt;/p&gt;

&lt;p&gt;Regulation is closing in anyway: the European Accessibility Act took effect for consumer digital products in June 2025, and ADA Title II requires state and local government apps to hit WCAG 2.1 AA by April 2026. Regulation without automation is a punishment. AI is what makes the automation possible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shift 1: the OS started covering for you
&lt;/h2&gt;

&lt;p&gt;iOS runs an on-device model that generates image descriptions for photos and unlabeled UI elements. Ship a button with no label and VoiceOver will guess: "Button, likely a play triangle." Android's TalkBack does the same with Gemini Nano, including for images inside third-party apps that never set &lt;code&gt;contentDescription&lt;/code&gt;. Both platforms now generate Live Captions for any audio playing on the device. Voice Control on iOS understands intent, so "tap the little heart" resolves even when your label is &lt;code&gt;favoriteButton&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The floor is rising. The ceiling is not. A guessed description loses to a real one every time, and the guess is silently wrong often enough to matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shift 2: audits got cheap
&lt;/h2&gt;

&lt;p&gt;Three categories worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Rule engines plus ML&lt;/strong&gt; (axe DevTools Mobile, Google's Accessibility Scanner). These now catch labels that exist but are meaningless, like &lt;code&gt;contentDescription="image1.jpg"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visual regression plus ML&lt;/strong&gt; (Applause, BrowserStack, testRigor). Diffs screens against known-good corpora and flags contrast ratios, small tap targets, low-legibility fonts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LLM audits.&lt;/strong&gt; Feed in screenshots plus an accessibility tree dump, get back findings in plain English: "The 'Sign in with Apple' button has no accessibility label; VoiceOver will announce it as 'Button'."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can wire the cheap version of the third one into CI yourself:&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="c1"&gt;// jest + @testing-library/react-native&lt;/span&gt;
&lt;span class="c1"&gt;// Fails the build when an interactive element ships unlabeled.&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;render&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="s2"&gt;@testing-library/react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nf"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;all buttons are labeled&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="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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UNSAFE_root&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;render&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ProductScreen&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;buttons&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UNSAFE_root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&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;n&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessibilityRole&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeGreaterThan&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="nx"&gt;buttons&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;b&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="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;accessibilityLabel&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toBeTruthy&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;WebAIM's own numbers say automated tooling catches 30 to 40% of WCAG failures. AI raises it, not to 100%. Keep a human in the loop, ideally one who uses the tech.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shift 3: the missing content is now generated
&lt;/h2&gt;

&lt;p&gt;Alt text from vision models is table stakes. Whisper-class models made captions good enough for accessibility use in most languages. LLMs are producing plain-language versions of dense content on the fly, which is a genuine breakthrough for cognitive accessibility. Live cross-language captioning is built into the call stack on both platforms.&lt;/p&gt;

&lt;p&gt;The catch: generated descriptions are only &lt;em&gt;usually&lt;/em&gt; right, and a screen reader confidently reading a subtly wrong description is worse than one that says nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Shift 4: fix it at generation time
&lt;/h2&gt;

&lt;p&gt;This is the one that matters. When an LLM writes the screen, the accessibility props are not extra work, they're the same tokens. That flips the default from "no labels, ship it" to "labels came with the generation."&lt;/p&gt;

&lt;p&gt;This is the bet &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=how-ai-is-making-mobile-accessibility-easier" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; is built on: prompt in, real React Native and Expo code out, with &lt;code&gt;accessibilityLabel&lt;/code&gt;, &lt;code&gt;accessibilityRole&lt;/code&gt;, &lt;code&gt;accessibilityHint&lt;/code&gt;, and &lt;code&gt;accessibilityState&lt;/code&gt; attached at generation rather than bolted on later. An image comes out as &lt;code&gt;accessibilityLabel="Sunset over the ocean"&lt;/code&gt; instead of a nameless &lt;code&gt;&amp;lt;Image /&amp;gt;&lt;/code&gt; someone will theoretically label next sprint.&lt;/p&gt;

&lt;p&gt;None of this is exclusive to one tool. A well-prompted Copilot, Cursor, or Claude does the same thing in a hand-written codebase. Put it in the repo rules and it applies to every generation:&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="c"&gt;&amp;lt;!-- .cursorrules / CLAUDE.md / copilot-instructions.md --&amp;gt;&lt;/span&gt;
Every interactive element must have accessibilityRole and accessibilityLabel.
Every Image must have accessibilityLabel or accessible={false}.
Touch targets must be &amp;gt;= 44x44 (use hitSlop when the visual is smaller).
Never use placeholder text as the only label for a TextInput.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What AI still can't do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It tells you labels exist, not that the flow is &lt;strong&gt;usable&lt;/strong&gt;. Reading order that jumps around passes every automated check.&lt;/li&gt;
&lt;li&gt;Generated alt text is generic. "A person" is compliant. "The founder of the org you're about to donate to" is what a sighted user sees.&lt;/li&gt;
&lt;li&gt;Cognitive accessibility is barely automatable. The fix is design work, not a tag.&lt;/li&gt;
&lt;li&gt;Live captioning fails contextually: wrong medication name, wrong price, wrong date. Plausible-but-wrong is the worst failure mode for exactly the users you were helping.&lt;/li&gt;
&lt;li&gt;Motor accessibility is under-covered because it doesn't map cleanly onto structured tests.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Treat AI as the intern who catches the obvious stuff at 10x speed. It makes a specialist's time higher-leverage. It doesn't replace them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The checklist I'd run on any AI-generated app
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Every interactive element has &lt;code&gt;accessibilityLabel&lt;/code&gt;, plus &lt;code&gt;accessibilityRole&lt;/code&gt; and &lt;code&gt;accessibilityHint&lt;/code&gt; where useful.&lt;/li&gt;
&lt;li&gt;Images are labeled or explicitly marked decorative with &lt;code&gt;accessible={false}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Contrast passes WCAG 2.2 AA (4.5:1 body, 3:1 large). Re-check after any palette tweak.&lt;/li&gt;
&lt;li&gt;Font scaling works. Test at 200%.&lt;/li&gt;
&lt;li&gt;Touch targets are 44x44 minimum. Generators over-index on clean small icons, so this is the most-violated rule in generated code.&lt;/li&gt;
&lt;li&gt;Forms have labels, not just placeholders.&lt;/li&gt;
&lt;li&gt;Video has captions.&lt;/li&gt;
&lt;li&gt;You personally turned on VoiceOver and TalkBack and completed the primary flow.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Point 4 is worth a snippet, since it's the one people get wrong quietly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useWindowDimensions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;PixelRatio&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Text&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="s2"&gt;react-native&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Bad: fixed height locks text out of scaling&lt;/span&gt;
&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;View&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;44&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;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Continue&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;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;

&lt;span class="c1"&gt;// Better: let the row grow with the user's font scale&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PixelRatio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getFontScale&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;View&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="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;minHeight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;44&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;scale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;justifyContent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;center&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt; &lt;span class="na"&gt;maxFontSizeMultiplier&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Continue&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="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nc"&gt;View&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where this goes next
&lt;/h2&gt;

&lt;p&gt;WCAG 3.0 moves toward outcome-based scoring instead of binary pass/fail, which is far better suited to LLM evaluation. EAA enforcement in Europe starts producing fines and case law in 2026, which changes the internal politics of a11y work fast. And expect compliance tooling that lives inside the AI development pipeline: reviewing PRs, gating deploys, producing an audit trail.&lt;/p&gt;

&lt;p&gt;The direction of travel is that accessibility becomes a property of how the app was built, not a coat of paint applied afterward.&lt;/p&gt;

&lt;p&gt;What's the a11y bug that has burned you hardest in a shipped app? Drop it in the comments. I'm especially interested in the ones no automated audit caught.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>a11y</category>
      <category>ai</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Mobile App Analytics in React Native: What to Track (and What to Skip)</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Thu, 13 Aug 2026 05:01:19 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/mobile-app-analytics-in-react-native-what-to-track-and-what-to-skip-25jp</link>
      <guid>https://dev.to/rapidnative-ai/mobile-app-analytics-in-react-native-what-to-track-and-what-to-skip-25jp</guid>
      <description>&lt;p&gt;I have shipped enough React Native apps to see the same pattern every time: on launch day someone wires up fifty analytics events and six months later nobody on the team can explain what any of them are for. This is a working developer's guide to instrumenting a React Native or Expo app so the numbers actually mean something.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Four Buckets
&lt;/h2&gt;

&lt;p&gt;Every useful mobile app metric fits into one of four categories:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Acquisition&lt;/strong&gt;: installs, CPI, CAC, install source, store conversion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Activation&lt;/strong&gt;: activation rate, time to value, onboarding funnel completion.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Engagement and Retention&lt;/strong&gt;: DAU, MAU, stickiness (DAU/MAU), session length, D1/D7/D30 retention cohorts, churn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monetization&lt;/strong&gt;: ARPU, conversion rate, LTV, trial-to-paid, renewal rate.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Track a couple from each bucket. Skip the rest until you outgrow them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Metrics That Are Actually Product Metrics
&lt;/h2&gt;

&lt;p&gt;Performance is not a "nice to have." A slow, crashy app poisons every other metric.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Crash-Free Session Rate&lt;/strong&gt;: target 99.5%+ for consumer, 99.9%+ if you take payment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ANR Rate&lt;/strong&gt; (Android only): Play Store visibility suffers when this crosses Google's bad-behavior threshold.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold Start Time&lt;/strong&gt;: under 500ms good, over 1500ms bad. Hermes bytecode substantially cuts JS parse time vs JSC.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API p95 Latency&lt;/strong&gt;: measured from the client, not the server. The tail is where users feel pain.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Stack I Actually Use in 2026
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Product analytics&lt;/td&gt;
&lt;td&gt;PostHog (self-hosted) or Amplitude (free tier)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Errors / performance&lt;/td&gt;
&lt;td&gt;Sentry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Subscriptions&lt;/td&gt;
&lt;td&gt;RevenueCat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attribution&lt;/td&gt;
&lt;td&gt;SKAdNetwork + Play Install Referrer (free, built-in)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All of these have first-class Expo config plugins in 2026. &lt;code&gt;npx expo install @sentry/react-native posthog-react-native react-native-purchases&lt;/code&gt; gets you 80% of the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Instrumentation Pattern That Scales
&lt;/h2&gt;

&lt;p&gt;Wrap your SDK. Always. Every screen and component should import &lt;code&gt;track()&lt;/code&gt; from a single module, never the SDK directly.&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;// src/analytics/track.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;PostHog&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;posthog-react-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;type&lt;/span&gt; &lt;span class="nx"&gt;EventName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;signup_completed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;paywall_viewed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;subscription_started&lt;/span&gt;&lt;span class="dl"&gt;'&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_logged&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;EventProps&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;track&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventProps&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="nx"&gt;PostHog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;capture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;EventProps&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="nx"&gt;PostHog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;identify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&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;Rules I have learned the hard way:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Event names are verbs, past tense.&lt;/strong&gt; &lt;code&gt;paywall_viewed&lt;/code&gt;, not &lt;code&gt;paywall&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User properties are stable, few, semantic.&lt;/strong&gt; &lt;code&gt;plan&lt;/code&gt;, &lt;code&gt;cohort_week&lt;/code&gt;, &lt;code&gt;platform_version&lt;/code&gt;. That is enough for 90% of segmentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No PII in event names or properties.&lt;/strong&gt; Not even hashed. Just don't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type the event names.&lt;/strong&gt; If your event schema is a &lt;code&gt;type&lt;/code&gt;, refactoring becomes safe. Untyped strings are how event soup starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Metrics I Have Deleted From Every Dashboard
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Total installs, unless you are showing it next to activated installs.&lt;/li&gt;
&lt;li&gt;Screen views as a KPI. Useful for debugging nav, useless as a headline.&lt;/li&gt;
&lt;li&gt;"Engagement" as a single number. It's a portfolio, not a scalar.&lt;/li&gt;
&lt;li&gt;App Store rating without volume and recency alongside it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rule: for every metric, name the decision it drives, the owner of that decision, and the cadence they act on it. Can't do all three? Delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Privacy: The 2026 Baseline
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;PrivacyInfo.xcprivacy&lt;/code&gt;&lt;/strong&gt; is mandatory if any of your SDKs touch required-reason APIs. Most analytics SDKs do. Skip this and your App Store submission gets rejected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consent for EU&lt;/strong&gt;: non-negotiable for behavioral events. Use Klaro if you want an OSS option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-party &amp;gt; third-party&lt;/strong&gt;: server-side event streams from your app to your own backend are increasingly the safe default. PostHog and Amplitude both support this.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where to Go From Here
&lt;/h2&gt;

&lt;p&gt;If you're starting from scratch, spin up an app with an AI React Native builder like &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=mobile-app-analytics-what-to-track" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt;. It scaffolds Expo apps with the wrapper pattern above, and event schemas become just another thing you can prompt for. Faster than doing the boilerplate by hand, and consistent across every screen.&lt;/p&gt;

&lt;p&gt;If you already have an app, delete half your events tomorrow. You'll be fine.&lt;/p&gt;

&lt;p&gt;Which metric have you deleted from a dashboard and never missed? That list is more useful than any tracking plan; drop yours in the comments.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>analytics</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Build a HIPAA-Compliant App: A Founder's Guide for 2026</title>
      <dc:creator>Mark F A</dc:creator>
      <pubDate>Wed, 12 Aug 2026 07:09:30 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/how-to-build-a-hipaa-compliant-app-a-founders-guide-for-2026-ha0</link>
      <guid>https://dev.to/rapidnative-ai/how-to-build-a-hipaa-compliant-app-a-founders-guide-for-2026-ha0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;There is no HIPAA certification.&lt;/strong&gt; No stamp, no badge. It's a continuous state you maintain through contracts, controls, and documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Six technical pillars carry most of the weight:&lt;/strong&gt; encryption at rest and in transit, unique auth with MFA, server-enforced RBAC, tamper-evident audit logs, automatic session logoff, and a signed BAA with every vendor that touches PHI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Missing BAAs are the most common root cause of HIPAA breaches.&lt;/strong&gt; They're also the easiest thing for an auditor to check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can use LLMs with PHI in 2026&lt;/strong&gt;, but only with providers that offer a BAA, on the right plan, with identifiers redacted before the call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The $60k-$300k agency number is real but not the only number.&lt;/strong&gt; A React Native and Supabase stack can hit a compliant beta in eight to twelve weeks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every healthcare founder eventually hits the same wall. You have a real idea, sometimes even paying pilot customers, and then a hospital CTO or a payer's legal team asks the question that stops the conversation: "Is your app HIPAA-compliant?"&lt;/p&gt;

&lt;p&gt;Suddenly the six-week MVP plan collides with a body of law from 1996, agencies quoting $150,000 to $300,000 for a "compliant build," and a vendor stack full of tools you love that quietly cannot legally touch patient data.&lt;/p&gt;

&lt;p&gt;Here's how you'd actually do it in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "HIPAA-compliant" actually means
&lt;/h2&gt;

&lt;p&gt;HIPAA-compliant app development means every point where Protected Health Information (PHI) is created, stored, transmitted, or accessed is covered by administrative, physical, and technical safeguards defined by the HIPAA Security Rule. And every third-party vendor that touches PHI has signed a Business Associate Agreement (BAA) with you.&lt;/p&gt;

&lt;p&gt;Compliance is not a certification you buy. There is no "HIPAA-certified" stamp issued by HHS. What exists is the HIPAA Security Rule, the Privacy Rule, and the Breach Notification Rule, plus the Office for Civil Rights that investigates breaches and complaints. Your job is to demonstrate, with documentation, contracts, and technical controls, that you meet every applicable requirement on the day someone asks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three rules that shape your architecture
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The Privacy Rule&lt;/strong&gt; governs how PHI can be used and disclosed. It introduces the concept every product designer bumps into first: the &lt;strong&gt;minimum necessary standard&lt;/strong&gt;. Your app collects, displays, and transmits only the PHI required to do its job. If your telehealth app doesn't need a full address to run a video visit, don't ask for it. It also defines patient rights (access, amendment, accounting of disclosures) and every one of those maps to a screen or an endpoint you have to build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Security Rule&lt;/strong&gt; is the one engineers live in. It covers electronic PHI and requires three safeguard categories:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Administrative:&lt;/strong&gt; risk assessments, workforce training, access management policies, incident response.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Physical:&lt;/strong&gt; facility access controls, workstation security, device and media disposal.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical:&lt;/strong&gt; access controls, audit logs, integrity controls, transmission security. This is the layer your code implements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The Breach Notification Rule&lt;/strong&gt; gives you a legal duty to notify affected individuals within 60 days, notify HHS, and in some cases notify media. "Unsecured" is the operative word. Properly encrypted PHI that gets stolen is generally treated as a much lower-risk event, which is the strongest practical argument that encryption is not optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 6 technical requirements that actually matter
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Encryption at rest and in transit.&lt;/strong&gt; AES-256 server-side and on device. TLS 1.2+ with modern ciphers in flight. Hardware-backed key storage for anything the client persists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unique user authentication with MFA.&lt;/strong&gt; Unique identifier per user, no shared logins, MFA for anyone accessing PHI. Biometrics count as a second factor on the client.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Role-based access control.&lt;/strong&gt; Patient sees their own record. Nurse sees their unit. Billing clerk sees charge codes but not clinical notes. Enforced on the server, never only in the UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tamper-evident audit logging.&lt;/strong&gt; Every read and write of PHI logged with who, what, when, and from where. Logs protected from modification, retained six years.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Automatic logoff and session controls.&lt;/strong&gt; Inactive sessions terminate. On mobile this means a background timer that clears the session and forces re-auth.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signed BAAs with every vendor that touches PHI.&lt;/strong&gt; The one most first-time healthcare founders miss.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;On mobile, requirement 1 mostly comes down to not persisting PHI in the wrong place:&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="c1"&gt;// DON'T: AsyncStorage is plaintext on disk.&lt;/span&gt;
&lt;span class="c1"&gt;// On Android it's SharedPreferences XML, readable with adb.&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;AsyncStorage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;patient_mrn&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;mrn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// DO: hardware-backed keychain/keystore, device-bound.&lt;/span&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;SecureStore&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;expo-secure-store&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setItemAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;session_token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;keychainAccessible&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SecureStore&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;WHEN_UNLOCKED_THIS_DEVICE_ONLY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;keychainService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;com.yourcompany.health.auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;requireAuthentication&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;// gates read behind Face ID / biometric&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better still: don't persist PHI on the device at all. Keep it in memory, fetch it per session, and let the server be the only durable store. That turns a lost-phone incident into a non-event.&lt;/p&gt;

&lt;p&gt;Requirement 5 is the one people forget until an auditor asks. A minimal version:&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="c1"&gt;// sessionTimer.js&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;AppState&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-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;TIMEOUT_MS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 15 min inactivity&lt;/span&gt;
&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;backgroundedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nx"&gt;AppState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEventListener&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;change&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;next&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;background&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;backgroundedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;();&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;next&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;backgroundedAt&lt;/span&gt;&lt;span class="p"&gt;)&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="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;backgroundedAt&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;TIMEOUT_MS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nf"&gt;clearSession&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;       &lt;span class="c1"&gt;// wipe in-memory PHI&lt;/span&gt;
      &lt;span class="nf"&gt;navigateToLogin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;    &lt;span class="c1"&gt;// force re-authentication&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;backgroundedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&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;Pair that with a foreground inactivity timer reset on user interaction, and log both the timeout and the re-auth to your audit trail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The vendor stack that will actually sign a BAA
&lt;/h2&gt;

&lt;p&gt;This is where most guides fail founders. They say "sign a BAA with your vendors" without saying which vendors will sign one, on which plan, at what price.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;HIPAA-eligible option in 2026&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cloud infrastructure&lt;/td&gt;
&lt;td&gt;AWS (BAA free via AWS Artifact, ~150 eligible services), Google Cloud, Azure&lt;/td&gt;
&lt;td&gt;AWS is the most-used HIPAA cloud in 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Database + auth + storage&lt;/td&gt;
&lt;td&gt;Supabase Team ($599/mo) + HIPAA add-on ($350/mo), or self-host&lt;/td&gt;
&lt;td&gt;Check current Supabase HIPAA docs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Frontend (mobile)&lt;/td&gt;
&lt;td&gt;React Native / Expo, no PHI in the bundle&lt;/td&gt;
&lt;td&gt;Most common mobile framework for HIPAA apps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Email (transactional)&lt;/td&gt;
&lt;td&gt;AWS SES (BAA available), Paubox, LuxSci&lt;/td&gt;
&lt;td&gt;Not SendGrid on standard plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMS / voice&lt;/td&gt;
&lt;td&gt;Twilio (HIPAA-eligible products only)&lt;/td&gt;
&lt;td&gt;Sign a BAA and configure specifically for HIPAA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push notifications&lt;/td&gt;
&lt;td&gt;OneSignal (HIPAA plan), or AWS SNS&lt;/td&gt;
&lt;td&gt;Never put PHI in the notification body&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error tracking&lt;/td&gt;
&lt;td&gt;Sentry (HIPAA tier), Datadog (HIPAA tier)&lt;/td&gt;
&lt;td&gt;Standard tiers do not qualify&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Analytics&lt;/td&gt;
&lt;td&gt;Server-side only, PHI-free events&lt;/td&gt;
&lt;td&gt;Google Analytics does not sign BAAs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI / LLM&lt;/td&gt;
&lt;td&gt;Anthropic (BAA available), AWS Bedrock, Vertex AI, Azure OpenAI&lt;/td&gt;
&lt;td&gt;See below&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two rules of thumb. If a vendor's marketing site doesn't say "HIPAA eligible" and their sales team can't produce a template BAA within a day, assume they can't. And HIPAA eligibility is almost always plan-specific, so the free tier you prototyped on almost certainly doesn't qualify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can you use LLMs with PHI?
&lt;/h2&gt;

&lt;p&gt;Yes, but only with specific providers, on specific plans, under a signed BAA.&lt;/p&gt;

&lt;p&gt;The major model providers now offer HIPAA-eligible tiers. Anthropic offers BAAs for enterprise customers using Claude. AWS Bedrock, Google Cloud Vertex AI, and Azure OpenAI all extend the underlying platform BAA to the models hosted on them. What you cannot do is ship PHI to a consumer API endpoint with no BAA. That's a breach the moment the request is sent, regardless of what the model does with the data.&lt;/p&gt;

&lt;p&gt;Even under a BAA, minimize what you send:&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="c1"&gt;// Redact identifiers server-side BEFORE the model call.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SAFE_FIELDS&lt;/span&gt; &lt;span class="o"&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;ageBand&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;sex&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;conditionCodes&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;medications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toModelPayload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;SAFE_FIELDS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;k&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="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;patient&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;acc&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;// Allow-list, not deny-list. A deny-list silently leaks&lt;/span&gt;
&lt;span class="c1"&gt;// every new field someone adds to the patient record.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Allow-list beats deny-list every time here. A deny-list quietly leaks whatever field a teammate adds next sprint. And log every prompt and response as part of your audit trail. If your AI features are purely non-PHI (educational content, appointment reminders with no diagnosis), you can often keep them on your regular non-BAA stack, as long as you can prove PHI never crosses that boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build checklist
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data classification.&lt;/strong&gt; List every data element. Tag each PHI, non-PHI, or de-identified. Remove fields and prefer tokens over raw values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Written risk assessment.&lt;/strong&gt; A Security Rule requirement, not a nice-to-have. Threats, likelihoods, mitigations. Update annually and on material architecture changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pick your HIPAA-eligible stack.&lt;/strong&gt; Sign BAAs before a single byte of PHI reaches any vendor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement the six pillars.&lt;/strong&gt; Encryption, MFA, server-side RBAC, audit logs with six-year retention, session logoff, BAAs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure SDLC.&lt;/strong&gt; Code review, SAST/DAST, dependency pinning, supply chain controls. First thing a serious buyer's security team asks about.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Train your workforce.&lt;/strong&gt; Security Awareness Training for anyone who could touch PHI. Keep records: dates, curricula, attendees.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policies and incident response plan.&lt;/strong&gt; Breach notification, sanctions, contingency, business continuity. Start from a template, then customize.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Third-party pen test before launch.&lt;/strong&gt; Not required by HIPAA, but every hospital and enterprise buyer will ask for a recent report.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy policy and Notice of Privacy Practices.&lt;/strong&gt; User-facing documents, not just legal artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Continuous compliance.&lt;/strong&gt; Quarterly access reviews, monthly log reviews, annual risk reassessment.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What it costs and how long it takes
&lt;/h2&gt;

&lt;p&gt;Every agency guide says $60,000 to $300,000 and six to twelve months. That's real for a bespoke, agency-built telemedicine app with EHR integration. It's not the only number.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Baseline:&lt;/strong&gt; whatever a non-HIPAA build of the same feature set would cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add 15-25%&lt;/strong&gt; for compliance-specific engineering: audit logging, RBAC, session management, encryption plumbing, admin console for access reviews.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vendor upcharge:&lt;/strong&gt; roughly $1,000-$3,000/month at MVP scale once you're on HIPAA tiers across the board.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance program:&lt;/strong&gt; policies, risk assessment, training, pen test, BAA legal review. Budget $10,000-$30,000 year one, roughly half that annually after.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Timelines compress the same way. A modern React Native and Supabase HIPAA stack, with the front-end scaffolded in an AI-assisted tool like &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=how-to-build-hipaa-compliant-app" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt;, can get you from zero to a working compliant beta in eight to twelve weeks. Worth being precise about the boundary, though: a code generation tool accelerates your front-end and scaffolding, not your compliance program. No AI code tool signs a BAA with you, because it doesn't touch your production PHI. And you should not paste PHI into any prompt during development. Scaffold with realistic-but-synthetic data and wire the real backend in your own environment.&lt;/p&gt;

&lt;p&gt;What no tool can compress is the compliance program itself: writing your risk assessment, executing your BAAs, getting the pen test scheduled. Start those in parallel with engineering, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is React Native suitable for HIPAA-compliant apps?&lt;/strong&gt; Yes. Single codebase to native iOS and Android, access to Keychain and Keystore for hardware-backed encryption, biometric APIs for MFA, same TLS stack as native. HIPAA compliance is a property of your architecture and vendor stack, not your mobile framework.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need a BAA with Apple or Google to publish?&lt;/strong&gt; No. They don't process PHI on your behalf when they distribute your app. You do need to follow their health data policies, and you need BAAs with every cloud, analytics, notification, and backend vendor that actually handles PHI.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use Firebase?&lt;/strong&gt; Partially. Firebase Auth, Cloud Firestore, and Cloud Functions on Blaze are covered under the Google Cloud BAA when configured correctly. Firebase Analytics, Crashlytics on defaults, and Cloud Messaging with PHI payloads are not. Confirm each service against Google Cloud's current list.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens in a breach?&lt;/strong&gt; Notify affected individuals within 60 days, notify HHS (immediately for 500+ people, otherwise annually), and in some cases notify media in the affected state. OCR fines range from $137 to over $2 million per violation depending on culpability. Encrypted PHI that's lost is generally a much lower-risk event.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need HIPAA if only patients use my app?&lt;/strong&gt; If you collect health data directly from consumers and aren't acting on behalf of a covered entity, you may not technically be subject to HIPAA. But you're almost certainly subject to the FTC Health Breach Notification Rule, state laws (California's CMIA, Washington's My Health My Data Act), and user expectations. Most direct-to-consumer health apps build to HIPAA-equivalent standards anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part most teams get wrong
&lt;/h2&gt;

&lt;p&gt;HIPAA has a reputation for being expensive and slow because most teams treat compliance as a phase at the end of the project. It isn't. It's an architectural constraint you build in from day one, and once it's in your foundation the incremental cost is much smaller than the guides suggest.&lt;/p&gt;

&lt;p&gt;Scaffold fast so you get to testable UI in days. Choose a HIPAA-eligible stack from the start. Get BAAs signed while engineers are still building. Write the risk assessment early, not the week before your first customer's security review.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What's your stack?&lt;/strong&gt; Drop it in the comments and I'll flag which pieces will fail an audit. And if you've shipped a HIPAA app already, I want to know which vendor surprised you most by refusing (or agreeing) to sign a BAA.&lt;/p&gt;

</description>
      <category>hipaa</category>
      <category>reactnative</category>
      <category>healthcare</category>
      <category>startup</category>
    </item>
    <item>
      <title>The State of Mobile AI in 2026 — From Code Generation to Full App Creation</title>
      <dc:creator>Famitha M A</dc:creator>
      <pubDate>Thu, 06 Aug 2026 07:55:07 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/the-state-of-mobile-ai-in-2026-from-code-generation-to-full-app-creation-37ig</link>
      <guid>https://dev.to/rapidnative-ai/the-state-of-mobile-ai-in-2026-from-code-generation-to-full-app-creation-37ig</guid>
      <description>&lt;p&gt;In 2023, AI could complete a line of code. In 2026, AI can build the entire mobile app — screens, backend, auth, camera access, and a signed IPA ready for the App Store.&lt;/p&gt;

&lt;p&gt;That's not marketing copy, it's a specific technical shift, and it happened faster than most of us in mobile expected. This post walks through the four generations of AI coding tools, why mobile lagged the web by three years, and what specifically broke open in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four generations, briefly
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Gen 1 (2021-2023): Autocomplete.&lt;/strong&gt; Copilot. AI completes the next line. Human owns the file.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gen 2 (2023-2024): Single-file generation.&lt;/strong&gt; Cursor, Claude Code. AI generates entire files. Human is the integrator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gen 3 (2024-2025): Web full-stack.&lt;/strong&gt; Lovable, v0, Bolt. Prompt to deployed web app.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gen 4 (2026): Mobile end-to-end.&lt;/strong&gt; Prompt to submission-ready native React Native + Expo app.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every generation compressed the space between "I have an idea" and "users are running my app." Gen 4 is the first one where a non-engineer can complete that arc entirely for native mobile.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why mobile lagged the web
&lt;/h2&gt;

&lt;p&gt;Web AI moved fast because the web is forgiving. Mobile is not. Each of the following cost the ecosystem roughly a year:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Two platforms, not one.&lt;/strong&gt; React Native shares ~80% of code across iOS and Android. The other 20% is where the pain lives: permissions, keyboard behavior, safe areas, gestures, notches. A model has to reason about platform divergence explicitly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Native APIs are gated.&lt;/strong&gt; Camera on web is &lt;code&gt;getUserMedia&lt;/code&gt;. Camera on mobile is &lt;code&gt;expo-camera&lt;/code&gt; + permission strings in &lt;code&gt;app.json&lt;/code&gt; + runtime prompts + a config plugin. Any wrong step and it doesn't build.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Preview is genuinely hard.&lt;/strong&gt; Web preview is an iframe. Mobile preview needs Metro, a matching JS engine (Hermes), and streaming updates without a full rebuild.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The App Store is a wall.&lt;/strong&gt; Apple's Guideline 4.2 rejects "wrapped websites." Web-first tools with a "mobile export" button hit this and bounced. Gen 4 had to output real native code from day one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fullstack means fullstack.&lt;/strong&gt; Real mobile means auth that survives app suspend/resume, secure token storage in the OS keychain, offline sync, and a data layer that handles being genuinely offline. Gen 3 tools papered over most of this — they targeted the web, which rarely needs it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "full app creation" actually produces in 2026
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Output&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Screens&lt;/td&gt;
&lt;td&gt;React Native + navigation, gestures, safe areas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Redux / Zustand / Context, chosen by complexity&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backend&lt;/td&gt;
&lt;td&gt;Provisioned Supabase project + schema + RLS + types&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auth&lt;/td&gt;
&lt;td&gt;Keychain-backed session, deep links, sign-in flows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;Migrations, seed data, offline-first where relevant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device&lt;/td&gt;
&lt;td&gt;Camera, GPS, push, biometrics with correct config plugins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Preview&lt;/td&gt;
&lt;td&gt;QR-code preview on real iOS and Android devices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export&lt;/td&gt;
&lt;td&gt;Clean React Native + Expo source, runs anywhere&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ship&lt;/td&gt;
&lt;td&gt;Signed IPA/AAB, submitted to App Store + Play&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every row above is a place a Gen 3 "mobile export" tool fails. Every row is now Gen 4 table stakes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four unlocks that made 2026 different
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Frontier models got good at multi-file reasoning.&lt;/strong&gt; The current generation of frontier models can hold an entire codebase in context and reason about change propagation. Prerequisite for full app gen.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Streaming output made iteration feel instant.&lt;/strong&gt; SSE + progressive component mounting turns "AI generation" from a batch job into a conversation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;LLM-native tooling replaced ported IDEs.&lt;/strong&gt; Virtual filesystems the AI writes into, sandboxed runners that typecheck and lint before feedback loops back to the model, multi-step LLM pipelines. The model is the CPU; the surrounding system is the OS.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Someone had to actually build the mobile-specific infrastructure.&lt;/strong&gt; This is the moat. Producing an Expo project that installs, builds, previews on a real device, submits to a store, and comes back with a valid receipt is a stack of ten problems requiring ten specialists.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Checklist: is a tool actually Generation 4?
&lt;/h2&gt;

&lt;p&gt;If you're evaluating tools in 2026, ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does the output run on a real phone via QR code, or just in a browser iframe?&lt;/li&gt;
&lt;li&gt;Does the tool generate backend + auth in the same prompt as the frontend?&lt;/li&gt;
&lt;li&gt;Can you export the code and run it locally without the vendor?&lt;/li&gt;
&lt;li&gt;Does the tool handle App Store submission or stop at "here's your code"?&lt;/li&gt;
&lt;li&gt;Does the tool support device features — camera, push, GPS, biometrics — out of the box?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most tools calling themselves "mobile AI app builders" in 2026 fail at least two. The ones that pass are the ones defining what AI mobile app development means for the rest of the decade.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for developers
&lt;/h2&gt;

&lt;p&gt;Senior mobile engineers have not been displaced — the ceiling of what you can build solo has moved up, not down. The mechanical work (screen scaffolding, auth boilerplate, Expo config, navigation setup) collapses to a prompt. The architectural work (data modeling, performance profiling, complex state machines, offline sync semantics) is still where craftsmanship compounds.&lt;/p&gt;

&lt;p&gt;The developers who ship the most in 2026 are the ones who treat AI as a code CPU and spend their time on the parts that matter.&lt;/p&gt;




&lt;p&gt;Try it out: &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=mobile-ai-2026-the-shift-from-code-generation-to-full-app-creation" rel="noopener noreferrer"&gt;rapidnative.com&lt;/a&gt; — describe an app in plain English, get a real React Native + Expo app on your phone in minutes. Free tier is 20 credits, no credit card.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>mobile</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Build a Pet Care App with React Native (2026)</title>
      <dc:creator>Mark F A</dc:creator>
      <pubDate>Fri, 31 Jul 2026 09:29:50 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/how-to-build-a-pet-care-app-with-react-native-2026-69i</link>
      <guid>https://dev.to/rapidnative-ai/how-to-build-a-pet-care-app-with-react-native-2026-69i</guid>
      <description>&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pet care is a well-scoped React Native project: bounded MVP, cross-platform users, forgiving early adopters.&lt;/li&gt;
&lt;li&gt;Stack: Expo SDK 52+, TypeScript, Expo Router, Supabase, NativeWind, &lt;code&gt;expo-notifications&lt;/code&gt;, FlashList.&lt;/li&gt;
&lt;li&gt;Store &lt;code&gt;next_due_at&lt;/code&gt; on the vaccination row, not derived — vaccination schedules vary by jurisdiction.&lt;/li&gt;
&lt;li&gt;Store UTC in the DB, render local with &lt;code&gt;date-fns-tz&lt;/code&gt;, and reschedule reminders on &lt;code&gt;AppState.change&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Honest timeline: 4–6 weeks solo, most of it boilerplate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pet care market keeps growing, and mobile apps are eating an increasing share of it — vet booking, vaccination reminders, feeding schedules. If you've been looking for a well-scoped React Native project to ship, a pet care app is genuinely one of the best options: bounded feature set, cross-platform relevance, and a user base that's forgiving of a rough v1 as long as it solves a real problem.&lt;/p&gt;

&lt;p&gt;Here's the practical build guide, from stack decisions through App Store submission.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why React Native fits pet care apps
&lt;/h2&gt;

&lt;p&gt;Pet care apps need cross-platform reach (pet owners split evenly between iOS and Android), rich UI (photo-heavy profiles), and native features like push notifications and camera — the exact sweet spot for React Native with Expo. They also have a naturally scoped MVP: pet profiles, health records, appointments, reminders. You can ship in weeks, not months.&lt;/p&gt;

&lt;h2&gt;
  
  
  The MVP feature set
&lt;/h2&gt;

&lt;p&gt;Ship these five features first. Adding a sixth before nailing edge cases (multi-species schedules, deleted pets, timezone-sensitive reminders) is the fastest way to never launch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pet profile&lt;/strong&gt; — name, species, breed, DOB, weight, photo, microchip ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health record&lt;/strong&gt; — vaccinations with next-due dates, medications, allergies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Appointment tracker&lt;/strong&gt; — vet visits, grooming, boarding with reminders&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feeding log&lt;/strong&gt; — food type, portion, schedule&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Push notifications&lt;/strong&gt; — vaccinations, medications, appointments&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React Native&lt;/strong&gt; with &lt;strong&gt;Expo SDK 52+&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TypeScript&lt;/strong&gt; (non-negotiable for anything you ship)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expo Router&lt;/strong&gt; for file-based routing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Supabase&lt;/strong&gt; for hosted Postgres + auth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NativeWind&lt;/strong&gt; for Tailwind-style styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;expo-notifications&lt;/strong&gt; for local push&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FlashList&lt;/strong&gt; (not &lt;code&gt;FlatList&lt;/code&gt;) for photo-heavy timelines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Scaffold it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx create-expo-app@latest pet-care-app &lt;span class="nt"&gt;--template&lt;/span&gt;
&lt;span class="c"&gt;# choose "Navigation (TypeScript)"&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;pet-care-app
npx expo &lt;span class="nb"&gt;install &lt;/span&gt;expo-notifications expo-image-picker expo-file-system
npm &lt;span class="nb"&gt;install &lt;/span&gt;nativewind zustand @supabase/supabase-js date-fns
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Route structure
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;app/
  (tabs)/
    _layout.tsx           # Pets | Schedule | Records | Settings
    index.tsx             # pet list
    schedule.tsx
    records.tsx
    settings.tsx
  pet/
    [id].tsx
    [id]/health.tsx
    [id]/appointments.tsx
  appointment/new.tsx
  vaccination/new.tsx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decide the route tree before writing screens. Nesting everything under &lt;code&gt;(tabs)/pets/[id]/...&lt;/code&gt; will bite you when you need to push a full-screen modal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data model
&lt;/h2&gt;

&lt;p&gt;Four entities cover the MVP:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entity&lt;/th&gt;
&lt;th&gt;Key fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;pets&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;id, owner_id, name, species, breed, date_of_birth, weight_kg, photo_url&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;vaccinations&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;id, pet_id, name, administered_at, next_due_at&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;appointments&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;id, pet_id, type, starts_at, location, notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feedings&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;id, pet_id, food_name, portion_grams, scheduled_at, given&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two things matter more than you'd expect:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Store &lt;code&gt;next_due_at&lt;/code&gt; on the row.&lt;/strong&gt; Rabies is annual in some jurisdictions, triennial in others. Don't hardcode.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Store UTC in the DB, render local with &lt;code&gt;date-fns-tz&lt;/code&gt;.&lt;/strong&gt; Get this wrong and a reminder set for 8 AM triggers at 3 AM after a flight.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Push notifications the right way
&lt;/h2&gt;

&lt;p&gt;The single feature that separates useful pet apps from forgettable ones:&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="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;Notifications&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;expo-notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduleNotificationAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pet&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="s2"&gt;'s &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;vaccination&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="s2"&gt; is due`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Book a vet appointment to stay on schedule.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;petId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;pet&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;vaccinationId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;vaccination&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;date&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;nextDueAt&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;Two rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request permissions gracefully — show a "why" screen before the OS prompt.&lt;/li&gt;
&lt;li&gt;Reschedule on &lt;code&gt;AppState.change&lt;/code&gt; when the app foregrounds. Timezone/DST drift is real.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The 4–6 week problem
&lt;/h2&gt;

&lt;p&gt;Honest timeline for a solo developer: 4–6 weeks for a shippable MVP if you're comfortable with React Native. 10–12 weeks if you're learning Expo/TypeScript/Supabase alongside.&lt;/p&gt;

&lt;p&gt;Most of that time is boilerplate — layout, navigation wiring, spacing, empty states, forms. None of it is the interesting part of building a pet care app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI-first shortcut
&lt;/h2&gt;

&lt;p&gt;Tools like &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=how-to-build-a-pet-care-app-with-react-native" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; generate a working React Native + Expo codebase from a natural-language description. The workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Describe the app: &lt;em&gt;"A pet care app with pet profiles, vaccination tracking, appointment reminders, and a schedule tab across all pets."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Get a working app in a few minutes.&lt;/li&gt;
&lt;li&gt;Scan a QR code, preview on your real phone.&lt;/li&gt;
&lt;li&gt;Click any element to describe changes ("make these cards bigger, add a soft green tint when a task is done").&lt;/li&gt;
&lt;li&gt;Export the full React Native + Expo source, or publish directly to the stores.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same output — a real React Native app — different path. Worth trying on the boring 80% of the build so you can spend time on the interesting 20%.&lt;/p&gt;

&lt;h2&gt;
  
  
  App Store gotchas specific to pet apps
&lt;/h2&gt;

&lt;p&gt;Three things reviewers catch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Health claims.&lt;/strong&gt; Track vaccinations/meds without positioning as a medical device. Be explicit in the description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Background location.&lt;/strong&gt; If you add walk tracking, justify the permission concretely: &lt;em&gt;"To log your dog's walk route while your phone is in your pocket."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Screenshots.&lt;/strong&gt; Empty-state screenshots underperform dramatically. Show real pets with real data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Use &lt;code&gt;eas submit&lt;/code&gt; — it handles both stores and eliminates most rejection loops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The features are the easy part. Reliable reminders, offline-first data, and fast photo handling are what separate 5-star pet apps from abandoned ones. Nail those, ship a focused MVP, and let owners tell you what's missing.&lt;/p&gt;

&lt;p&gt;If you've shipped a pet app — or any app where reminders are the core value — how did you handle timezone drift on scheduled notifications? Drop a comment, I'd like to hear what actually held up in production.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Build a News Aggregator App with AI Recommendations (React Native + Expo)</title>
      <dc:creator>Mark F A</dc:creator>
      <pubDate>Wed, 29 Jul 2026 06:43:33 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/build-a-news-aggregator-app-with-ai-recommendations-react-native-expo-207n</link>
      <guid>https://dev.to/rapidnative-ai/build-a-news-aggregator-app-with-ai-recommendations-react-native-expo-207n</guid>
      <description>&lt;p&gt;The average reader engages with about 4% of the news they scroll past. The remaining 96% is duplication, noise, or algorithmically boosted outrage. That gap is the whole reason a well-built AI news aggregator still has room in the market — and thanks to modern tooling, it's now a weekend project instead of a quarter-long roadmap.&lt;/p&gt;

&lt;p&gt;Here's the architecture that keeps showing up in successful indie news apps, plus a shortcut for shipping one fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four layers you actually need
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion&lt;/strong&gt; — NewsAPI, RSS, publisher APIs, GDELT.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understanding&lt;/strong&gt; — embeddings, categorization, dedup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ranking&lt;/strong&gt; — hybrid: content + collaborative + recency decay + diversity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Presentation&lt;/strong&gt; — reader mode, saves, offline, push, deep links.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Skip any layer and you have a directory nobody opens. Nail all four and you have a daily habit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reference tech stack
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client&lt;/td&gt;
&lt;td&gt;React Native + Expo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feed API&lt;/td&gt;
&lt;td&gt;NewsAPI + curated RSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;Postgres (Supabase)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Embeddings&lt;/td&gt;
&lt;td&gt;OpenAI &lt;code&gt;text-embedding-3-small&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Vector search&lt;/td&gt;
&lt;td&gt;pgvector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Push&lt;/td&gt;
&lt;td&gt;Expo Notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Offline&lt;/td&gt;
&lt;td&gt;AsyncStorage&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Nothing exotic. The hard part is the product decisions, not the pieces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building it fast
&lt;/h2&gt;

&lt;p&gt;The traditional way: &lt;code&gt;npx create-expo-app&lt;/code&gt;, set up navigation, build 12 screens by hand, wire the API, add state management, style everything, then start on the ranker.&lt;/p&gt;

&lt;p&gt;The 2026 way: describe the app in a prompt to an AI app builder (I've been using &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=build-news-aggregator-app-ai-recommendations" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; for this kind of thing) and get a working Expo project as output — real code, exportable, no lock-in. Then hand-tune the parts that actually matter (the ranker, the sources, the cold-start UX).&lt;/p&gt;

&lt;p&gt;A prompt that produces a strong first version:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a news aggregator app called Skim. Vertical feed of article cards with hero image, source logo, headline, summary. Top pill row for categories: For You, Top, Tech, Business, Science, Sports. Tap opens a reader-mode view with share, save, and "More like this." Bottom tabs: Feed, Search, Saved, Profile. Dark theme, pull-to-refresh, shimmer loading.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You get a working multi-screen app in about a minute, previewable on your phone via QR.&lt;/p&gt;

&lt;h2&gt;
  
  
  The recommendation engine
&lt;/h2&gt;

&lt;p&gt;Once you have a live feed, add three ranking signals:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content-based.&lt;/strong&gt; Embed each article. Store a rolling user "taste vector" as the mean of embeddings of articles they read/saved/lingered on. Cosine-similarity a new article against the user vector. Solves cold-start well.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Collaborative.&lt;/strong&gt; Once you have enough interaction data, "users who read X also read Y" is often stronger than pure text similarity — it captures fun, authority, contrarianism, things the text doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Recency + diversity re-ranking.&lt;/strong&gt; Multiply scores by &lt;code&gt;exp(-age_hours / half_life)&lt;/code&gt; with a 12–24h half-life. Then, when selecting the top N, penalize each candidate by its similarity to already-selected items. This is what stops the feed from becoming five versions of the same story.&lt;/p&gt;

&lt;p&gt;Rough weights I've seen work: &lt;code&gt;0.4 * content + 0.3 * collab + 0.2 * recency + 0.1 * diversity&lt;/code&gt;. Tune with real data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cold-start UX matters more than ranker quality
&lt;/h2&gt;

&lt;p&gt;The first five swipes decide whether a new user comes back tomorrow. Default to a strong editorial mix (top stories across categories) on session 1–2. Bring personalization online after session 3. Every good news app I've studied does some version of this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The retention features people underinvest in
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Save-for-later with offline caching (people read on the subway)&lt;/li&gt;
&lt;li&gt;"Why am I seeing this?" per-story explainer&lt;/li&gt;
&lt;li&gt;Explicit topic mute/follow controls&lt;/li&gt;
&lt;li&gt;Weekly digest push or email&lt;/li&gt;
&lt;li&gt;Notifications capped at 3–5/day, gated on the user's taste vector&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Copyright, briefly
&lt;/h2&gt;

&lt;p&gt;Show headline + short summary + hero image + link back. Don't cache full article bodies at scale without a license. Reader mode is fine if fetched on-device (Instapaper pattern).&lt;/p&gt;

&lt;h2&gt;
  
  
  The full workflow
&lt;/h2&gt;

&lt;p&gt;If you want to see this in action end-to-end, RapidNative turns the prompt above into a real Expo project you can extend. Free tier is 20 credits, no card. The exported code is standard Expo — you own it.&lt;/p&gt;

&lt;p&gt;Happy to answer questions on the ranker specifics in the comments.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>A Design System for Push Notifications: 12 Components, 3 States, One Figma File</title>
      <dc:creator>Mark F A</dc:creator>
      <pubDate>Wed, 29 Jul 2026 06:18:29 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/a-design-system-for-push-notifications-12-components-3-states-one-figma-file-408f</link>
      <guid>https://dev.to/rapidnative-ai/a-design-system-for-push-notifications-12-components-3-states-one-figma-file-408f</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Push is the surface users see 20x a week — it belongs in the design system, not in a marketing sprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;12 notification archetypes&lt;/strong&gt; cover almost every app: social-mention, social-activity, DM, group-message, transaction-confirm, transaction-update, reminder-user-set, reminder-streak, security-alert, content-drop, system-status, promotional.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3 states get skipped every time:&lt;/strong&gt; grouped, expanded, and the lock-screen/Notification-Center/banner size variants.&lt;/li&gt;
&lt;li&gt;The handoff artifact is a &lt;strong&gt;YAML file per component&lt;/strong&gt; that maps 1:1 to an &lt;code&gt;expo-notifications&lt;/code&gt; payload. If code diverges from the YAML, code review fails.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;weekly 30-minute notification review&lt;/strong&gt; catches copy drift before users do.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Most design systems stop at the app icon. Push notifications live outside the app, on the lock screen, in the notification shade, in the app-switcher preview — and they get designed ad-hoc, by whoever is writing the marketing send that week. Then a user sees seven notifications from your brand and none of them look like they came from the same company.&lt;/p&gt;

&lt;p&gt;Six months ago I built a push-notification design system for a React Native app the team was scaling from 20k to 200k users. Twelve components, three states each, one Figma file, and a handoff spec that maps every component to a specific expo-notifications payload shape. Here's the whole thing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Push Notifications Belong in the Design System
&lt;/h2&gt;

&lt;p&gt;Push is the most visible surface of your product for users who aren't in the app. If your in-app UI is polished but your notifications look like they were written by a bot, the notification is what users remember — it's the surface they see 20 times a week, versus the in-app UI they see three times.&lt;/p&gt;

&lt;p&gt;When push isn't in the design system, three things break:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Copy voice drifts.&lt;/strong&gt; Marketing writes chirpy, engineering writes terse, and the same user gets both.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Icon and color usage is inconsistent.&lt;/strong&gt; Some notifications use the brand accent, some use system defaults, some use whatever emoji the sender liked.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rich media (images, actions, replies) get added case-by-case,&lt;/strong&gt; with no consistent affordance for users to know what interactions are available.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A design system fixes all three by making the notification a first-class component with reviewable specs, not an afterthought at the end of a marketing sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 12 Components
&lt;/h2&gt;

&lt;p&gt;After working across four apps, the same twelve notification archetypes keep showing up. Design one component per archetype:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Social-mention&lt;/strong&gt; — someone tagged/mentioned/replied to the user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social-activity&lt;/strong&gt; — someone the user follows did something.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct-message&lt;/strong&gt; — a 1:1 message from another user.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group-message&lt;/strong&gt; — a message in a group, showing group name.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction-confirm&lt;/strong&gt; — order placed, payment received.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transaction-update&lt;/strong&gt; — delivery moved, ride arrived, booking changed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reminder-user-set&lt;/strong&gt; — the alarm/reminder the user configured.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reminder-streak&lt;/strong&gt; — 'you're one workout from your streak' style.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security-alert&lt;/strong&gt; — new login, fraud check, 2FA code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content-drop&lt;/strong&gt; — new episode, new article from a followed source.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System-status&lt;/strong&gt; — app update available, service outage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Promotional&lt;/strong&gt; — offer, discount, event (use sparingly).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each component gets its own frame in Figma with a filled-in example, a spec sheet, and links to the two engineers who own the code path that sends it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three States Designers Forget
&lt;/h2&gt;

&lt;p&gt;Designers default to designing the single-notification-on-lock-screen state. Then production shows three other states that were never designed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grouped state (iOS + Android).&lt;/strong&gt; When your app sends 3+ notifications in a session, the OS collapses them into a stack. The stack header is 'YourApp' — but if you set &lt;code&gt;threadIdentifier&lt;/code&gt; (iOS) or &lt;code&gt;channelId&lt;/code&gt; + &lt;code&gt;group&lt;/code&gt; (Android), you get semantic grouping like '4 messages from Sarah.' Design what those group headers look like. Design what happens at 15 notifications (do they collapse further?). Design the tap-to-expand animation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expanded state.&lt;/strong&gt; Long-press on iOS or swipe-down on Android reveals the expanded notification, which can show images, quick-reply fields, action buttons. Most apps ship the default (a bigger version of the collapsed view). You can do better: this is prime real estate for a mini-interaction that doesn't require opening the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lock-screen preview vs Notification Center vs Banner.&lt;/strong&gt; Same content, three different sizing constraints. The banner (top of screen when phone is unlocked) truncates at ~40 chars. The lock-screen preview shows 2-3 lines. Notification Center shows 4-5 lines and stacks. Design the copy for the tightest constraint first, then progressively enhance for the roomier surfaces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Figma-to-Code Handoff That Actually Survives
&lt;/h2&gt;

&lt;p&gt;The handoff spec I ship looks like this — one file per component, machine-readable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;social-mention&lt;/span&gt;
&lt;span class="na"&gt;trigger&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;user_mentioned_in_comment&lt;/span&gt;
&lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{actor.display_name}}&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;mentioned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;you"&lt;/span&gt;
  &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{excerpt}}"&lt;/span&gt;
  &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;deep_link&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;app://comments/{{comment_id}}"&lt;/span&gt;
    &lt;span class="na"&gt;actor_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{actor.id}}"&lt;/span&gt;
    &lt;span class="na"&gt;thread_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{parent_id}}"&lt;/span&gt;
&lt;span class="na"&gt;ios&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;category_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;SOCIAL_MENTION&lt;/span&gt;
  &lt;span class="na"&gt;thread_identifier&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mentions_{{parent_id}}"&lt;/span&gt;
  &lt;span class="na"&gt;interruption_level&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;active&lt;/span&gt;
  &lt;span class="na"&gt;relevance_score&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0.7&lt;/span&gt;
&lt;span class="na"&gt;android&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;channel_id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;social_mentions&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mentions&lt;/span&gt;
  &lt;span class="na"&gt;priority&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
&lt;span class="na"&gt;copy_rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;excerpt max 60 chars, ellipsize&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;never include reactor emojis in title&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;if actor is verified, prepend U+2713&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The React Native code that sends it maps 1:1:&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="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;Notifications&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;expo-notifications&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;sendMentionNotification&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="nl"&gt;actorName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;actorVerified&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;parentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actorVerified&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`\u2713 &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="nx"&gt;actorName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; mentioned you`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&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="nx"&gt;actorName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; mentioned you`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Notifications&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;scheduleNotificationAsync&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;truncate&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="nx"&gt;excerpt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;categoryIdentifier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;SOCIAL_MENTION&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;deep_link&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`app://comments/&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="nx"&gt;commentId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;actor_id&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="nx"&gt;actorName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;thread_id&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="nx"&gt;parentId&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="na"&gt;trigger&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="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 yaml file is the contract. If the code diverges from the yaml, code review fails. If a designer wants to change the notification, they change the yaml, which triggers a code PR. Everyone's working from the same spec, and copy can't drift silently.&lt;/p&gt;

&lt;p&gt;Wiring the channels, categories, and deep-link routing by hand is the part that eats a sprint — the &lt;a href="https://rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=push-notification-design-system" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; starters ship with the expo-notifications scaffolding and channel config already in place, so you can go straight to writing the specs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Weekly Notification Design Review
&lt;/h2&gt;

&lt;p&gt;Even with a design system, notification copy drifts. Marketing writes a one-off. Engineering hardcodes a fallback. QA never sees it because notifications only fire in production. Six months in, you have 40 slightly-off notifications your team can't remember shipping.&lt;/p&gt;

&lt;p&gt;Run a weekly 30-minute notification design review. The agenda:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Screenshot every notification the app sent in the last 7 days (via the log I mentioned above — instrument every send).&lt;/li&gt;
&lt;li&gt;Compare each against its yaml spec. Flag drift.&lt;/li&gt;
&lt;li&gt;Look at delivery + open rates per component. Retire any component with under 5% open rate for two weeks running.&lt;/li&gt;
&lt;li&gt;Discuss the copy of any new components proposed this week.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Thirty minutes, weekly. It's the single highest-leverage design meeting I've ever run — small enough that engineering shows up, focused enough that decisions actually get made, and it catches the drift before users notice.&lt;/p&gt;

&lt;p&gt;When we ship this notification design system in RapidNative starters, teams stop treating push as marketing exhaust and start treating it as a product surface. The difference shows up in retention within a quarter.&lt;/p&gt;




&lt;p&gt;How many of the 12 archetypes does your app actually send — and how many of them were designed on purpose? Drop a comment with the notification you know is drifting.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>expo</category>
      <category>design</category>
      <category>mobile</category>
    </item>
    <item>
      <title>How to Build a Booking App for Service Businesses with AI (2026 Guide)</title>
      <dc:creator>Mark F A</dc:creator>
      <pubDate>Wed, 29 Jul 2026 05:17:09 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/how-to-build-a-booking-app-for-service-businesses-with-ai-2026-guide-2791</link>
      <guid>https://dev.to/rapidnative-ai/how-to-build-a-booking-app-for-service-businesses-with-ai-2026-guide-2791</guid>
      <description>&lt;p&gt;If you've ever tried to spec out a booking app for a service business — salon, cleaning company, mobile mechanic, personal trainer — you know the "obvious" features hide 40 nasty edge cases. Buffer time. Timezones. Double-booking races. No-show economics. Reminders that don't wake people up at 3 am.&lt;/p&gt;

&lt;p&gt;This post walks through the actual engineering shape of a service-business booking app in 2026, and how AI app builders (&lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=how-to-build-a-booking-app-for-service-businesses" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt;, in the examples below) collapse the 8–14 week custom build into 1–3 days. The generated output is real React Native + Expo you own and export, not a wrapped webview.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 8 must-have features (skip the rest for v1)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Service catalog (name, duration, price)&lt;/li&gt;
&lt;li&gt;Real-time availability keyed by staff or resource&lt;/li&gt;
&lt;li&gt;Buffer time between appointments&lt;/li&gt;
&lt;li&gt;Deposits or full prepayment (single biggest no-show reducer)&lt;/li&gt;
&lt;li&gt;Push + SMS reminders (24h push, 2h SMS)&lt;/li&gt;
&lt;li&gt;One-tap reschedule / cancel with a policy window&lt;/li&gt;
&lt;li&gt;Customer profile with past appointments&lt;/li&gt;
&lt;li&gt;Staff / admin view for daily calendar and blocking time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Gift cards, packages, loyalty, in-app chat — all v2. Ship v1 first.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five availability checks nobody documents
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;is_bookable(slot_time T, staff S, service_duration D, buffer B)&lt;/code&gt; returns true iff:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;[T, T + D + B]&lt;/code&gt; sits inside &lt;code&gt;S.working_hours&lt;/code&gt; for that day&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;S&lt;/code&gt; has no other appointment overlapping &lt;code&gt;[T − B, T + D + B]&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;S&lt;/code&gt; has not personally blocked that window&lt;/li&gt;
&lt;li&gt;Date is not on the business's holiday list&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;T &amp;gt;= now() + min_lead_time&lt;/code&gt; (typically 60m)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Miss any one of these and a customer will discover it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The workflow (works for any AI builder)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Write a one-page spec.&lt;/strong&gt; Services, staff, hours, buffer, deposit, cancellation window, min-lead-time. This is the real bottleneck — not the code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Paste it as a prompt.&lt;/strong&gt; Example for a hair salon:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Build a booking app for "Wave &amp;amp; Co." salon. Services: Cut $45/45m, Cut+Color $150/120m, Blowout $60/45m. Stylists: Alex, Sam, Jamie. $10 Stripe deposit. Push reminder 24h out. My Bookings screen with cancel-if-&amp;gt;24h. Staff view per stylist.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;3. Preview on a real phone via QR.&lt;/strong&gt; Never trust a booking flow you haven't tapped through on-device. Calendar UX, tap targets, and keyboard behavior on a phone-number field only make sense on real hardware.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Point-and-edit instead of re-prompting.&lt;/strong&gt; Click an element in the preview and describe the change ("default the date picker to today"). &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=how-to-build-a-booking-app-for-service-businesses" rel="noopener noreferrer"&gt;RapidNative's point-and-edit mode&lt;/a&gt; preserves everything you already got right, which full regeneration does not.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Wire real integrations.&lt;/strong&gt; Stripe &lt;code&gt;PaymentIntents&lt;/code&gt; with &lt;code&gt;capture_method: manual&lt;/code&gt; for deposits held until no-show, plus Expo Push and Twilio (~$0.008/SMS in the US).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Ship it.&lt;/strong&gt; &lt;code&gt;eas build --platform all&lt;/code&gt; — Expo handles the code signing pain and publishes to both stores under your developer account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional dev vs AI-built (2026 numbers)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Path&lt;/th&gt;
&lt;th&gt;Time to v1&lt;/th&gt;
&lt;th&gt;Upfront&lt;/th&gt;
&lt;th&gt;You own code&lt;/th&gt;
&lt;th&gt;Your brand on stores&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agency custom build&lt;/td&gt;
&lt;td&gt;8–14 weeks&lt;/td&gt;
&lt;td&gt;$10k–$25k&lt;/td&gt;
&lt;td&gt;Depends on contract&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SaaS (Fresha/Booksy)&lt;/td&gt;
&lt;td&gt;1 day&lt;/td&gt;
&lt;td&gt;$0–$60/mo&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI app builder (&lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=how-to-build-a-booking-app-for-service-businesses" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt;)&lt;/td&gt;
&lt;td&gt;1–3 days&lt;/td&gt;
&lt;td&gt;Credit-based&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Production gotchas
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Timezones&lt;/strong&gt;: store every appointment in UTC, render in user's local, never trust the client clock for availability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;End-of-day buffer&lt;/strong&gt;: a 5:15pm 45m cut + 10m buffer ends 6:10pm — don't offer that slot if the shop closes at 6.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Race conditions&lt;/strong&gt;: booking creation must be transactional. Lock, verify, insert or fail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reminders&lt;/strong&gt;: never schedule a push for 3 am local. Clamp to &lt;code&gt;max(now+24h, 9am_local_appointment_day)&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No-show deposits&lt;/strong&gt;: default deposit to a real amount ($10–$25 consumer, more for high-ticket). Overriding it manually is fine; defaulting to zero is not.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Prompt templates for 6 niches
&lt;/h2&gt;

&lt;p&gt;Personal trainer, cleaning company, mobile mechanic, tutor, dog groomer, massage therapist — the full library is in the canonical post. They all share the same skeleton; the details (pricing, buffer, intake fields) are what generate different apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;If you want to skip straight to it: &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=how-to-build-a-booking-app-for-service-businesses" rel="noopener noreferrer"&gt;rapidnative.com&lt;/a&gt; — free tier, 20 credits, paste a prompt, get an app on your phone. Full walkthrough with all six prompt templates and the comparison table is on the &lt;a href="https://www.rapidnative.com/blogs/how-to-build-a-booking-app-for-service-businesses" rel="noopener noreferrer"&gt;canonical post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Happy building.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>React Native Performance Monitoring: Tools and Techniques for 2026</title>
      <dc:creator>Russel Dsouza</dc:creator>
      <pubDate>Tue, 28 Jul 2026 13:42:21 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/react-native-performance-monitoring-tools-and-techniques-for-2026-dh4</link>
      <guid>https://dev.to/rapidnative-ai/react-native-performance-monitoring-tools-and-techniques-for-2026-dh4</guid>
      <description>&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Flipper is gone&lt;/strong&gt; (removed as default in RN 0.76). React Native DevTools is the sanctioned replacement.&lt;/li&gt;
&lt;li&gt;Track &lt;strong&gt;eight metrics&lt;/strong&gt; — the two most under-monitored are &lt;strong&gt;slow frames %&lt;/strong&gt; and &lt;strong&gt;frozen frames %&lt;/strong&gt;, the closest proxy for "feels laggy."&lt;/li&gt;
&lt;li&gt;The pragmatic 2026 production default: &lt;strong&gt;Sentry&lt;/strong&gt; for crashes + performance, optionally layered with Firebase Crashlytics (free redundancy) or Datadog RUM (if you're already on Datadog).&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;New Architecture&lt;/strong&gt; shifts the culprits: TurboModule calls blocking the JS thread, and Fabric commit contention on the UI thread.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The React Native performance monitoring landscape looks nothing like it did two years ago. Flipper is gone as the default debugger. The New Architecture — Fabric, TurboModules, and the bridgeless runtime — is on by default in every fresh app. Hermes is the assumed engine.&lt;/p&gt;

&lt;p&gt;If you're shipping in 2026, you need a stack that reflects those changes: dev-time profilers that speak Perfetto and Hermes, production observability that captures slow frames per screen, and a workflow for turning that data into fixes before your App Store rating quietly drops from 4.6 to 4.1.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "performance" actually means in React Native
&lt;/h2&gt;

&lt;p&gt;React Native has a two-thread execution model, and every performance conversation lives inside it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The JS thread&lt;/strong&gt; — business logic, Redux reducers, &lt;code&gt;Animated&lt;/code&gt; orchestration, most third-party code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The UI (main) thread&lt;/strong&gt; — layout, drawing, gesture recognition, native module execution, &lt;code&gt;useNativeDriver&lt;/code&gt; animations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A janky animation is almost always a UI thread problem. A slow list scroll under load is usually a JS thread problem. A screen that takes three seconds to appear after a tap is likely JS thread — &lt;em&gt;unless&lt;/em&gt; it's blocking on a network call, in which case it's a waterfall issue. A frozen screen after login is often a JSI serialization stall.&lt;/p&gt;

&lt;p&gt;Monitoring means tracking both threads, tying the data to specific user journeys, and being able to answer three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is it slow?&lt;/strong&gt; — objective metrics on frame rate, TTI, startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Where is it slow?&lt;/strong&gt; — which screen, component, native module.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Why is it slow?&lt;/strong&gt; — flame graphs, network waterfalls, memory pressure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your setup only answers question one, you don't have monitoring — you have a smoke alarm.&lt;/p&gt;

&lt;h2&gt;
  
  
  The eight metrics that matter in 2026
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;What it measures&lt;/th&gt;
&lt;th&gt;Healthy target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JS FPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frame rate on the JS thread&lt;/td&gt;
&lt;td&gt;60 fps sustained (120 on ProMotion)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;UI FPS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frame rate on the native main thread&lt;/td&gt;
&lt;td&gt;60 fps sustained (120 on ProMotion)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cold start time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Icon tap to first interactive frame&lt;/td&gt;
&lt;td&gt;&amp;lt; 2.0s on mid-tier Android&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Warm start time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Time to interactive when backgrounded&lt;/td&gt;
&lt;td&gt;&amp;lt; 400ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;TTI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Screen mount → first tap-responsive frame&lt;/td&gt;
&lt;td&gt;&amp;lt; 1s per screen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Slow frames %&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frames rendered in &amp;gt; 16.67ms&lt;/td&gt;
&lt;td&gt;&amp;lt; 5% per session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Frozen frames %&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frames rendered in &amp;gt; 700ms&lt;/td&gt;
&lt;td&gt;&amp;lt; 0.1% per session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;JS bundle size&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Compressed bundle on first launch&lt;/td&gt;
&lt;td&gt;&amp;lt; 2MB gzip&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The two most under-monitored metrics are &lt;strong&gt;slow frames %&lt;/strong&gt; and &lt;strong&gt;frozen frames %&lt;/strong&gt; — the closest proxy for the qualitative "laggy." Sentry, Firebase, and Embrace expose them natively; if your stack doesn't, that's the first upgrade to make.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 2026 tooling stack
&lt;/h2&gt;

&lt;p&gt;Monitoring splits into two phases with different tools: &lt;strong&gt;dev-time profiling&lt;/strong&gt; (finding the problem locally) and &lt;strong&gt;production observability&lt;/strong&gt; (knowing it exists at all).&lt;/p&gt;

&lt;h3&gt;
  
  
  Development-time tools
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React Native DevTools&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;JS profiling, network, component inspector&lt;/td&gt;
&lt;td&gt;Default since RN 0.76. Replaces Flipper.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hermes Sampling Profiler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Deep JS CPU flame graphs&lt;/td&gt;
&lt;td&gt;Export as &lt;code&gt;.cpuprofile&lt;/code&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Perfetto / Systrace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;System-wide traces across all threads&lt;/td&gt;
&lt;td&gt;Android. Best for bridge/native contention.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Xcode Instruments&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;iOS CPU, memory, energy, Core Animation&lt;/td&gt;
&lt;td&gt;Time Profiler + Animation Hitches.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Android Studio Profiler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Java/Kotlin allocations, native memory&lt;/td&gt;
&lt;td&gt;Pair with Perfetto.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;React DevTools Profiler&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Render counts, wasted renders&lt;/td&gt;
&lt;td&gt;Ships with RN DevTools.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If a tutorial tells you to install Flipper, it's out of date. Flipper was removed as the default debugger in RN 0.76 and the community plugins are largely unmaintained. React Native DevTools is the replacement, and it's better for JS profiling.&lt;/p&gt;

&lt;h3&gt;
  
  
  Production observability
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Strengths&lt;/th&gt;
&lt;th&gt;Watch out for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sentry&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Crash reporting, integrated perf traces, native + JS stack merging&lt;/td&gt;
&lt;td&gt;Sampling costs add up at scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Firebase Performance&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free, Crashlytics integration, custom traces&lt;/td&gt;
&lt;td&gt;Sparse UI, no session replay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Datadog RUM&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Backend APM correlation, cohort analysis&lt;/td&gt;
&lt;td&gt;Expensive at high MAU&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Embrace&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Session-first, full journey replay&lt;/td&gt;
&lt;td&gt;Smaller ecosystem&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Instabug&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bug reporting + perf in one SDK&lt;/td&gt;
&lt;td&gt;Duplicates Sentry if you have it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;New Relic Mobile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Enterprise-grade infra correlation&lt;/td&gt;
&lt;td&gt;Heavier SDK&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The pragmatic default for most teams: &lt;strong&gt;Sentry for crashes + performance + source-mapped traces&lt;/strong&gt;, optionally with &lt;strong&gt;Firebase Crashlytics&lt;/strong&gt; as a free redundant crash pipeline, or &lt;strong&gt;Datadog RUM&lt;/strong&gt; if you're already on Datadog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Minimum-viable production setup
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Install Sentry with performance monitoring.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @sentry/wizard@latest &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-i&lt;/span&gt; reactNative
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set &lt;code&gt;tracesSampleRate: 0.2&lt;/code&gt; and enable &lt;code&gt;enableNativeFramesTracking: true&lt;/code&gt; and &lt;code&gt;enableAutoPerformanceTracing: true&lt;/code&gt;. Native frames tracking is what gives you slow/frozen frame percentages per screen.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Instrument navigation.&lt;/strong&gt; Wrap your app with Sentry's navigation integration so every screen transition becomes a labelled transaction. Without this, traces are anonymous and useless.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Upload source maps on every release.&lt;/strong&gt; Untranslated stack traces are worthless. Bake source map upload into CI so every TestFlight or Play build has readable traces waiting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Add custom spans around slow operations.&lt;/strong&gt; Any function over 100ms — cache hydration, a DB query, a large image decode — gets a manual span. This turns "screen X is slow sometimes" into "screen X is slow because recipe hydration takes 800ms on cold launch."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Set alerts for regressions:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Crash-free session rate below 99.5%&lt;/li&gt;
&lt;li&gt;P75 cold start rising &amp;gt; 20% between releases&lt;/li&gt;
&lt;li&gt;Slow frame % above 8% on any top-10 screen&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A real diagnostic walkthrough: "the feed screen is laggy"
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Confirm in production data
&lt;/h3&gt;

&lt;p&gt;Filter to the screen, look at P75 and P95 of slow frames per session. If P75 is fine and P95 is bad, it's a device-tier issue — likely mid-tier Android under a specific data condition. Note the device and OS distribution of the worst sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Reproduce on the right hardware
&lt;/h3&gt;

&lt;p&gt;Never diagnose Android performance on an iPhone 15 Pro. Grab a Pixel 6a or a mid-tier Samsung and reproduce there. Enable the perf overlay for live JS FPS and UI FPS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Capture a trace
&lt;/h3&gt;

&lt;p&gt;For JS-thread issues: React Native DevTools → Performance tab → record the interaction. Look for long JS tasks (&amp;gt; 50ms bars), high re-render counts, serialization work.&lt;/p&gt;

&lt;p&gt;For UI-thread issues on Android: capture a Perfetto trace. Look for main-thread frames overflowing 16.67ms, and whether the overflow is in &lt;code&gt;Choreographer#doFrame&lt;/code&gt; or in native module calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Isolate the offender
&lt;/h3&gt;

&lt;p&gt;Common culprits, ranked by frequency in 2026 codebases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A &lt;code&gt;FlatList&lt;/code&gt; with heavy &lt;code&gt;renderItem&lt;/code&gt; work&lt;/strong&gt; — no memoization, inline arrows. Fix: memoize the row, stabilize props, add &lt;code&gt;getItemLayout&lt;/code&gt; if heights are known.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A large state slice re-rendering the whole tree&lt;/strong&gt; — a selector returning new object identity every time. Fix: narrow the selector, add shallow comparison.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An image decode blocking the UI thread&lt;/strong&gt; — full-res images in small views. Fix: server-side resize, &lt;code&gt;expo-image&lt;/code&gt; with &lt;code&gt;contentFit&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A synchronous &lt;code&gt;AsyncStorage&lt;/code&gt; migration on cold start&lt;/strong&gt; — reading 5MB before render. Fix: defer via &lt;code&gt;InteractionManager.runAfterInteractions&lt;/code&gt;, or MMKV with lazy hydration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A third-party analytics SDK spawning threads on startup&lt;/strong&gt; — 200–400ms blocks. Fix: initialize lazily after first frame.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Step 5: Verify in a canary
&lt;/h3&gt;

&lt;p&gt;Ship behind a flag or to a beta cohort. Compare P75 slow-frame % on the screen before and after. If the number doesn't move, the fix isn't the fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Architecture changes what you monitor
&lt;/h2&gt;

&lt;p&gt;Under the old bridge, the biggest sinkholes were JSON serialization between JS and native, visible in traces as huge queues. The New Architecture's synchronous, typed JSI calls mostly remove that class of problem — but introduce two new ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Synchronous TurboModule calls that block the JS thread.&lt;/strong&gt; A native module doing 50ms of I/O now blocks JS directly, where it used to buffer through the async bridge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fabric commit contention on the UI thread.&lt;/strong&gt; Shadow-tree commits are more efficient on average but spike sharply when a large tree diff lands — watch navigation transitions where the whole screen tree re-mounts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Modern Sentry SDKs instrument both Fabric commits and TurboModule calls when auto-performance tracing is on. On an older SDK, upgrade — you'll see spans you didn't have before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bundle size and startup — the fixes that compound
&lt;/h2&gt;

&lt;p&gt;Startup time is the metric most correlated with day-1 retention, and the one most engineers stop optimizing after the first release.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Enable Hermes.&lt;/strong&gt; If you're not on it, you're leaving 30–50% of cold-start performance on the table.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Precompile Hermes bytecode.&lt;/strong&gt; Ship &lt;code&gt;.hbc&lt;/code&gt;, not &lt;code&gt;.js&lt;/code&gt;. RN does this for release builds — verify it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ship inline requires on Android.&lt;/strong&gt; &lt;code&gt;inlineRequires: true&lt;/code&gt; in Metro defers module evaluation until first use, cutting 200–500ms on large apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Split third-party SDKs.&lt;/strong&gt; Anything not needed in the first second — analytics, remote config, feature flags — initializes after &lt;code&gt;InteractionManager.runAfterInteractions&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure with production RUM, not local.&lt;/strong&gt; Cold start on your dev machine is meaningless. Trust P75 from real devices.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Track JS bundle size release-over-release. A 100KB regression per release compounds fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Best free monitoring tool in 2026?&lt;/strong&gt; Firebase Performance + Crashlytics. Automatic startup and HTTP traces, custom traces, native crash reporting, all free up to a generous tier. Limitations: sparse UI, no session replay — most teams add Sentry for deeper diagnostics.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Flipper still usable?&lt;/strong&gt; No — removed as default in RN 0.76, plugins unmaintained. Use React Native DevTools, plus Perfetto (Android) and Xcode Instruments (iOS) for native profiling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How do I measure startup time in production?&lt;/strong&gt; Sentry's &lt;code&gt;enableAppStartTracking: true&lt;/code&gt;, or Firebase's automatic &lt;code&gt;_app_start&lt;/code&gt; trace. Track &lt;strong&gt;P75 cold start&lt;/strong&gt; — the average hides regressions behind a few fast flagship sessions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What FPS should an app maintain?&lt;/strong&gt; 60 fps sustained on both threads (120 on ProMotion). Track slow frames (&amp;gt; 16.67ms) as a percentage and aim under 5% per session.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do I need separate monitoring for the New Architecture?&lt;/strong&gt; Same metrics, different culprits — TurboModule calls and Fabric commit contention. Any modern SDK instruments both automatically; just don't run a pre-GA-era SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;The dev-time story is React Native DevTools plus platform-native profilers (Perfetto, Instruments). The production story is Sentry as the default, with Firebase Crashlytics as free redundancy or Datadog RUM for existing Datadog teams.&lt;/p&gt;

&lt;p&gt;The bigger shift is philosophical: performance is a metric you monitor continuously, not a project you do once. The teams shipping the smoothest apps in 2026 have slow-frame alerts wired to Slack, source maps on every release, and a P75 startup dashboard someone actually looks at each Monday.&lt;/p&gt;

&lt;p&gt;I write more about how we handle this in &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=rn-performance-monitoring" rel="noopener noreferrer"&gt;AI-generated React Native apps at RapidNative&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;What's your current monitoring stack — and what's the worst "laggy screen" bug you've tracked down? Mine was a 5MB AsyncStorage migration running on every cold start.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>performance</category>
      <category>mobile</category>
      <category>webdev</category>
    </item>
    <item>
      <title>How to Prototype a Mobile App in Hours (Wireframe React Native)</title>
      <dc:creator>Mark F A</dc:creator>
      <pubDate>Fri, 24 Jul 2026 12:06:54 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/how-to-prototype-a-mobile-app-in-hours-wireframe-react-native-1aei</link>
      <guid>https://dev.to/rapidnative-ai/how-to-prototype-a-mobile-app-in-hours-wireframe-react-native-1aei</guid>
      <description>&lt;h1&gt;
  
  
  How to Prototype a Mobile App in Hours (Wireframe → React Native)
&lt;/h1&gt;

&lt;p&gt;TL;DR:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The wireframe → clickable-prototype → coded-prototype pipeline is now collapsible into a single step&lt;/li&gt;
&lt;li&gt;Four phases: capture wireframe (20-40 min) → generate base app (2-3 min) → iterate visually (1-2 hrs) → install on device via QR (30 sec)&lt;/li&gt;
&lt;li&gt;First testable build: 1-3 hours vs. 2-4 weeks traditional&lt;/li&gt;
&lt;li&gt;Includes an hour-by-hour log of a real 5-screen food-delivery MVP shipped in one afternoon&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The three artifacts most guides conflate
&lt;/h2&gt;

&lt;p&gt;Before we get into the workflow, one clarification that trips up most prototyping conversations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Wireframe&lt;/strong&gt; — static layout. No interactivity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clickable prototype&lt;/strong&gt; — hotspots wired between static screens (Figma, InVision). Can't run real logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Working prototype&lt;/strong&gt; — real React Native code, real navigation, real state, installable on device via QR.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production app&lt;/strong&gt; — the working prototype plus backend, auth, analytics, store listing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most "prototyping" tutorials stop at rung two. The interesting compression is going from rung one straight to rung three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The four-phase workflow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Capture the wireframe (20-40 min)
&lt;/h3&gt;

&lt;p&gt;Any medium that captures screens + elements + tap behavior works: hand sketch, whiteboard photo, Excalidraw, Figma frame, competitor screenshot, or a written paragraph. Treat the wireframe as disposable — its job is to be input to the generator, not a deliverable.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Generate the base app (2-3 min)
&lt;/h3&gt;

&lt;p&gt;Feed the wireframe + a one-paragraph description into an AI mobile app builder (&lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=blog&amp;amp;utm_campaign=mobile-app-prototyping-wireframe-to-working-app" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt; is what we use — sketch/prompt/screenshot → React Native + Expo → QR preview in one editor, free tier with 20 credits and no card). You get back a live React Native + Expo app with routing, styled screens, and stubbed data. It's about 70% right — good enough to iterate against.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Iterate visually (1-2 hours)
&lt;/h3&gt;

&lt;p&gt;The base app has ~30% wrong. Point-and-edit is the accelerator here: click any element in the live preview, describe the change ("make cards rounder, move price to top-right"), and the underlying code updates. Product teams typically do 30-50 micro-iterations in the first hour.&lt;/p&gt;

&lt;p&gt;Two rules for this phase:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't accept the first version of any screen.&lt;/li&gt;
&lt;li&gt;Don't over-specify visuals before validating flow. Colors are cheap; navigation is expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Ship to real hardware (30 sec)
&lt;/h3&gt;

&lt;p&gt;Scan a QR code, install into a preview client on iPhone or Android, and use the app on real hardware. This is where the bugs Figma can't show you actually show up: thumb-tap zones, safe areas, keyboard behavior, dark mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Traditional vs AI-native — the timeline table
&lt;/h2&gt;

&lt;p&gt;For a 5-screen MVP:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Traditional&lt;/th&gt;
&lt;th&gt;AI-native&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Wireframing&lt;/td&gt;
&lt;td&gt;4-8 hours&lt;/td&gt;
&lt;td&gt;20-40 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hi-fi design&lt;/td&gt;
&lt;td&gt;2-5 days&lt;/td&gt;
&lt;td&gt;Skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Clickable prototype&lt;/td&gt;
&lt;td&gt;4-8 hours&lt;/td&gt;
&lt;td&gt;Skipped&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coded prototype&lt;/td&gt;
&lt;td&gt;1-3 weeks&lt;/td&gt;
&lt;td&gt;2-3 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Iteration cycle&lt;/td&gt;
&lt;td&gt;1-3 days per round&lt;/td&gt;
&lt;td&gt;30 sec to 2 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Real-device install&lt;/td&gt;
&lt;td&gt;4-8 hours (Xcode/TestFlight)&lt;/td&gt;
&lt;td&gt;30 sec (QR)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;First testable build&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2-4 weeks&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1-3 hours&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The compression is mostly the elimination of role handoffs. When one operator drives the whole loop, the "waiting for someone else" hours evaporate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which wireframe inputs convert best
&lt;/h2&gt;

&lt;p&gt;Ranked worst → best in signal-to-noise:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Napkin sketch photo&lt;/strong&gt; — works, but expect nuance loss. Pair with a written description.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Whiteboard photo&lt;/strong&gt; — better; whiteboards force larger, more legible drawings.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Excalidraw / FigJam&lt;/strong&gt; — excellent. Digital medium produces clean shapes and labels.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Figma frame&lt;/strong&gt; — very good. Layout and hierarchy transfer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Written PRD paragraph&lt;/strong&gt; — underrated. A precise 3-4 sentence description often beats a bad sketch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Competitor screenshot&lt;/strong&gt; — highest signal. Already a mobile UI, so layout, typography, and interaction all transfer.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Hour-by-hour: food-delivery MVP, one afternoon
&lt;/h2&gt;

&lt;p&gt;Two-person team, five screens (restaurant list → detail → cart → checkout → order tracking):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0:00-0:30&lt;/strong&gt; — Whiteboard sketch, phone photo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0:30-0:38&lt;/strong&gt; — Upload + one-paragraph description → base app generated.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;0:38-1:20&lt;/strong&gt; — First iteration pass. Point-and-edit ~40 changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1:20-1:35&lt;/strong&gt; — QR install on both team phones. Two nav bugs surface; fix in 3 min.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1:35-2:20&lt;/strong&gt; — Send QR to three test users, collect feedback in shared doc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;2:20-3:15&lt;/strong&gt; — Second iteration pass. Address top-10 UX notes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;3:15-3:30&lt;/strong&gt; — Publish shareable preview URL, drop in Slack.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three-and-a-half hours end-to-end, iOS + Android, on real phones.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to NOT use this workflow
&lt;/h2&gt;

&lt;p&gt;The AI loop isn't universal. Stay traditional when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Novel interaction patterns with no precedent (generators pattern-match; no pattern = no output quality).&lt;/li&gt;
&lt;li&gt;Regulated industries (medical, aviation, financial trading) that need documented decision trails.&lt;/li&gt;
&lt;li&gt;Pixel-perfect exec-review demos with no budget for a "70%-then-iterate" opening state.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the other 90% — CRUD apps, marketplaces, social, dashboards, internal tools — the compressed loop wins on speed, iteration count, and cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;The whole compression comes down to one operator driving one loop: sketch it, generate it, poke at it on a real phone, repeat. Curious how the four phases feel end-to-end for others — drop a comment with what you're prototyping, and I'll answer questions for the next couple of days.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>ai</category>
      <category>mobile</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Complete Guide to Offline Storage in React Native (2026)</title>
      <dc:creator>Famitha M A</dc:creator>
      <pubDate>Wed, 22 Jul 2026 09:28:13 +0000</pubDate>
      <link>https://dev.to/rapidnative-ai/the-complete-guide-to-offline-storage-in-react-native-2026-1n7j</link>
      <guid>https://dev.to/rapidnative-ai/the-complete-guide-to-offline-storage-in-react-native-2026-1n7j</guid>
      <description>&lt;p&gt;Most React Native apps treat the network as an always-on dependency, and it shows the moment a request stalls. Offline storage isn't one library — it's a stack: key-value stores for preferences, secure enclaves for tokens, structured DBs for domain data, and query caches for server state. This post walks through the whole stack and the offline-first pattern that ties it together.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five categories of RN offline storage
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Key-value&lt;/strong&gt; — AsyncStorage (compat) or MMKV (default). MMKV is ~30x faster and supports sync reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Secure&lt;/strong&gt; — &lt;code&gt;expo-secure-store&lt;/code&gt; or &lt;code&gt;react-native-keychain&lt;/code&gt;. Backed by iOS Keychain / Android Keystore.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structured&lt;/strong&gt; — &lt;code&gt;expo-sqlite&lt;/code&gt;, &lt;code&gt;op-sqlite&lt;/code&gt; (JSI, synchronous), WatermelonDB (reactive + sync), Realm (Atlas Device Sync).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File system&lt;/strong&gt; — &lt;code&gt;expo-file-system&lt;/code&gt; for media. Never base64 blobs into KV storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-state cache&lt;/strong&gt; — TanStack Query + &lt;code&gt;persistQueryClient&lt;/code&gt;, backed by MMKV.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Perf&lt;/th&gt;
&lt;th&gt;Encryption&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;AsyncStorage&lt;/td&gt;
&lt;td&gt;KV&lt;/td&gt;
&lt;td&gt;1x&lt;/td&gt;
&lt;td&gt;Community fork&lt;/td&gt;
&lt;td&gt;Legacy/compat&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MMKV&lt;/td&gt;
&lt;td&gt;KV&lt;/td&gt;
&lt;td&gt;~30x&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;New default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;expo-secure-store&lt;/td&gt;
&lt;td&gt;Secure KV&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;OS keychain&lt;/td&gt;
&lt;td&gt;Tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;op-sqlite&lt;/td&gt;
&lt;td&gt;SQL&lt;/td&gt;
&lt;td&gt;JSI&lt;/td&gt;
&lt;td&gt;SQLCipher&lt;/td&gt;
&lt;td&gt;Structured data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WatermelonDB&lt;/td&gt;
&lt;td&gt;Reactive DB&lt;/td&gt;
&lt;td&gt;Lazy&lt;/td&gt;
&lt;td&gt;SQLCipher&lt;/td&gt;
&lt;td&gt;Large offline lists + sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Realm&lt;/td&gt;
&lt;td&gt;Object DB&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Atlas Device Sync&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The offline-first pattern
&lt;/h2&gt;

&lt;p&gt;Four moving parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local-first reads&lt;/strong&gt; — UI always reads from local store. Network populates store; store renders UI.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optimistic writes&lt;/strong&gt; — UI updates immediately, before the server acks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mutation queue&lt;/strong&gt; — Durable list (SQLite or MMKV) of pending server-side changes with client IDs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sync engine&lt;/strong&gt; — Background worker drains the queue with exponential backoff, reconciles conflicts.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;markMessageAsRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&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;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&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="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;readAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;pendingSync&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="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mutationQueue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;enqueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;message.markRead&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;readAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;syncEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wake&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;
  
  
  Conflict resolution
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;LWW&lt;/strong&gt; — timestamps, newest wins. Fine for most consumer apps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-authoritative&lt;/strong&gt; — reject stale writes with a version mismatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CRDTs&lt;/strong&gt; — Yjs, Automerge. Overkill unless you're building a collaborative editor.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Decision cheat sheet
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Prefs / JWT? → MMKV + expo-secure-store&lt;/li&gt;
&lt;li&gt;Instant-open cached lists? → TanStack Query + &lt;code&gt;persistQueryClient&lt;/code&gt; on MMKV&lt;/li&gt;
&lt;li&gt;Thousands of offline records? → op-sqlite (or WatermelonDB for reactive)&lt;/li&gt;
&lt;li&gt;Cross-device sync? → WatermelonDB or Realm&lt;/li&gt;
&lt;li&gt;Media? → expo-file-system + metadata in SQLite&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Pitfalls to skip
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Blocking JS thread with async AsyncStorage at startup&lt;/li&gt;
&lt;li&gt;Blobs in KV stores&lt;/li&gt;
&lt;li&gt;No cache-wipe on logout&lt;/li&gt;
&lt;li&gt;Infinite retry loops in the mutation queue&lt;/li&gt;
&lt;li&gt;Persisting Redux state without a schema version&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;Offline-first isn't a defensive posture — it's a performance strategy. Reading from local storage beats a round-trip every time, and a good sync engine hides the network from your users. Pick each layer deliberately and this becomes a solved problem, not a recurring one.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://www.rapidnative.com/?utm_source=devto&amp;amp;utm_medium=content&amp;amp;utm_campaign=complete-guide-offline-storage-react-native" rel="noopener noreferrer"&gt;RapidNative&lt;/a&gt;, an AI mobile app builder that generates production-ready React Native code you can extend with any of these libraries. Would love feedback from anyone using MMKV + TanStack Query in production.&lt;/p&gt;

</description>
      <category>reactnative</category>
      <category>mobile</category>
      <category>javascript</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
