<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged react-native</title>
<link>https://coderlegion.com/tag/react-native</link>
<description>Powered by Question2Answer</description>
<item>
<title>How to Fix Metro Bundler Stuck or Not Starting Errors in React Native</title>
<link>https://coderlegion.com/32069/how-to-fix-metro-bundler-stuck-or-not-starting-errors-in-react-native</link>
<description>&lt;p&gt;If you've worked with React Native for a while, you've probably run into a problem where Metro refuses to start, gets stuck while bundling, or leaves your app waiting for JavaScript that never arrives.&lt;/p&gt;
&lt;p&gt;Sometimes the terminal shows an error immediately. Other times, Metro appears to start normally, but the app stays on a loading screen or fails to connect to the development server.&lt;/p&gt;
&lt;p&gt;The frustrating part is that these symptoms can have completely different causes. A port conflict, stale cache, broken dependency installation, invalid configuration, or network issue can all look like a Metro problem.&lt;/p&gt;
&lt;p&gt;Here's how to narrow it down and fix the actual cause instead of repeatedly restarting your project and hoping something changes.&lt;/p&gt;
&lt;h3 id=&quot;1-identify-what-metro-is-actually-doing&quot;&gt;1. Identify What Metro Is Actually Doing&lt;/h3&gt;
&lt;p&gt;Before changing anything, identify which of these situations matches your problem.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metro won't start:&lt;/strong&gt; The development server exits with an error, refuses to launch, or reports that a port is already in use.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metro starts but gets stuck bundling:&lt;/strong&gt; The terminal shows that bundling has started, but it never finishes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The app cannot connect to Metro:&lt;/strong&gt; The server appears to be running, but the application displays a loading screen or a connection error.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metro starts, but the app crashes:&lt;/strong&gt; Bundling may be successful, but a JavaScript exception or native configuration problem prevents the app from running.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;These are different failure points. Clearing the cache might help with a stale bundle, but it won't fix a port occupied by another process or an invalid native configuration.&lt;/p&gt;
&lt;p&gt;Start with the simplest checks and move toward more disruptive fixes only when necessary.&lt;/p&gt;
&lt;h3 id=&quot;2-stop-metro-and-start-it-again&quot;&gt;2. Stop Metro and Start It Again&lt;/h3&gt;
&lt;p&gt;First, stop any Metro server running in the terminal by pressing &lt;code&gt;Ctrl + C&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then start it using the command appropriate for your project.&lt;/p&gt;
&lt;h4 id=&quot;if-you-re-using-expo&quot;&gt;If you're using Expo&lt;/h4&gt;
&lt;p&gt;From your project directory, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To clear Expo's Metro cache at the same time, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start --clear
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;if-you-re-using-react-native-cli&quot;&gt;If you're using React Native CLI&lt;/h4&gt;
&lt;p&gt;Start Metro from your project's root directory:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you suspect stale cached files, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native start --reset-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You don't need to clear the cache every time Metro fails. Start with the normal command. Use the cache-reset option when the symptoms suggest stale or inconsistent cached data, or when a normal restart doesn't help.&lt;/p&gt;
&lt;p&gt;If Metro exits immediately, read the terminal output before trying another command. The first meaningful error is often more useful than the final stack trace.&lt;/p&gt;
&lt;h3 id=&quot;3-check-whether-port-8081-is-already-in-use&quot;&gt;3. Check Whether Port 8081 Is Already in Use&lt;/h3&gt;
&lt;p&gt;Metro normally uses port &lt;code&gt;8081&lt;/code&gt;. If another process is listening on that port, Metro may fail to start or ask you to use a different one.&lt;/p&gt;
&lt;p&gt;The terminal might show a message similar to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Error: listen EADDRINUSE: address already in use :::8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means another process is already using the requested address and port.&lt;/p&gt;
&lt;h4 id=&quot;on-windows&quot;&gt;On Windows&lt;/h4&gt;
&lt;p&gt;Open Command Prompt and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;netstat -ano | findstr :8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for a line containing &lt;code&gt;LISTENING&lt;/code&gt;. The last column contains the process ID (PID).&lt;/p&gt;
&lt;p&gt;You can identify the process with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;tasklist /FI &quot;PID eq 12345&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;12345&lt;/code&gt; with the actual PID.&lt;/p&gt;
&lt;p&gt;If you've confirmed that the process is an old Metro server you no longer need, you can stop it:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-cmd&quot;&gt;taskkill /PID 12345 /F
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;on-macos-or-linux&quot;&gt;On macOS or Linux&lt;/h4&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;lsof -i :8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inspect the process listed for port &lt;code&gt;8081&lt;/code&gt;. If it's an old Metro process that you no longer need, stop that specific process:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;kill &amp;lt;PID&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Replace &lt;code&gt;&amp;lt;PID&amp;gt;&lt;/code&gt; with the actual process ID.&lt;/p&gt;
&lt;p&gt;Don't terminate a process simply because it's using the port. Confirm what it is first, especially if other development tools are running.&lt;/p&gt;
&lt;p&gt;Once the port is available, start Metro again.&lt;/p&gt;
&lt;h3 id=&quot;4-clear-metro-s-cache-when-bundling-gets-stuck&quot;&gt;4. Clear Metro's Cache When Bundling Gets Stuck&lt;/h3&gt;
&lt;p&gt;Metro transforms your JavaScript files and their dependencies into a bundle the application can execute. Cached transformation results help make subsequent builds faster, but stale or inconsistent cached data can sometimes cause confusing behavior.&lt;/p&gt;
&lt;p&gt;Typical symptoms include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bundling worked before, but now fails after code or dependency changes.&lt;/li&gt;
&lt;li&gt;The app appears to use an older version of your JavaScript.&lt;/li&gt;
&lt;li&gt;Metro repeatedly fails while transforming files without an obvious dependency error.&lt;/li&gt;
&lt;li&gt;A normal restart doesn't resolve the problem.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Try the appropriate cache-reset command from Step 2.&lt;/p&gt;
&lt;p&gt;After the cache is cleared, let Metro rebuild the bundle. The first build can take longer because the cached transformation results need to be regenerated.&lt;/p&gt;
&lt;p&gt;If the same error returns, don't keep clearing the cache. Look at the specific file, dependency, or configuration mentioned in the terminal output. A repeatable error usually means there's something more specific to fix.&lt;/p&gt;
&lt;h3 id=&quot;5-check-your-dependencies-before-reinstalling-everything&quot;&gt;5. Check Your Dependencies Before Reinstalling Everything&lt;/h3&gt;
&lt;p&gt;Metro depends on the packages and configuration in your React Native project. If a package installation was interrupted, dependencies were changed, or incompatible versions were introduced, Metro may fail while loading its configuration or resolving modules.&lt;/p&gt;
&lt;p&gt;Start by checking the actual error.&lt;/p&gt;
&lt;p&gt;For example, these messages point toward different problems:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cannot find module ...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Cannot find module 'metro-config'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A missing module doesn't automatically mean the entire dependency installation is broken. It might be an incorrect import path, a package that was never installed, or a version mismatch.&lt;/p&gt;
&lt;p&gt;Check the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Did the problem start immediately after installing, removing, or updating a package?&lt;/li&gt;
&lt;li&gt;Does the error identify a particular module or package?&lt;/li&gt;
&lt;li&gt;Does your project use the package versions expected by its React Native or Expo version?&lt;/li&gt;
&lt;li&gt;Did you accidentally run the installation command from the wrong directory?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the project uses npm and has a valid, committed &lt;code&gt;package-lock.json&lt;/code&gt;, a clean installation may be appropriate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This reinstalls dependencies according to the lockfile and removes the existing &lt;code&gt;node_modules&lt;/code&gt; directory as part of the process.&lt;/p&gt;
&lt;p&gt;For a Yarn project, use the installation command appropriate for its Yarn version and existing lockfile. Don't switch package managers or delete your lockfile just to see whether Metro starts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Avoid deleting &lt;code&gt;node_modules&lt;/code&gt;, lockfiles, and caches all at once as your first response.&lt;/strong&gt; Doing so can introduce new version differences and make it harder to identify what originally caused the failure.&lt;/p&gt;
&lt;h3 id=&quot;6-check-metro-and-expo-configuration&quot;&gt;6. Check Metro and Expo Configuration&lt;/h3&gt;
&lt;p&gt;If Metro reports an error while loading its configuration, clearing the cache or reinstalling dependencies might not help.&lt;/p&gt;
&lt;p&gt;Look for changes to files such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;metro.config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;babel.config.js&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Expo app configuration, such as &lt;code&gt;app.json&lt;/code&gt; or &lt;code&gt;app.config.js&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pay particular attention to recent changes involving custom Metro configuration, Babel plugins, aliases, monorepo settings, or package versions.&lt;/p&gt;
&lt;p&gt;For example, if Metro fails while loading &lt;code&gt;metro.config.js&lt;/code&gt;, inspect the configuration and the package named in the error. A missing import or incompatible configuration dependency can prevent the server from starting at all.&lt;/p&gt;
&lt;p&gt;If the problem appeared after a configuration change, compare that change with the last working version. Reverting one recent change is often more informative than rebuilding the entire environment.&lt;/p&gt;
&lt;p&gt;For Expo projects, also make sure you're following the configuration requirements for your installed Expo SDK rather than copying a Metro configuration from an unrelated project or older tutorial.&lt;/p&gt;
&lt;h3 id=&quot;7-if-metro-runs-but-the-app-cannot-connect&quot;&gt;7. If Metro Runs but the App Cannot Connect&lt;/h3&gt;
&lt;p&gt;A running Metro server doesn't guarantee that your app can reach it.&lt;/p&gt;
&lt;p&gt;This distinction matters because repeatedly resetting Metro's cache won't fix a connection problem.&lt;/p&gt;
&lt;h4 id=&quot;check-the-development-server-url&quot;&gt;Check the development-server URL&lt;/h4&gt;
&lt;p&gt;When Metro starts, inspect the URL or connection information it prints in the terminal.&lt;/p&gt;
&lt;p&gt;If you're using a physical device, make sure the device and computer can communicate over the selected network. VPNs, firewalls, guest Wi-Fi networks, and network isolation can interfere with local development connections.&lt;/p&gt;
&lt;h4 id=&quot;if-you-re-using-expo-1&quot;&gt;If you're using Expo&lt;/h4&gt;
&lt;p&gt;Expo supports different connection methods depending on your environment and configuration. If the normal connection method doesn't work, try the tunnel option:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start --tunnel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A tunnel can help when the device cannot reach your computer directly over the local network, although it may be slower and depends on the tunnel service being available.&lt;/p&gt;
&lt;h4 id=&quot;if-you-re-using-an-android-emulator&quot;&gt;If you're using an Android emulator&lt;/h4&gt;
&lt;p&gt;Check that the emulator is running and that the development server is reachable. For a React Native CLI workflow using the standard Android emulator setup, you can forward port &lt;code&gt;8081&lt;/code&gt; with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;adb reverse tcp:8081 tcp:8081
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is useful when the emulator needs a route to Metro through Android Debug Bridge. It isn't a universal fix for every device or connection setup.&lt;/p&gt;
&lt;p&gt;If the app can connect but crashes after loading the bundle, move on to the JavaScript or native error shown by the app. At that point, Metro itself might be working correctly.&lt;/p&gt;
&lt;h3 id=&quot;8-don-t-confuse-a-javascript-error-with-a-metro-startup-failure&quot;&gt;8. Don't Confuse a JavaScript Error With a Metro Startup Failure&lt;/h3&gt;
&lt;p&gt;Sometimes developers blame Metro because the application stops working immediately after a successful bundle.&lt;/p&gt;
&lt;p&gt;But these messages describe different problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metro startup error:&lt;/strong&gt; The development server cannot initialize.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Module resolution error:&lt;/strong&gt; Metro cannot find or resolve a requested module.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;JavaScript runtime exception:&lt;/strong&gt; The bundle loads, but the application encounters an error while executing code.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Native build error:&lt;/strong&gt; The Android or iOS application fails during native compilation or configuration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, if Metro reports that it cannot resolve a module imported by &lt;code&gt;App.tsx&lt;/code&gt;, investigate the import path, filename, package installation, and any configured aliases.&lt;/p&gt;
&lt;p&gt;If the Android build fails before the application starts, inspect the native build output instead of treating every failure as a Metro cache problem.&lt;/p&gt;
&lt;p&gt;Finding the correct failure point saves time because each category requires a different solution.&lt;/p&gt;
&lt;h3 id=&quot;9-when-none-of-these-fixes-works&quot;&gt;9. When None of These Fixes Works&lt;/h3&gt;
&lt;p&gt;If Metro still refuses to start, collect the evidence before making more changes.&lt;/p&gt;
&lt;p&gt;A useful troubleshooting report should include:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The exact command used to start Metro.&lt;/li&gt;
&lt;li&gt;The full terminal error, especially the first meaningful error before the stack trace.&lt;/li&gt;
&lt;li&gt;Your operating system.&lt;/li&gt;
&lt;li&gt;Whether the project uses Expo or React Native CLI.&lt;/li&gt;
&lt;li&gt;The relevant React Native and Expo versions.&lt;/li&gt;
&lt;li&gt;Any recent dependency or configuration changes.&lt;/li&gt;
&lt;li&gt;Whether Metro fails to start, gets stuck bundling, or starts while the app cannot connect.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remove access tokens, passwords, private URLs, and other sensitive information before sharing logs publicly.&lt;/p&gt;
&lt;p&gt;If the output contains several errors, don't assume the final message is the root cause. A failure to load one configuration dependency can trigger multiple follow-up errors.&lt;/p&gt;
&lt;p&gt;This is also where a diagnostic tool can help. If you've already tried the obvious fixes and the logs are difficult to interpret, you can paste the relevant error and technical context into &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt;. It can help analyze the failure, identify likely causes, and suggest targeted next steps instead of making you cycle through unrelated fixes.&lt;/p&gt;
&lt;h3 id=&quot;final-thoughts&quot;&gt;Final Thoughts&lt;/h3&gt;
&lt;p&gt;When Metro Bundler gets stuck or refuses to start, the best approach is to identify where the failure occurs before changing your project.&lt;/p&gt;
&lt;p&gt;Start with a normal restart, check port &lt;code&gt;8081&lt;/code&gt;, clear the cache when appropriate, and inspect dependency or configuration errors when they appear. If Metro is running but the app cannot connect, troubleshoot the connection rather than repeatedly resetting the bundler.&lt;/p&gt;
&lt;p&gt;The goal isn't to run every possible fix. It's to find the specific reason your development environment stopped working and make the smallest change that resolves it.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/32069/how-to-fix-metro-bundler-stuck-or-not-starting-errors-in-react-native</guid>
<pubDate>Thu, 22 Oct 2026 12:02:08 +0000</pubDate>
</item>
<item>
<title>One Design System, Two Runtimes: Sharing Contracts Between React and React Native</title>
<link>https://coderlegion.com/28700/one-design-system-two-runtimes-sharing-contracts-between-react-and-react-native</link>
<description>&lt;p&gt;A cross-platform design system sounds simple until the first real component forces the platforms apart.&lt;/p&gt;
&lt;p&gt;React and React Native can expose the same product concept — a button, checkbox, input, modal — while relying on very different runtime primitives. The web has DOM attributes, anchors, CSS, hover and keyboard behavior. React Native has &lt;code&gt;Pressable&lt;/code&gt;, native accessibility props, gesture events, and a completely different styling model.&lt;/p&gt;
&lt;p&gt;Trying to hide all of those differences behind one implementation usually makes the API worse. In Vellira, I have been taking a different approach: &lt;strong&gt;share the semantic contract, not the runtime implementation&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That distinction has become one of the most useful architectural rules in the project.&lt;/p&gt;
&lt;h3 id=&quot;the-real-goal-is-consistency-not-identical-code&quot;&gt;The real goal is consistency, not identical code&lt;/h3&gt;
&lt;p&gt;When I started building Vellira, it was tempting to think about cross-platform components mainly as a code-sharing problem.&lt;/p&gt;
&lt;p&gt;If a web button and a native button look similar, why not make them use as much of the same component code as possible?&lt;/p&gt;
&lt;p&gt;Because the important thing users consume is not the internal component tree. It is the behavior and mental model of the public API.&lt;/p&gt;
&lt;p&gt;For a button, developers should be able to learn concepts such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;color&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;appearance&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shape&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;loading&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;disabled&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fullWidth&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;iconOnly&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;once and carry that knowledge between platforms.&lt;/p&gt;
&lt;p&gt;The implementation underneath those concepts does not need to be the same.&lt;/p&gt;
&lt;p&gt;This leads to a more useful definition of parity:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Cross-platform parity means shared semantics where the platforms agree, plus explicit platform-specific capabilities where they do not.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is much stronger than forcing two different runtimes through the same abstraction.&lt;/p&gt;
&lt;h3 id=&quot;put-shared-semantics-in-a-platform-neutral-contract&quot;&gt;Put shared semantics in a platform-neutral contract&lt;/h3&gt;
&lt;p&gt;Vellira keeps the common Button vocabulary in &lt;code&gt;@vellira-ui/types&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;A simplified view of the shared contract looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export type ButtonSize = 'sm' | 'md' | 'lg';

export type ButtonColor =
  | 'primary'
  | 'neutral'
  | 'success'
  | 'warning'
  | 'danger';

export type ButtonAppearance =
  | 'solid'
  | 'outline'
  | 'ghost'
  | 'soft'
  | 'link';

export interface BaseButtonProps {
  color?: ButtonColor;
  appearance?: ButtonAppearance;
  size?: ButtonSize;
  shape?: 'square' | 'rounded' | 'pill';
  fullWidth?: boolean;
  loading?: boolean;
  loadingText?: string;
  disabled?: boolean;
  iconOnly?: boolean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There is nothing web-specific or native-specific in that interface. It describes what a Vellira button &lt;em&gt;means&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That shared layer gives both implementations a stable semantic center. If &lt;code&gt;appearance=&quot;soft&quot;&lt;/code&gt; exists on both platforms, it should represent the same design intent. If &lt;code&gt;loading&lt;/code&gt; disables interaction, that rule should be understood consistently too.&lt;/p&gt;
&lt;p&gt;This is where I want reuse to be strongest.&lt;/p&gt;
&lt;h3 id=&quot;let-each-platform-extend-the-contract-honestly&quot;&gt;Let each platform extend the contract honestly&lt;/h3&gt;
&lt;p&gt;Once the common semantics are defined, the platform packages can extend them instead of pretending their runtimes are identical.&lt;/p&gt;
&lt;p&gt;On the web, &lt;code&gt;ButtonProps&lt;/code&gt; extends the shared contract and normal HTML button behavior. It can also expose anchor-related properties such as &lt;code&gt;href&lt;/code&gt;, &lt;code&gt;target&lt;/code&gt;, and &lt;code&gt;rel&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export interface ButtonProps
  extends
    BaseButtonProps,
    Omit&amp;lt;ButtonHTMLAttributes&amp;lt;HTMLButtonElement&amp;gt;, 'color'&amp;gt;,
    Pick&amp;lt;
      AnchorHTMLAttributes&amp;lt;HTMLAnchorElement&amp;gt;,
      'href' | 'target' | 'rel' | 'download'
    &amp;gt; {
  children?: ReactNode;
  iconStart?: ReactNode;
  iconEnd?: ReactNode;
  tooltip?: string;
  badge?: ReactNode;
  shortcut?: ReactNode;
  asChild?: boolean;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Native starts from the same &lt;code&gt;BaseButtonProps&lt;/code&gt;, but the runtime-specific surface is different. It builds on &lt;code&gt;PressableProps&lt;/code&gt;, exposes &lt;code&gt;onPress&lt;/code&gt;, native styles, and accessibility properties that belong to the native platform.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;export interface ButtonProps
  extends BaseButtonProps, Omit&amp;lt;PressableProps, /* platform exclusions */&amp;gt; {
  children?: ReactNode;
  iconStart?: ButtonIconElement;
  iconEnd?: ButtonIconElement;
  iconSize?: number;
  onPress?: (event: GestureResponderEvent) =&amp;gt; void;
  style?: StyleProp&amp;lt;ViewStyle&amp;gt;;
  textStyle?: StyleProp&amp;lt;TextStyle&amp;gt;;
  accessibilityLabel?: string;
  testID?: string;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The two APIs are clearly related, but they are not artificially identical.&lt;/p&gt;
&lt;p&gt;That is intentional.&lt;/p&gt;
&lt;p&gt;A web developer still gets normal web capabilities. A React Native developer still gets normal native capabilities. The design system owns the common semantics without becoming an abstraction tax on either platform.&lt;/p&gt;
&lt;h3 id=&quot;healthy-divergence-is-part-of-a-good-cross-platform-api&quot;&gt;Healthy divergence is part of a good cross-platform API&lt;/h3&gt;
&lt;p&gt;Some differences should not be normalized away.&lt;/p&gt;
&lt;p&gt;For example, a web button can naturally compose with an anchor or another DOM element. That is where properties such as &lt;code&gt;href&lt;/code&gt; or Vellira's &lt;code&gt;asChild&lt;/code&gt; capability make sense.&lt;/p&gt;
&lt;p&gt;React Native does not have an HTML anchor element. Inventing a fake &lt;code&gt;href&lt;/code&gt; prop just to make the type signatures look more symmetrical would create parity on paper while making the native API less natural.&lt;/p&gt;
&lt;p&gt;The same rule applies in the opposite direction. React Native has concepts such as &lt;code&gt;StyleProp&amp;lt;ViewStyle&amp;gt;&lt;/code&gt;, &lt;code&gt;testID&lt;/code&gt;, and native press events. Wrapping every one of those in invented cross-platform names would make the system harder to understand and harder to debug.&lt;/p&gt;
&lt;p&gt;So I use a simple test when deciding whether something belongs in the shared contract:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Does this property describe product-level component behavior, or does it describe the runtime that implements it?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Product-level behavior is a strong candidate for the shared layer. Runtime behavior usually stays with the platform package.&lt;/p&gt;
&lt;p&gt;That distinction keeps the common API small enough to remain meaningful.&lt;/p&gt;
&lt;h3 id=&quot;share-design-decisions-separately-from-component-implementations&quot;&gt;Share design decisions separately from component implementations&lt;/h3&gt;
&lt;p&gt;Component props are only one layer of cross-platform consistency.&lt;/p&gt;
&lt;p&gt;Visual decisions such as spacing, radii, typography scales, colors, and semantic states also need a common source of truth. Those decisions can be represented as tokens and consumed differently by CSS on the web and native style objects in React Native.&lt;/p&gt;
&lt;p&gt;The important architectural point is the same: &lt;strong&gt;share the decision, adapt the execution&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;A radius token does not require the web and native styling engines to work the same way. It only requires both implementations to resolve the same design intent.&lt;/p&gt;
&lt;p&gt;This is a useful pattern because it prevents &quot;cross-platform&quot; from turning into &quot;lowest common denominator&quot;. Shared semantics stay centralized while each renderer remains free to use the platform correctly.&lt;/p&gt;
&lt;h3 id=&quot;test-parity-at-the-contract-boundary&quot;&gt;Test parity at the contract boundary&lt;/h3&gt;
&lt;p&gt;Once implementations are separate, drift becomes the next problem.&lt;/p&gt;
&lt;p&gt;It is easy for the web Button to gain a new appearance or loading rule while the React Native Button silently falls behind. Separate implementations therefore need explicit validation around the things that are supposed to stay aligned.&lt;/p&gt;
&lt;p&gt;In Vellira, both Button implementations have their own tests and Storybook coverage. The shared types define a common vocabulary, while platform tests verify actual runtime behavior.&lt;/p&gt;
&lt;p&gt;That gives two different kinds of confidence:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Contract confidence&lt;/strong&gt; — the shared semantic API is defined in one place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Runtime confidence&lt;/strong&gt; — each implementation proves that the contract behaves correctly on its own platform.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I prefer this over a large shared implementation with a growing number of platform conditionals. A shared file can look beautifully DRY while still hiding platform-specific bugs.&lt;/p&gt;
&lt;p&gt;Separate runtime tests make those differences visible.&lt;/p&gt;
&lt;h3 id=&quot;a-practical-three-layer-model&quot;&gt;A practical three-layer model&lt;/h3&gt;
&lt;p&gt;The pattern I am converging on in Vellira can be summarized as three layers.&lt;/p&gt;
&lt;h4 id=&quot;1-shared-semantics&quot;&gt;1. Shared semantics&lt;/h4&gt;
&lt;p&gt;Define the concepts developers should learn once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;size
appearance
color
loading
disabled
shape
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These belong in platform-neutral types and design contracts.&lt;/p&gt;
&lt;h4 id=&quot;2-platform-api&quot;&gt;2. Platform API&lt;/h4&gt;
&lt;p&gt;Extend those concepts with capabilities developers expect from the runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Web:          href, target, DOM attributes, asChild
React Native: onPress, accessibilityLabel, style, testID
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not disguise real platform differences.&lt;/p&gt;
&lt;h4 id=&quot;3-platform-implementation&quot;&gt;3. Platform implementation&lt;/h4&gt;
&lt;p&gt;Use the native tools of each runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Web:          DOM + CSS
React Native: Pressable + native styles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The implementation can diverge significantly as long as the shared semantics remain trustworthy.&lt;/p&gt;
&lt;h3 id=&quot;what-this-changes-when-designing-new-components&quot;&gt;What this changes when designing new components&lt;/h3&gt;
&lt;p&gt;This architecture also changes how I think about adding components.&lt;/p&gt;
&lt;p&gt;Instead of beginning with &quot;How can I reuse the most code?&quot;, I begin with questions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What behavior is actually common to both platforms?&lt;/li&gt;
&lt;li&gt;Which names should mean the same thing everywhere?&lt;/li&gt;
&lt;li&gt;Which states must have equivalent semantics?&lt;/li&gt;
&lt;li&gt;Which capabilities are genuinely platform-specific?&lt;/li&gt;
&lt;li&gt;What should be validated so parity cannot drift accidentally?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Only after those questions are answered does implementation reuse become interesting.&lt;/p&gt;
&lt;p&gt;Sometimes there will be useful shared code below the component layer. Sometimes the correct answer is two mostly independent implementations. Both are acceptable if the public contract stays coherent.&lt;/p&gt;
&lt;p&gt;This makes the design system easier to evolve because platform differences are treated as architecture inputs, not inconvenient exceptions.&lt;/p&gt;
&lt;h3 id=&quot;the-rule-i-keep-coming-back-to&quot;&gt;The rule I keep coming back to&lt;/h3&gt;
&lt;p&gt;The most useful lesson so far is that cross-platform design systems do not need maximum code sharing. They need &lt;strong&gt;maximum semantic clarity&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Developers should feel that the React and React Native packages belong to the same system. They should recognize the vocabulary, states, design decisions, and interaction intent. But they should not have to give up the strengths of their platform to get that consistency.&lt;/p&gt;
&lt;p&gt;For Vellira, that means sharing contracts aggressively and implementations selectively.&lt;/p&gt;
&lt;p&gt;It is a smaller abstraction, but a much more durable one.&lt;/p&gt;
&lt;p&gt;You can explore the project in the &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.vellira.dev&quot;&gt;Vellira documentation&lt;/a&gt; or browse the implementation on &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/vellira-dev/vellira&quot;&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28700/one-design-system-two-runtimes-sharing-contracts-between-react-and-react-native</guid>
<pubDate>Sat, 26 Sep 2026 09:42:11 +0000</pubDate>
</item>
<item>
<title>AI Can Generate More UI. Who Keeps It Consistent?</title>
<link>https://coderlegion.com/28523/ai-can-generate-more-ui-who-keeps-it-consistent</link>
<description>&lt;p&gt;AI makes UI faster to produce — and easier to fragment. Why Vellira is building cross-platform components and developer tooling to keep growing UI systems coherent as humans and coding agents move faster.&lt;/p&gt;
&lt;p&gt;AI can now generate UI faster than many teams can review it.&lt;/p&gt;
&lt;p&gt;A developer can describe a component, ask a coding model to implement it, and get a plausible result in minutes. The same model can refactor nearby code, write tests, update types, and produce several variations before a traditional implementation would have reached its first review.&lt;/p&gt;
&lt;p&gt;That is a real improvement.&lt;/p&gt;
&lt;p&gt;But it also moves the bottleneck.&lt;/p&gt;
&lt;p&gt;The hard question is becoming less:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;How quickly can we create another component?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;And more:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;How do we keep the whole UI system coherent when humans and AI can change it this quickly?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is one of the problems we are building Vellira around.&lt;/p&gt;
&lt;h3 id=&quot;generation-is-getting-cheaper-fragmentation-is-not-&quot;&gt;Generation is getting cheaper. Fragmentation is not.&lt;/h3&gt;
&lt;p&gt;Imagine an AI-generated component that looks good, compiles, and works in a local preview.&lt;/p&gt;
&lt;p&gt;That still leaves a surprising number of questions unanswered.&lt;/p&gt;
&lt;p&gt;Does it duplicate something the system already has? Does its API fit the surrounding components? Does it use the shared design foundations? Is the same product capability represented correctly on React Native? Are accessibility expectations covered? Did the documentation and examples move with the implementation? Will the next contributor understand how this component is supposed to evolve?&lt;/p&gt;
&lt;p&gt;A locally correct component can still make the system around it less consistent.&lt;/p&gt;
&lt;p&gt;That distinction matters more as generation gets faster.&lt;/p&gt;
&lt;p&gt;When UI changes slowly, teams can compensate for weak system boundaries with review, documentation, and memory. Someone remembers which component should be reused. Someone catches the one-off visual value. Someone notices that Web changed but React Native did not. Someone remembers to update Storybook.&lt;/p&gt;
&lt;p&gt;As the rate of change increases, that becomes harder to sustain.&lt;/p&gt;
&lt;p&gt;More UI output creates more opportunities for small differences to accumulate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;APIs drift between similar components;&lt;/li&gt;
&lt;li&gt;accessibility behavior becomes inconsistent;&lt;/li&gt;
&lt;li&gt;local values bypass shared tokens;&lt;/li&gt;
&lt;li&gt;documentation falls behind implementation;&lt;/li&gt;
&lt;li&gt;Web and React Native diverge unintentionally;&lt;/li&gt;
&lt;li&gt;similar capabilities get implemented more than once;&lt;/li&gt;
&lt;li&gt;tests encode different expectations for comparable behavior;&lt;/li&gt;
&lt;li&gt;package and public surfaces become harder to keep aligned.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of these problems were invented by AI.&lt;/p&gt;
&lt;p&gt;AI simply increases the rate at which they can appear.&lt;/p&gt;
&lt;p&gt;If a team can create five or ten times more UI changes, the system around those changes has to become stronger too.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=9517244816750394364&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h3 id=&quot;a-component-library-is-necessary-it-is-no-longer-sufficient-&quot;&gt;A component library is necessary. It is no longer sufficient.&lt;/h3&gt;
&lt;p&gt;Traditional design systems are often described as collections of reusable components, tokens, guidelines, and documentation.&lt;/p&gt;
&lt;p&gt;Those foundations still matter. Vellira has them too.&lt;/p&gt;
&lt;p&gt;But a catalog can only tell you so much.&lt;/p&gt;
&lt;p&gt;It can show that &lt;code&gt;Button&lt;/code&gt;, &lt;code&gt;Select&lt;/code&gt;, or &lt;code&gt;Tabs&lt;/code&gt; exists. It cannot, by itself, keep every future change aligned with the same expectations around APIs, accessibility, platforms, documentation, testing, and quality.&lt;/p&gt;
&lt;p&gt;This becomes especially visible when UI work is shared between developers and coding agents.&lt;/p&gt;
&lt;p&gt;A coding model can generate a perfectly reasonable component that does not quite belong to the system around it.&lt;/p&gt;
&lt;p&gt;It may introduce a slightly different API. It may recreate a capability that already exists. It may look correct on Web while ignoring the native side of the project. It may use values that are visually close to existing tokens without actually using the shared token system.&lt;/p&gt;
&lt;p&gt;The code is not necessarily bad.&lt;/p&gt;
&lt;p&gt;The surrounding system is simply asking a larger question than &quot;does this component work?&quot;&lt;/p&gt;
&lt;p&gt;It is asking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Does this change still belong to the same product and engineering system?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is the layer we are increasingly interested in with Vellira.&lt;/p&gt;
&lt;h3 id=&quot;what-vellira-is-becoming&quot;&gt;What Vellira is becoming&lt;/h3&gt;
&lt;p&gt;Vellira started as a cross-platform design system for React and React Native.&lt;/p&gt;
&lt;p&gt;The public project still has that foundation: reusable components, shared tokens, documentation, Storybook, and cross-platform implementation work.&lt;/p&gt;
&lt;p&gt;But building the component set exposed a second problem very quickly.&lt;/p&gt;
&lt;p&gt;Creating components is only part of the work. Keeping all of them aligned as the system grows requires repeatable workflows and tooling around the components themselves.&lt;/p&gt;
&lt;p&gt;Today the public Vellira project already combines:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;reusable React and React Native components;&lt;/li&gt;
&lt;li&gt;shared design tokens and cross-platform foundations;&lt;/li&gt;
&lt;li&gt;Storybook and documentation for discovery and usage;&lt;/li&gt;
&lt;li&gt;repeatable component-generation workflows;&lt;/li&gt;
&lt;li&gt;automated component quality checks;&lt;/li&gt;
&lt;li&gt;accessibility-focused validation and testing;&lt;/li&gt;
&lt;li&gt;metadata-backed tooling that gives the project structured information about its own components.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those features alone is the point.&lt;/p&gt;
&lt;p&gt;The interesting part is what they allow us to work toward together: a UI system that becomes easier to extend without becoming easier to fragment.&lt;/p&gt;
&lt;p&gt;We do not want adding a component to mean only &quot;these files compile.&quot;&lt;/p&gt;
&lt;p&gt;We want the surrounding project to help keep that component connected to the same design foundations, the same platform expectations, the same documentation, and the same engineering standards as the rest of Vellira.&lt;/p&gt;
&lt;p&gt;That is useful when a developer writes the code manually.&lt;/p&gt;
&lt;p&gt;It becomes even more useful when an implementation can appear in seconds.&lt;/p&gt;
&lt;h3 id=&quot;vellira-is-not-trying-to-out-code-the-coding-model&quot;&gt;Vellira is not trying to out-code the coding model&lt;/h3&gt;
&lt;p&gt;This distinction is important.&lt;/p&gt;
&lt;p&gt;Coding models are getting better quickly. They should be able to write more implementation code, understand larger codebases, make broader changes, and handle more routine engineering work over time.&lt;/p&gt;
&lt;p&gt;That is good for Vellira.&lt;/p&gt;
&lt;p&gt;We are not building the project around the assumption that AI will remain bad at writing components.&lt;/p&gt;
&lt;p&gt;We are building around the opposite assumption: AI will get very good at producing and modifying UI.&lt;/p&gt;
&lt;p&gt;If that happens, the surrounding system becomes more important, not less.&lt;/p&gt;
&lt;p&gt;A model can generate a new component.&lt;/p&gt;
&lt;p&gt;The project still needs to keep APIs coherent.&lt;/p&gt;
&lt;p&gt;A model can change ten components in one pass.&lt;/p&gt;
&lt;p&gt;The project still needs to keep accessibility, documentation, public surfaces, tokens, and platform behavior from drifting apart.&lt;/p&gt;
&lt;p&gt;A model can become better at understanding context.&lt;/p&gt;
&lt;p&gt;The repository still benefits from making its important expectations explicit and repeatable instead of depending on every author to remember them perfectly.&lt;/p&gt;
&lt;p&gt;So the goal is not to slow AI-assisted development down.&lt;/p&gt;
&lt;p&gt;The goal is to make that speed safer to absorb.&lt;/p&gt;
&lt;h3 id=&quot;the-author-matters-less-than-the-result&quot;&gt;The author matters less than the result&lt;/h3&gt;
&lt;p&gt;There is a temptation to treat AI-generated code as a completely separate category that needs a special design-system workflow.&lt;/p&gt;
&lt;p&gt;I do not think that is the most useful distinction.&lt;/p&gt;
&lt;p&gt;Once code enters a repository, the important question is not who typed it.&lt;/p&gt;
&lt;p&gt;The important question is whether it fits the project.&lt;/p&gt;
&lt;p&gt;A human can introduce an inconsistent API.&lt;/p&gt;
&lt;p&gt;A coding agent can follow the existing conventions perfectly.&lt;/p&gt;
&lt;p&gt;A developer can forget documentation.&lt;/p&gt;
&lt;p&gt;An automated workflow can keep it current.&lt;/p&gt;
&lt;p&gt;The author is not a reliable measure of system quality. The resulting state of the project is.&lt;/p&gt;
&lt;p&gt;That is why Vellira is investing in tooling around the component lifecycle rather than relying only on the intelligence or discipline of whichever author produced the latest change.&lt;/p&gt;
&lt;p&gt;Humans and AI can both move faster when the project around them carries more of the consistency burden.&lt;/p&gt;
&lt;h3 id=&quot;cross-platform-ui-raises-the-stakes&quot;&gt;Cross-platform UI raises the stakes&lt;/h3&gt;
&lt;p&gt;Vellira targets both React and React Native.&lt;/p&gt;
&lt;p&gt;That makes this problem even more visible because &quot;the same component&quot; does not always mean &quot;the same source code.&quot;&lt;/p&gt;
&lt;p&gt;Web and native platforms have different primitives, interaction models, accessibility APIs, and constraints. Some behavior should be shared. Some behavior needs to diverge intentionally.&lt;/p&gt;
&lt;p&gt;A useful cross-platform design system therefore needs more than copied implementations.&lt;/p&gt;
&lt;p&gt;It needs to keep one product language coherent while still respecting each platform.&lt;/p&gt;
&lt;p&gt;Faster code generation does not remove that challenge.&lt;/p&gt;
&lt;p&gt;It can amplify it.&lt;/p&gt;
&lt;p&gt;An AI tool can create two implementations very quickly, but speed alone cannot tell a team whether those implementations still represent one coherent component system.&lt;/p&gt;
&lt;p&gt;That is why the tooling around Vellira's components matters as much as the component count itself.&lt;/p&gt;
&lt;h3 id=&quot;the-bottleneck-is-moving-from-production-to-coordination&quot;&gt;The bottleneck is moving from production to coordination&lt;/h3&gt;
&lt;p&gt;This is the part of AI-assisted frontend development that I find most interesting.&lt;/p&gt;
&lt;p&gt;Better coding models reduce the cost of producing code.&lt;/p&gt;
&lt;p&gt;When producing UI is expensive, teams naturally make fewer changes. When producing UI becomes cheap, experimentation increases, more contributors can work in parallel, and automated agents can make changes of their own.&lt;/p&gt;
&lt;p&gt;The limiting resource starts moving away from typing speed.&lt;/p&gt;
&lt;p&gt;It moves toward coordination, consistency, review, and maintenance.&lt;/p&gt;
&lt;p&gt;That creates a different kind of leverage for developer tooling.&lt;/p&gt;
&lt;p&gt;If ten changes can be produced in the time it previously took to produce one, tooling that helps those ten changes remain part of the same system becomes more valuable.&lt;/p&gt;
&lt;p&gt;This is the bet behind the direction Vellira is taking:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;AI can generate more UI. Vellira is being built to help keep that UI coherent.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The better generation becomes, the more important the system around generation becomes.&lt;/p&gt;
&lt;h3 id=&quot;where-vellira-is-today&quot;&gt;Where Vellira is today&lt;/h3&gt;
&lt;p&gt;We are still building the foundation, and I want to be precise about that.&lt;/p&gt;
&lt;p&gt;The immediate work is concrete: expand the cross-platform component set, strengthen component workflows, improve quality and accessibility checks, and make the public project easier to adopt and use.&lt;/p&gt;
&lt;p&gt;We are proving these ideas inside Vellira itself before making broader claims about what the tooling can understand or manage outside the project.&lt;/p&gt;
&lt;p&gt;That constraint is intentional.&lt;/p&gt;
&lt;p&gt;It is easy to describe an ambitious future for developer tooling. It is more useful to earn that future by making the current system work reliably first.&lt;/p&gt;
&lt;p&gt;But the product direction is already clear.&lt;/p&gt;
&lt;p&gt;Frontend teams are going to work with more generated code, more automation, and more AI-assisted changes — not less.&lt;/p&gt;
&lt;p&gt;The answer should not be to slow those tools down.&lt;/p&gt;
&lt;p&gt;It should be to make the UI system around them stronger.&lt;/p&gt;
&lt;p&gt;If you are building React or React Native interfaces and thinking about how AI changes design-system workflows, follow Vellira as we build this in public:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.vellira.dev&quot;&gt;Explore the Vellira documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://storybook.vellira.dev&quot;&gt;Browse the component system in Storybook&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/vellira-dev/vellira&quot;&gt;Follow Vellira on GitHub&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AI can help us build UI faster.&lt;/p&gt;
&lt;p&gt;Vellira is being built so that faster does not have to mean more fragmented.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/28523/ai-can-generate-more-ui-who-keeps-it-consistent</guid>
<pubDate>Thu, 24 Sep 2026 16:57:01 +0000</pubDate>
</item>
<item>
<title>How to Fix Firebase `PERMISSION_DENIED` Errors in React Native</title>
<link>https://coderlegion.com/28157/how-to-fix-firebase-permissiondenied-errors-in-react-native</link>
<description>&lt;p&gt;If you are using Firebase with a React Native app, sooner or later you will probably run into something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Error: PERMISSION_DENIED: Permission denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And the annoying part is that the error does not tell you much.&lt;/p&gt;
&lt;p&gt;It does not say:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Your Firestore rule on this collection is blocking the request.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It just tells you that Firebase rejected it.&lt;/p&gt;
&lt;p&gt;That can happen for several different reasons. Your Firestore rules might be doing exactly what you told them to do, your user might not actually be authenticated, App Check might be involved, or your app might be connected to a different Firebase project than you think.&lt;/p&gt;
&lt;p&gt;So before changing random rules until the error disappears, it is better to figure out &lt;strong&gt;why Firebase is rejecting the request&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-find-out-which-firebase-service-is-rejecting-the-request&quot;&gt;First, find out which Firebase service is rejecting the request&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PERMISSION_DENIED&lt;/code&gt; is not limited to Firestore.&lt;/p&gt;
&lt;p&gt;You can see permission-related errors with services such as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cloud Firestore&lt;/li&gt;
&lt;li&gt;Realtime Database&lt;/li&gt;
&lt;li&gt;Firebase Storage&lt;/li&gt;
&lt;li&gt;Firebase App Check&lt;/li&gt;
&lt;li&gt;Firebase Authentication-related operations&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So start by looking at the full error.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the stack trace shows a Firestore operation such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;firestore()
  .collection(&quot;users&quot;)
  .doc(userId)
  .get();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then your first place to look is Firestore security rules.&lt;/p&gt;
&lt;p&gt;If the error happens while uploading a file, Storage rules are more relevant.&lt;/p&gt;
&lt;p&gt;This sounds obvious, but a long React Native stack trace can make it easy to focus on the wrong part.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-firestore-security-rules&quot;&gt;Check your Firestore security rules&lt;/h3&gt;
&lt;p&gt;If the error comes from Firestore, check your rules first.&lt;/p&gt;
&lt;p&gt;For example, you might have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {

    match /users/{userId} {
      allow read, write: if request.auth != null;
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This rule only allows authenticated users to access documents under:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/{userId}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your app makes the request before the user has signed in, Firebase will reject it.&lt;/p&gt;
&lt;p&gt;That can produce:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;permission-denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is that Firebase is not necessarily malfunctioning here.&lt;/p&gt;
&lt;p&gt;Your rule may simply be doing exactly what it was written to do.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-the-user-is-actually-authenticated&quot;&gt;Check whether the user is actually authenticated&lt;/h3&gt;
&lt;p&gt;One of the easiest mistakes is assuming that the user is signed in when the request runs.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const user = auth().currentUser;

console.log(user);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If that prints:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;null
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;then there is no authenticated Firebase user at that moment.&lt;/p&gt;
&lt;p&gt;A rule such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if request.auth != null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will reject the request.&lt;/p&gt;
&lt;p&gt;This can happen when your app starts up and immediately tries to read Firestore before Firebase Authentication has restored the user's session.&lt;/p&gt;
&lt;p&gt;For example, you might have code that effectively does this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const user = auth().currentUser;

firestore()
  .collection(&quot;users&quot;)
  .doc(user.uid)
  .get();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If &lt;code&gt;user&lt;/code&gt; is not available yet, the request can fail.&lt;/p&gt;
&lt;p&gt;Instead, make sure your application waits for the authentication state before making requests that require authentication.&lt;/p&gt;
&lt;p&gt;With React Native Firebase, you can listen for authentication changes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;useEffect(() =&amp;gt; {
  const unsubscribe = auth().onAuthStateChanged(user =&amp;gt; {
    if (user) {
      // Safe to make requests that require authentication
    }
  });

  return unsubscribe;
}, []);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact structure depends on your app, but the idea is the same:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not assume authentication has finished restoring just because the app has started.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-that-the-rule-matches-the-path-you-are-actually-using&quot;&gt;Check that the rule matches the path you are actually using&lt;/h3&gt;
&lt;p&gt;This is another very common one.&lt;/p&gt;
&lt;p&gt;Imagine your rules contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read, write: if request.auth != null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But your app actually accesses:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/profiles/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That rule does not apply to &lt;code&gt;/profiles/123&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need a matching rule for the path being accessed.&lt;/p&gt;
&lt;p&gt;This becomes especially confusing when you have nested collections.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/123/settings/theme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not the same path as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your rules need to match the actual document path.&lt;/p&gt;
&lt;p&gt;If you are getting &lt;code&gt;PERMISSION_DENIED&lt;/code&gt;, look at the exact collection and document your code is trying to access.&lt;/p&gt;
&lt;p&gt;Do not assume the rule you are looking at is the one Firebase is evaluating.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;be-careful-with-nested-firestore-documents&quot;&gt;Be careful with nested Firestore documents&lt;/h3&gt;
&lt;p&gt;Suppose you have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;users/{userId}/settings/{settingId}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might write:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read, write: if request.auth != null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and expect that to cover everything underneath the user.&lt;/p&gt;
&lt;p&gt;It does not automatically mean that every nested document is covered.&lt;/p&gt;
&lt;p&gt;You may need a recursive wildcard depending on the structure and rules you want.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId}/{document=**} {
  allow read, write: if request.auth != null;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Be careful with this kind of rule, though.&lt;/p&gt;
&lt;p&gt;It can give access to much more data than you intended.&lt;/p&gt;
&lt;p&gt;Do not add a broad wildcard just because it makes the error disappear.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;do-not-use-allow-read-write-if-true-as-the-final-fix&quot;&gt;Do not use &lt;code&gt;allow read, write: if true&lt;/code&gt; as the final fix&lt;/h3&gt;
&lt;p&gt;You will sometimes see people recommend this while debugging:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, it can make the permission error disappear.&lt;/p&gt;
&lt;p&gt;It also allows anyone who can access your Firebase project from the client to read and write the matching data.&lt;/p&gt;
&lt;p&gt;That is not a real fix.&lt;/p&gt;
&lt;p&gt;If you temporarily use an open rule to confirm that the rules are the cause, put the secure rule back immediately afterward.&lt;/p&gt;
&lt;p&gt;For example, if the app should only allow authenticated users:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if request.auth != null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is very different from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The goal is not simply to make Firebase stop complaining.&lt;/p&gt;
&lt;p&gt;The goal is to make the rule match what your application is actually supposed to allow.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-you-are-using-the-correct-firebase-project&quot;&gt;Check whether you are using the correct Firebase project&lt;/h3&gt;
&lt;p&gt;This one can waste a lot of time.&lt;/p&gt;
&lt;p&gt;Your React Native app might be connected to a different Firebase project than the one you are looking at in the Firebase Console.&lt;/p&gt;
&lt;p&gt;You think you are checking the rules for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-production-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but the app is actually connected to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-development-project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you change rules in the console and nothing happens.&lt;/p&gt;
&lt;p&gt;The app keeps returning:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;permission-denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;because you are fixing the wrong project.&lt;/p&gt;
&lt;p&gt;Check your Firebase configuration and make sure the project ID matches the Firebase project you are inspecting.&lt;/p&gt;
&lt;p&gt;For Android, check the Firebase configuration used by your Android app.&lt;/p&gt;
&lt;p&gt;For iOS, check the corresponding iOS Firebase configuration.&lt;/p&gt;
&lt;p&gt;If you have development, staging, and production projects, this becomes especially important.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-android-and-ios-firebase-configuration&quot;&gt;Check your Android and iOS Firebase configuration&lt;/h3&gt;
&lt;p&gt;If the problem only happens on one platform, compare the Firebase configuration between them.&lt;/p&gt;
&lt;p&gt;For Android, make sure the correct Firebase configuration is being used.&lt;/p&gt;
&lt;p&gt;For iOS, make sure the correct configuration is being used there as well.&lt;/p&gt;
&lt;p&gt;A situation like this is possible:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Android → Firebase project A
iOS     → Firebase project B
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can then spend hours changing Firestore rules in project A while testing an iOS build that is actually talking to project B.&lt;/p&gt;
&lt;p&gt;If the error happens only on Android or only on iOS, platform-specific Firebase configuration should be on your checklist.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-firebase-app-check&quot;&gt;Check Firebase App Check&lt;/h3&gt;
&lt;p&gt;App Check can also be involved when Firebase rejects requests.&lt;/p&gt;
&lt;p&gt;App Check is designed to help verify that requests are coming from your legitimate app rather than an unauthorized client.&lt;/p&gt;
&lt;p&gt;If you recently enabled App Check or changed its configuration, check whether the failing request is related to App Check.&lt;/p&gt;
&lt;p&gt;This becomes especially important if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the app worked before enabling App Check&lt;/li&gt;
&lt;li&gt;requests work in one environment but not another&lt;/li&gt;
&lt;li&gt;you are testing a development build&lt;/li&gt;
&lt;li&gt;you changed App Check enforcement settings&lt;/li&gt;
&lt;li&gt;you changed the app's Android or iOS configuration&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do not immediately disable App Check just because it is involved.&lt;/p&gt;
&lt;p&gt;First determine whether the request is failing because of App Check configuration or because of your Firestore/Storage rules.&lt;/p&gt;
&lt;p&gt;Those are different problems.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;development-and-production-can-behave-differently&quot;&gt;Development and production can behave differently&lt;/h3&gt;
&lt;p&gt;A common source of confusion is:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;It works in development, but my production build gets &lt;code&gt;PERMISSION_DENIED&lt;/code&gt;.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That does not necessarily mean the Firebase rules randomly changed.&lt;/p&gt;
&lt;p&gt;Your development and production builds may be using different:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firebase projects&lt;/li&gt;
&lt;li&gt;application IDs&lt;/li&gt;
&lt;li&gt;bundle identifiers&lt;/li&gt;
&lt;li&gt;configuration files&lt;/li&gt;
&lt;li&gt;App Check settings&lt;/li&gt;
&lt;li&gt;authentication settings&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the problem only happens in a release build, compare the Firebase configuration between the working and failing builds.&lt;/p&gt;
&lt;p&gt;This is usually more useful than changing your Firestore rules blindly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-the-exact-user-id-being-used&quot;&gt;Check the exact user ID being used&lt;/h3&gt;
&lt;p&gt;Suppose your rule is intended to allow users to access their own documents:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read, write: if request.auth.uid == userId;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That means this request:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will only work if:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;request.auth.uid == &quot;123&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If your application accidentally requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/456
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while the logged-in user is actually &lt;code&gt;123&lt;/code&gt;, Firebase will reject it.&lt;/p&gt;
&lt;p&gt;So log the values you are using while debugging:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(&quot;Current user:&quot;, auth().currentUser?.uid);
console.log(&quot;Requested user:&quot;, userId);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If they do not match, you have found the problem.&lt;/p&gt;
&lt;p&gt;This is much more useful than changing the security rule to allow everyone.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-storage-rules-if-the-error-happens-during-uploads&quot;&gt;Check your Storage rules if the error happens during uploads&lt;/h3&gt;
&lt;p&gt;If the problem happens while uploading an image, PDF, or another file, you may actually be dealing with Firebase Storage rather than Firestore.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FirebaseError: [storage/unauthorized] User is not authorized to perform the desired action.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Storage has its own security rules.&lt;/p&gt;
&lt;p&gt;A Firestore rule like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if request.auth != null;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;does not control Firebase Storage.&lt;/p&gt;
&lt;p&gt;If the error happens during:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;storage()
  .ref(&quot;profile-images/avatar.jpg&quot;)
  .putFile(filePath);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;look at your Storage rules instead.&lt;/p&gt;
&lt;p&gt;The service producing the error matters.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-realtime-database-rules-separately&quot;&gt;Check Realtime Database rules separately&lt;/h3&gt;
&lt;p&gt;The same idea applies to Realtime Database.&lt;/p&gt;
&lt;p&gt;Realtime Database has its own rules structure, for example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;rules&quot;: {
    &quot;.read&quot;: &quot;auth != null&quot;,
    &quot;.write&quot;: &quot;auth != null&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Changing Firestore rules will not fix a Realtime Database permission problem.&lt;/p&gt;
&lt;p&gt;If your application uses several Firebase services, make sure you know which one is rejecting the request before changing anything.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;use-the-firebase-emulator-when-appropriate&quot;&gt;Use the Firebase Emulator when appropriate&lt;/h3&gt;
&lt;p&gt;If you are working on complicated security rules, the Firebase Emulator Suite can make debugging much easier.&lt;/p&gt;
&lt;p&gt;Instead of repeatedly testing rules against production data, you can test your rules locally.&lt;/p&gt;
&lt;p&gt;This is especially useful when your rules depend on things like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;request.auth
request.auth.uid
request.resource.data
resource.data
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, a rule might look correct at first glance but fail because the incoming document does not contain the field your rule expects.&lt;/p&gt;
&lt;p&gt;Testing the rule with known authentication and data values makes these problems much easier to see.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;look-at-what-changed-right-before-the-error&quot;&gt;Look at what changed right before the error&lt;/h3&gt;
&lt;p&gt;This is one of the most useful debugging habits for Firebase errors.&lt;/p&gt;
&lt;p&gt;Ask:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What changed immediately before &lt;code&gt;PERMISSION_DENIED&lt;/code&gt; appeared?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Maybe you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;changed Firestore rules&lt;/li&gt;
&lt;li&gt;added authentication&lt;/li&gt;
&lt;li&gt;changed the user document structure&lt;/li&gt;
&lt;li&gt;switched Firebase projects&lt;/li&gt;
&lt;li&gt;enabled App Check&lt;/li&gt;
&lt;li&gt;changed an Android package name&lt;/li&gt;
&lt;li&gt;changed an iOS bundle identifier&lt;/li&gt;
&lt;li&gt;updated a Firebase dependency&lt;/li&gt;
&lt;li&gt;moved a Firestore collection&lt;/li&gt;
&lt;li&gt;changed the document ID&lt;/li&gt;
&lt;li&gt;created a new production build&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The timing does not prove what caused the error, but it gives you a very good place to start.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-simple-debugging-order&quot;&gt;A simple debugging order&lt;/h3&gt;
&lt;p&gt;When I get a Firebase &lt;code&gt;PERMISSION_DENIED&lt;/code&gt; error, I usually go through these checks:&lt;/p&gt;
&lt;h4 id=&quot;1-identify-the-firebase-service&quot;&gt;1. Identify the Firebase service&lt;/h4&gt;
&lt;p&gt;Is it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firestore?&lt;/li&gt;
&lt;li&gt;Storage?&lt;/li&gt;
&lt;li&gt;Realtime Database?&lt;/li&gt;
&lt;li&gt;App Check?&lt;/li&gt;
&lt;li&gt;something else?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;2-read-the-exact-path&quot;&gt;2. Read the exact path&lt;/h4&gt;
&lt;p&gt;What collection, document, or storage path is the app trying to access?&lt;/p&gt;
&lt;h4 id=&quot;3-check-authentication&quot;&gt;3. Check authentication&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(auth().currentUser?.uid);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is there actually a logged-in user?&lt;/p&gt;
&lt;h4 id=&quot;4-check-the-security-rule&quot;&gt;4. Check the security rule&lt;/h4&gt;
&lt;p&gt;Does the rule actually match the path being accessed?&lt;/p&gt;
&lt;h4 id=&quot;5-check-the-values-used-by-the-rule&quot;&gt;5. Check the values used by the rule&lt;/h4&gt;
&lt;p&gt;If the rule compares user IDs, roles, or document fields, verify those values.&lt;/p&gt;
&lt;h4 id=&quot;6-check-the-firebase-project&quot;&gt;6. Check the Firebase project&lt;/h4&gt;
&lt;p&gt;Make sure the app and Firebase Console are using the same project.&lt;/p&gt;
&lt;h4 id=&quot;7-check-app-check&quot;&gt;7. Check App Check&lt;/h4&gt;
&lt;p&gt;Especially if the problem started after enabling or enforcing it.&lt;/p&gt;
&lt;h4 id=&quot;8-check-platform-specific-configuration&quot;&gt;8. Check platform-specific configuration&lt;/h4&gt;
&lt;p&gt;If only Android or iOS is affected, compare the configuration for that platform.&lt;/p&gt;
&lt;h4 id=&quot;9-reproduce-the-problem-again&quot;&gt;9. Reproduce the problem again&lt;/h4&gt;
&lt;p&gt;After making one change, test again.&lt;/p&gt;
&lt;p&gt;This gives you a much clearer debugging trail.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;example-finding-a-simple-rules-mistake&quot;&gt;Example: finding a simple rules mistake&lt;/h3&gt;
&lt;p&gt;Imagine your Firestore rule says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;match /users/{userId} {
  allow read: if request.auth != null
               &amp;amp;&amp;amp; request.auth.uid == userId;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your app is logged in as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;abc123
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but your code requests:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;/users/undefined
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Firebase sees:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;request.auth.uid == &quot;abc123&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;userId == &quot;undefined&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The condition is false.&lt;/p&gt;
&lt;p&gt;So Firebase returns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;permission-denied
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is not Firebase.&lt;/p&gt;
&lt;p&gt;The problem is that your application generated the wrong document path.&lt;/p&gt;
&lt;p&gt;This is why checking the exact request is so important.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-1&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;PERMISSION_DENIED&lt;/code&gt; can be frustrating because the error itself is often very short while the number of possible causes is not.&lt;/p&gt;
&lt;p&gt;The important thing is to avoid treating it as simply:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Firebase permissions are broken.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Start by finding out &lt;strong&gt;which Firebase service rejected the request&lt;/strong&gt;, then check the exact path, authentication state, security rules, and Firebase project.&lt;/p&gt;
&lt;p&gt;If the error appeared after a recent change, use that as a clue.&lt;/p&gt;
&lt;p&gt;And avoid the temptation to solve everything with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;allow read, write: if true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That only hides the real problem and can leave your data exposed.&lt;/p&gt;
&lt;p&gt;If your error is buried inside a much larger React Native/Firebase log and it is difficult to tell whether the problem is authentication, rules, App Check, configuration, or something else, you can paste the full error into &lt;strong&gt;FixMyError&lt;/strong&gt; and use it to narrow down what is actually going wrong.&lt;/p&gt;
&lt;p&gt;Try it at &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;https://www.fixmyerrorapp.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/28157/how-to-fix-firebase-permissiondenied-errors-in-react-native</guid>
<pubDate>Tue, 22 Sep 2026 12:05:03 +0000</pubDate>
</item>
<item>
<title>How to Fix Expo Doctor Errors in Expo</title>
<link>https://coderlegion.com/26393/how-to-fix-expo-doctor-errors-in-expo</link>
<description>&lt;p&gt;If you work with Expo long enough, you will probably run into &lt;code&gt;expo-doctor&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and instead of everything being green, you get something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check Expo config
✖ Check dependencies
✖ Check package versions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes there is one problem.&lt;/p&gt;
&lt;p&gt;Sometimes there are several.&lt;/p&gt;
&lt;p&gt;And sometimes the output looks much worse than the actual problem.&lt;/p&gt;
&lt;p&gt;The good thing is that &lt;code&gt;expo-doctor&lt;/code&gt; is usually pretty clear about what it does not like. The tricky part is knowing what the warnings actually mean and which ones you need to fix.&lt;/p&gt;
&lt;p&gt;Here is how I usually work through Expo Doctor errors without immediately changing half of the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-run-expo-doctor-by-itself&quot;&gt;First, run Expo Doctor by itself&lt;/h3&gt;
&lt;p&gt;Start with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let it finish and read the full output.&lt;/p&gt;
&lt;p&gt;Do not immediately start changing dependencies just because you see a red &lt;code&gt;✖&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Expo Doctor runs several checks, and a failure in one check does not necessarily mean your entire project is broken.&lt;/p&gt;
&lt;p&gt;For example, you might see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check that packages match versions required by installed Expo SDK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is very different from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check for common project configuration problems
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first one points toward dependency versions.&lt;/p&gt;
&lt;p&gt;The second could require looking at your Expo configuration.&lt;/p&gt;
&lt;p&gt;So the first step is simply figuring out &lt;strong&gt;which check actually failed&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-expo-sdk-version&quot;&gt;Check your Expo SDK version&lt;/h3&gt;
&lt;p&gt;A lot of Expo Doctor problems come down to package versions not matching the Expo SDK you are using.&lt;/p&gt;
&lt;p&gt;You can check your Expo version in &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;dependencies&quot;: {
    &quot;expo&quot;: &quot;~54.0.0&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact version will depend on your project.&lt;/p&gt;
&lt;p&gt;You can also run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo config --type public
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important thing is to know which Expo SDK your project is actually using before changing dependencies.&lt;/p&gt;
&lt;p&gt;This matters because Expo packages are not completely independent of the SDK.&lt;/p&gt;
&lt;p&gt;You cannot always just install the newest version of every package and expect everything to work together.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;fix-packages-that-are-on-the-wrong-version&quot;&gt;Fix packages that are on the wrong version&lt;/h3&gt;
&lt;p&gt;One of the most common Expo Doctor messages is related to package versions.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check that packages match versions required by installed Expo SDK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expo may tell you that some packages are expected to be on different versions.&lt;/p&gt;
&lt;p&gt;If that happens, do not manually guess which version to install.&lt;/p&gt;
&lt;p&gt;For Expo-managed projects, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expo can use your current SDK to install compatible package versions.&lt;/p&gt;
&lt;p&gt;You can also check the project with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install --check
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If it reports packages that are out of date, you can usually let Expo correct them with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install --fix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is generally safer than doing something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install package-name@latest
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;because the latest package version is not necessarily the version your current Expo SDK expects.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;do-not-blindly-update-everything&quot;&gt;Do not blindly update everything&lt;/h3&gt;
&lt;p&gt;This is worth mentioning because it is an easy mistake to make.&lt;/p&gt;
&lt;p&gt;Suppose Expo Doctor tells you that three packages have version mismatches.&lt;/p&gt;
&lt;p&gt;You might think:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;Fine, I'll just update everything.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Then you run something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and suddenly you have five new errors.&lt;/p&gt;
&lt;p&gt;You have now changed many things at once, which makes it harder to figure out what actually caused the problem.&lt;/p&gt;
&lt;p&gt;If Expo is telling you that a package should be on a specific version, start with that package.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install react-native-screens
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rather than manually searching for the newest version and installing it.&lt;/p&gt;
&lt;p&gt;Make one meaningful change at a time when possible.&lt;/p&gt;
&lt;p&gt;It makes debugging much easier.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-package-json-for-old-dependencies&quot;&gt;Check &lt;code&gt;package.json&lt;/code&gt; for old dependencies&lt;/h3&gt;
&lt;p&gt;Another common situation is an old dependency that you no longer use.&lt;/p&gt;
&lt;p&gt;Over time, a project can collect packages from old features, experiments, tutorials, or libraries you tried and later removed.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;package.json&lt;/code&gt; might contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;dependencies&quot;: {
    &quot;some-old-package&quot;: &quot;^1.2.3&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;even though nothing in your application imports it anymore.&lt;/p&gt;
&lt;p&gt;If Expo Doctor complains about that package, first check whether you still need it.&lt;/p&gt;
&lt;p&gt;Search your project for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;some-old-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If there are no imports or references and you know you do not need it anymore, removing the dependency may be the correct fix.&lt;/p&gt;
&lt;p&gt;For npm:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm uninstall some-old-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Yarn:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn remove some-old-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run Expo Doctor again.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-for-packages-that-are-not-compatible-with-your-project&quot;&gt;Check for packages that are not compatible with your project&lt;/h3&gt;
&lt;p&gt;Sometimes the problem is not simply that a package is old.&lt;/p&gt;
&lt;p&gt;A package may require native code or a setup that does not fit the way your Expo project is configured.&lt;/p&gt;
&lt;p&gt;This becomes especially important when adding React Native libraries that were designed with bare React Native projects in mind.&lt;/p&gt;
&lt;p&gt;You may install a package successfully with npm, but that does not automatically mean your Expo project is configured to use it correctly.&lt;/p&gt;
&lt;p&gt;If Expo Doctor starts complaining immediately after adding a new library, that timing is useful information.&lt;/p&gt;
&lt;p&gt;Try to remember:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What changed right before the error appeared?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;If you just installed a package and Expo Doctor started failing, investigate that package first.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-expo-configuration&quot;&gt;Check your Expo configuration&lt;/h3&gt;
&lt;p&gt;Expo Doctor can also complain about your project configuration.&lt;/p&gt;
&lt;p&gt;Depending on your setup, this information may live in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.config.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.config.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For example, your configuration might contain something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;expo&quot;: {
    &quot;name&quot;: &quot;My App&quot;,
    &quot;slug&quot;: &quot;my-app&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you recently changed your configuration, added a plugin, changed an Android or iOS setting, or installed a package that requires an Expo config plugin, check those changes carefully.&lt;/p&gt;
&lt;p&gt;A configuration problem can sometimes look like a dependency problem because several parts of the Expo toolchain depend on the same project configuration.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-expo-config-plugins&quot;&gt;Check Expo config plugins&lt;/h3&gt;
&lt;p&gt;Config plugins are another area that can cause confusion.&lt;/p&gt;
&lt;p&gt;If you have something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;expo&quot;: {
    &quot;plugins&quot;: [
      &quot;some-package&quot;
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;make sure that package is actually installed and that the plugin is valid for your current setup.&lt;/p&gt;
&lt;p&gt;A common situation is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;You install a package.&lt;/li&gt;
&lt;li&gt;You add its plugin to the Expo configuration.&lt;/li&gt;
&lt;li&gt;Later, you remove the package.&lt;/li&gt;
&lt;li&gt;The plugin entry stays behind.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Now Expo may still try to process something that no longer exists.&lt;/p&gt;
&lt;p&gt;If you recently removed a native package, check &lt;code&gt;app.json&lt;/code&gt; or your app config for leftover plugin entries.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-for-duplicate-dependencies&quot;&gt;Check for duplicate dependencies&lt;/h3&gt;
&lt;p&gt;Another useful thing to investigate is duplicate versions of the same package.&lt;/p&gt;
&lt;p&gt;For example, your project might end up with different parts of the dependency tree using different versions of a React Native package.&lt;/p&gt;
&lt;p&gt;You can inspect the dependency tree with npm:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm ls
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or check a specific package:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm ls react-native-screens
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you see multiple versions where you expected one, that can be a clue.&lt;/p&gt;
&lt;p&gt;This does not automatically mean that multiple versions are wrong. Some projects legitimately have them.&lt;/p&gt;
&lt;p&gt;The important part is to understand &lt;strong&gt;why&lt;/strong&gt; they are there before trying to force everything onto one version.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-react-native-version-too&quot;&gt;Check your React Native version too&lt;/h3&gt;
&lt;p&gt;Expo SDK and React Native versions are closely related.&lt;/p&gt;
&lt;p&gt;If you manually changed your React Native version, or upgraded one without properly upgrading the Expo SDK around it, Expo Doctor may start reporting compatibility problems.&lt;/p&gt;
&lt;p&gt;For example, if your &lt;code&gt;package.json&lt;/code&gt; contains:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;dependencies&quot;: {
    &quot;expo&quot;: &quot;~54.0.0&quot;,
    &quot;react-native&quot;: &quot;...&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;make sure those versions belong together.&lt;/p&gt;
&lt;p&gt;If you are upgrading Expo, follow the Expo upgrade process instead of manually changing a bunch of package versions and hoping they line up afterward.&lt;/p&gt;
&lt;p&gt;A partially upgraded project is often harder to fix than a project that was left alone.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;run-expo-doctor-again-after-fixing-something&quot;&gt;Run &lt;code&gt;expo-doctor&lt;/code&gt; again after fixing something&lt;/h3&gt;
&lt;p&gt;This sounds obvious, but it is an important part of the process.&lt;/p&gt;
&lt;p&gt;After making a change, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;again.&lt;/p&gt;
&lt;p&gt;You want to know whether the change actually fixed the problem.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Before:
✖ Check package versions

After:
✔ Check package versions
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you know that particular problem is gone.&lt;/p&gt;
&lt;p&gt;If another check is still failing, you can work on that one separately.&lt;/p&gt;
&lt;p&gt;This is much easier than making ten changes and running Expo Doctor once at the end.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-if-expo-doctor-still-fails-&quot;&gt;What if Expo Doctor still fails?&lt;/h3&gt;
&lt;p&gt;If you have corrected the obvious dependency and configuration problems but Expo Doctor is still reporting an error, look at the &lt;strong&gt;exact check that is failing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;✖ Check for common project configuration problems
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not enough information by itself.&lt;/p&gt;
&lt;p&gt;Read the lines underneath it.&lt;/p&gt;
&lt;p&gt;Expo Doctor normally provides additional information about what it found.&lt;/p&gt;
&lt;p&gt;That information is much more useful than the &lt;code&gt;✖&lt;/code&gt; itself.&lt;/p&gt;
&lt;p&gt;Copy the complete section of the output, not just the first line.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;clear-the-cache-if-the-project-still-behaves-strangely&quot;&gt;Clear the cache if the project still behaves strangely&lt;/h3&gt;
&lt;p&gt;Clearing caches is not usually the first fix for an Expo Doctor warning.&lt;/p&gt;
&lt;p&gt;Expo Doctor is checking your project configuration and dependencies, so a cache reset will not magically fix an incorrect package version.&lt;/p&gt;
&lt;p&gt;However, if you have already fixed the reported problem and the project is still behaving as if the old state exists, clearing the Metro cache can help.&lt;/p&gt;
&lt;p&gt;For Expo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This is particularly useful after changing dependencies, imports, or other files that Metro may have cached.&lt;/p&gt;
&lt;p&gt;Just keep in mind that &lt;strong&gt;clearing Metro's cache and fixing Expo Doctor are two different things&lt;/strong&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-reinstalling-node-modules-makes-sense&quot;&gt;When reinstalling &lt;code&gt;node_modules&lt;/code&gt; makes sense&lt;/h3&gt;
&lt;p&gt;If your package configuration looks correct but your installation itself seems broken, reinstalling dependencies can be worth trying.&lt;/p&gt;
&lt;p&gt;Delete:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then reinstall:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Afterward:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And if necessary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would not delete your lockfile automatically.&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;package-lock.json&lt;/code&gt; or &lt;code&gt;yarn.lock&lt;/code&gt; records the dependency versions that your project is using. Removing it can cause a completely new dependency tree to be installed, which may introduce more changes than you wanted.&lt;/p&gt;
&lt;p&gt;If you specifically need to regenerate the lockfile, that is a different situation.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;be-careful-when-upgrading-an-expo-project&quot;&gt;Be careful when upgrading an Expo project&lt;/h3&gt;
&lt;p&gt;If the reason for your Expo Doctor errors is that you are trying to move to a newer Expo SDK, do not treat it like a normal dependency update.&lt;/p&gt;
&lt;p&gt;An Expo SDK upgrade can involve:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expo packages&lt;/li&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;native dependencies&lt;/li&gt;
&lt;li&gt;config plugins&lt;/li&gt;
&lt;li&gt;Android configuration&lt;/li&gt;
&lt;li&gt;iOS configuration&lt;/li&gt;
&lt;li&gt;other packages tied to the SDK&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you upgrade only one or two pieces, you can end up with a project where everything is technically installed but the versions do not belong together.&lt;/p&gt;
&lt;p&gt;If your goal is specifically to upgrade Expo, follow the upgrade process for the target SDK rather than trying to fix every warning individually.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-simple-way-to-approach-expo-doctor&quot;&gt;A simple way to approach Expo Doctor&lt;/h3&gt;
&lt;p&gt;When I get an Expo Doctor error, I usually work through it in this order:&lt;/p&gt;
&lt;h4 id=&quot;1-run-expo-doctor&quot;&gt;1. Run Expo Doctor&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;2-find-the-failed-check&quot;&gt;2. Find the failed check&lt;/h4&gt;
&lt;p&gt;Do not worry about every line yet. Find the actual &lt;code&gt;✖&lt;/code&gt; checks.&lt;/p&gt;
&lt;h4 id=&quot;3-read-the-details-underneath&quot;&gt;3. Read the details underneath&lt;/h4&gt;
&lt;p&gt;That is usually where the useful information is.&lt;/p&gt;
&lt;h4 id=&quot;4-check-what-changed-recently&quot;&gt;4. Check what changed recently&lt;/h4&gt;
&lt;p&gt;Did you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;install a package?&lt;/li&gt;
&lt;li&gt;update Expo?&lt;/li&gt;
&lt;li&gt;update React Native?&lt;/li&gt;
&lt;li&gt;remove a package?&lt;/li&gt;
&lt;li&gt;change &lt;code&gt;app.json&lt;/code&gt;?&lt;/li&gt;
&lt;li&gt;add a config plugin?&lt;/li&gt;
&lt;li&gt;switch branches?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Recent changes are often the best clue.&lt;/p&gt;
&lt;h4 id=&quot;5-fix-the-smallest-thing-first&quot;&gt;5. Fix the smallest thing first&lt;/h4&gt;
&lt;p&gt;If a package version is wrong, fix that package.&lt;/p&gt;
&lt;p&gt;If a plugin is left behind, remove it.&lt;/p&gt;
&lt;p&gt;If a dependency is no longer needed, uninstall it.&lt;/p&gt;
&lt;h4 id=&quot;6-run-expo-doctor-again&quot;&gt;6. Run Expo Doctor again&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then see what remains.&lt;/p&gt;
&lt;p&gt;This gives you a much cleaner debugging loop.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;one-example&quot;&gt;One example&lt;/h3&gt;
&lt;p&gt;Imagine you run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and get a dependency version warning.&lt;/p&gt;
&lt;p&gt;You check &lt;code&gt;package.json&lt;/code&gt; and notice that you manually installed a newer version of an Expo-related package a few days ago.&lt;/p&gt;
&lt;p&gt;Instead of immediately reinstalling everything, you can try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or, if Expo identifies the package as needing a correction:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install --fix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;again.&lt;/p&gt;
&lt;p&gt;If the check passes, you have your answer.&lt;/p&gt;
&lt;p&gt;If it does not, you now have a smaller problem to investigate.&lt;/p&gt;
&lt;p&gt;That is the part that makes debugging much easier: &lt;strong&gt;change something, test it, and use the new output to decide what to do next.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-2&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;Expo Doctor is there to help you find problems before they turn into much bigger issues.&lt;/p&gt;
&lt;p&gt;The important thing is not to treat every warning as a reason to rebuild your entire project.&lt;/p&gt;
&lt;p&gt;Start with the exact check that failed.&lt;/p&gt;
&lt;p&gt;If it is a dependency problem, check the package versions.&lt;/p&gt;
&lt;p&gt;If it is a configuration problem, check your Expo config.&lt;/p&gt;
&lt;p&gt;If you recently installed or removed a package, investigate that change.&lt;/p&gt;
&lt;p&gt;And if you are upgrading Expo, make sure you are upgrading the related dependencies together instead of manually mixing versions.&lt;/p&gt;
&lt;p&gt;Most importantly, avoid making a dozen changes at once. When you do that, even if the error disappears, you have no idea which change actually fixed it.&lt;/p&gt;
&lt;p&gt;If your Expo Doctor output is more complicated—especially when several checks fail at the same time—you can paste the full error into &lt;strong&gt;FixMyError&lt;/strong&gt; and use it to break down what the output is actually telling you.&lt;/p&gt;
&lt;p&gt;You can try it at &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;https://www.fixmyerrorapp.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/26393/how-to-fix-expo-doctor-errors-in-expo</guid>
<pubDate>Mon, 07 Sep 2026 11:14:51 +0000</pubDate>
</item>
<item>
<title>Flutter vs React Native for MVP: Which One Should You Actually Choose?</title>
<link>https://coderlegion.com/26095/flutter-vs-react-native-for-mvp-which-one-should-you-actually-choose</link>
<description>&lt;p&gt;You've validated the idea. You've got a rough spec, maybe a Figma file, and a deadline that's tighter than you'd like. Now you're stuck on a decision that feels bigger than it should: Flutter or React Native.&lt;/p&gt;
&lt;p&gt;We get why this question causes so much hesitation. Picking wrong doesn't just mean a slower app; it can mean rebuilding parts of it six months in, paying more for developers than you budgeted, or watching your MVP timeline slip because the framework didn't fit your team's skill set. For a founder trying to get to market before the runway runs out, that's not a small mistake.&lt;/p&gt;
&lt;p&gt;This isn't a &quot;&lt;strong&gt;Flutter is better&lt;/strong&gt;&quot; or &quot;&lt;strong&gt;React Native is better&lt;/strong&gt;&quot; article. It's a breakdown of what actually determines the right choice for an MVP- specifically, not for a five-year-old enterprise app, not for a game studio, but for a small team trying to ship something real, get it in front of users, and learn fast.&lt;/p&gt;
&lt;h2 id=&quot;the-real-question-isn-t-which-framework-is-better-&quot;&gt;The Real Question Isn't &quot;Which Framework Is Better&quot;&lt;/h2&gt;
&lt;p&gt;It's &quot;which framework fits your constraints.&quot; For an MVP, three constraints matter more than anything else:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;How fast can you build and test&lt;/li&gt;
&lt;li&gt;What talent you can actually hire or already have&lt;/li&gt;
&lt;li&gt;How much native-feeling polish your app needs on day one&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Framework benchmarks and GitHub star counts don't answer any of those. Your team, your budget, and your app's complexity do. If you want a broader look at how Flutter and React Native compare across performance, cost, platform support, and scalability, our &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nebulatechit.com/blogs/react-native-vs-flutter/&quot;&gt;React Native vs Flutter comparison&lt;/a&gt; covers those differences in more detail. &lt;/p&gt;
&lt;h2 id=&quot;flutter-what-it-s-actually-good-at&quot;&gt;Flutter: What It's Actually Good At&lt;/h2&gt;
&lt;p&gt;Flutter, built by Google, compiles to native code and uses its own rendering pipeline rather than relying entirely on native OS UI components. On iOS and Android API 29+, Flutter's Impeller rendering engine is the default, giving the framework a rendering pipeline designed for predictable performance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Consistent UI across platforms&lt;/strong&gt;. A button looks and behaves the same on iOS and Android because Flutter draws it itself. For MVPs with a strong design system or a founder who cares about pixel-level consistency, this is a real advantage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strong performance for animation-heavy apps.&lt;/strong&gt; If your MVP leans on custom transitions, gestures, or visual polish, Flutter tends to handle that more predictably than React Native, since it isn't bridging through native UI components.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Single codebase, genuinely.&lt;/strong&gt; Flutter's &quot;write once&quot; promise holds up better in practice than it used to. Widget-based architecture means less platform-specific patching for most standard app types.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ecosystem familiarity matters when hiring.&lt;/strong&gt; Developer surveys such as the &lt;a rel=&quot;nofollow&quot; href=&quot;https://survey.stackoverflow.co/2024/&quot;&gt;Stack Overflow Developer Survey&lt;/a&gt; provide a useful snapshot of the technologies developers use and want to learn, which can help put your team's existing skills and hiring options into context. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt; Flutter apps are written in Dart, a language most web developers haven't touched. If your existing team knows JavaScript or TypeScript, you're either training them or hiring fresh, and Dart talent is a smaller pool than JavaScript talent in most markets.&lt;/p&gt;
&lt;h2 id=&quot;react-native-what-it-s-actually-good-at&quot;&gt;React Native: What It's Actually Good At&lt;/h2&gt;
&lt;p&gt;React Native, maintained by Meta, uses JavaScript or TypeScript, and its New Architecture includes Fabric for rendering and direct communication between JavaScript and native code through JSI. That gives it a different set of strengths:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Access to a broader pool of JavaScript/TypeScript talent.&lt;/strong&gt; If you already have web developers who know React, the learning curve to React Native is short. For early-stage teams that can't afford long onboarding, this matters more than most technical comparisons.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Faster initial builds for standard app patterns.&lt;/strong&gt; Forms, lists, navigation, and API-driven screens the bread and butter of most MVPs are well-trodden territory in React Native, with a large ecosystem of libraries already built for these use cases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Easier path to a companion web app.&lt;/strong&gt; If there's any chance your MVP will need a web version later, React skills transfer more directly than Dart skills.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The trade-off:&lt;/strong&gt; React Native can still require additional optimization for highly animated, graphics-heavy, or gesture-intensive experiences, particularly when your app relies heavily on platform-specific functionality or complex native integrations. For a straightforward CRUD-style MVP, this is less likely to be a deciding factor. &lt;/p&gt;
&lt;h2 id=&quot;where-founders-get-this-decision-wrong&quot;&gt;Where Founders Get This Decision Wrong&lt;/h2&gt;
&lt;p&gt;We see the same few mistakes repeatedly when teams choose a framework for an MVP:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Choosing based on long-term scale instead of MVP speed.&lt;/strong&gt; An MVP's job is to test a hypothesis, not to be future-proof for a million users. Optimizing for hypothetical scale before you have real usage data solves a problem you might never have.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ignoring existing team skills.&lt;/strong&gt; If you already have a React or JavaScript-heavy team and choose Flutter because a benchmark article said it's faster, you've added a learning curve to an already tight timeline. That cost is real and often larger than the performance gap you were chasing. Research on &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mdpi.com/2227-9709/12/2/45&quot;&gt;Flutter and React Native developer trends&lt;/a&gt; also highlights differences in developer satisfaction and job-market demand, reinforcing why your team's existing skills should factor into the decision. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Treating this as a permanent decision.&lt;/strong&gt; Your MVP doesn't need to solve every future technical requirement on day one. But switching frameworks later still means significant redevelopment, so the goal should be to make a sensible decision now, not assume you'll simply rebuild when the product grows.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Skipping a technical spike.&lt;/strong&gt; Before committing, building a tiny proof-of-concept one core screen, one core interaction in both frameworks for a day or two can surface friction that no comparison article will predict for your specific use case.&lt;/p&gt;
&lt;h2 id=&quot;a-practical-way-to-decide&quot;&gt;A Practical Way to Decide&lt;/h2&gt;
&lt;p&gt;If you're still unsure, this is roughly how we'd narrow it down:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;If your team already knows JavaScript/React, and your MVP is mostly forms, lists, and API calls, lean React Native.&lt;/li&gt;
&lt;li&gt;If your MVP depends heavily on custom animation, a highly branded UI, or gesture-based interaction, lean Flutter.&lt;/li&gt;
&lt;li&gt;If you're hiring contractors and want the largest available talent pool, lean React Native.&lt;/li&gt;
&lt;li&gt;If highly consistent rendering and custom visual interactions matter more than developer availability, lean Flutter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Neither framework is a wrong answer for most MVPs. The wrong answer is picking one for reasons that don't match your actual constraints.&lt;/p&gt;
&lt;p&gt;For teams weighing this decision alongside broader technical planning, work through this exact evaluation with founders before writing a single line of code, mapping the framework choice to the team, budget, and timeline rather than a generic recommendation.&lt;/p&gt;
&lt;h2 id=&quot;the-bottom-line&quot;&gt;The Bottom Line&lt;/h2&gt;
&lt;p&gt;Neither Flutter nor React Native is objectively better; they’re better for different situations. The right choice for your MVP comes down to what your team already knows, how visually complex your app needs to be, and how much you’re prioritizing speed versus long-term flexibility.&lt;/p&gt;
&lt;p&gt;If you’re still weighing the trade-offs, the most useful next step isn’t reading another comparison. Build a small proof-of-concept in whichever framework you’re leaning toward and see how it actually feels to work with for a day. That will tell you more than any benchmark.&lt;/p&gt;
&lt;h2 id=&quot;faqs&quot;&gt;FAQs&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;1. Is Flutter or React Native faster to build an MVP with?&lt;/strong&gt;&lt;br&gt;
 It depends on your team's existing skills more than the framework itself. A JavaScript-fluent team will usually move faster in React Native; a team with no strong language preference may find Flutter's tooling and hot reload just as fast, sometimes faster for UI-heavy screens.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Which framework is cheaper for an MVP?&lt;/strong&gt;&lt;br&gt;
Cost tracks talent availability more than the technology itself. React Native developers are generally more abundant, which can mean more competitive rates, particularly for freelance or contract hires.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Can I switch frameworks later if I start with the wrong one?&lt;/strong&gt;&lt;br&gt;
Yes, though it means a rebuild of the front end, not a simple migration. Many teams treat the MVP framework choice as temporary by design, planning to revisit it once the product has real user data and funding.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Does Flutter or React Native perform better?&lt;/strong&gt;&lt;br&gt;
For most standard MVP app types forms, dashboards, content feeds the performance difference is negligible to end users. The gap widens for animation-heavy or graphics-intensive apps, where Flutter tends to have an edge. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26095/flutter-vs-react-native-for-mvp-which-one-should-you-actually-choose</guid>
<pubDate>Wed, 02 Sep 2026 21:53:13 +0000</pubDate>
</item>
<item>
<title>How to Fix “Unable to Resolve Module” Errors in React Native and Expo</title>
<link>https://coderlegion.com/25620/how-to-fix-unable-to-resolve-module-errors-in-react-native-and-expo</link>
<description>&lt;h2 id=&quot;how-to-fix-unable-to-resolve-module-errors-in-react-native-and-expo&quot;&gt;How to Fix “Unable to Resolve Module” Errors in React Native and Expo&lt;/h2&gt;
&lt;p&gt;If you have been working with React Native or Expo for a while, there is a good chance you have seen an error that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ./components/Header from App.js

None of these files exist:
  * components/Header(.native|.ios|.android|.js|.jsx|.ts|.tsx)
  * components/Header/index(.native|.ios|.android|.js|.jsx|.ts|.tsx)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or maybe:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module react-native-safe-area-context from App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or the error is buried somewhere inside a much larger Metro bundler log, making it harder to see what is actually wrong.&lt;/p&gt;
&lt;p&gt;The annoying part about this error is that &lt;strong&gt;&quot;Unable to resolve module&quot; does not always mean the same thing&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Sometimes the file path is wrong. Sometimes the package was never installed. Sometimes it is installed, but your dependencies are in a bad state. And sometimes everything looks correct until you notice that one letter in a filename has the wrong capitalization.&lt;/p&gt;
&lt;p&gt;I've run into all of these before.&lt;/p&gt;
&lt;p&gt;So instead of randomly deleting &lt;code&gt;node_modules&lt;/code&gt; and hoping for the best, here is how I usually work through this error.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-read-the-exact-module-metro-cannot-find&quot;&gt;First, read the exact module Metro cannot find&lt;/h3&gt;
&lt;p&gt;This sounds obvious, but the first line of the error usually tells you exactly what Metro is looking for.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ./components/Header from App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Metro is trying to find:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;./components/Header
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;relative to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That immediately gives you something to check.&lt;/p&gt;
&lt;p&gt;Ask yourself:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Does that file actually exist?&lt;/li&gt;
&lt;li&gt;Is the path correct?&lt;/li&gt;
&lt;li&gt;Is the filename spelled correctly?&lt;/li&gt;
&lt;li&gt;Are you importing it from the correct folder?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, imagine your project looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-app/
├── App.js
└── components/
    └── Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then this import should work:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But this will not:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./component/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;One missing &lt;code&gt;s&lt;/code&gt; is enough to break the build.&lt;/p&gt;
&lt;p&gt;When the error points to a local file, I usually start here before doing anything else.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-the-file-name-and-capitalization&quot;&gt;Check the file name and capitalization&lt;/h3&gt;
&lt;p&gt;This one can be surprisingly annoying, especially if your app worked on one machine but suddenly fails during a build or after pushing the project somewhere else.&lt;/p&gt;
&lt;p&gt;Imagine your file is named:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But your import says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Depending on the operating system and environment, this can cause problems because:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;are not always treated as the same file.&lt;/p&gt;
&lt;p&gt;The same thing can happen with folders:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Components/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;vs:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;components/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If Metro says it cannot find a local module and the path looks correct at first glance, check every part of the path carefully.&lt;/p&gt;
&lt;p&gt;I have lost more time than I would like to admit because of a single capital letter.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;make-sure-the-package-is-actually-installed&quot;&gt;Make sure the package is actually installed&lt;/h3&gt;
&lt;p&gt;Sometimes the missing module is not one of your own files.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In that case, check whether the package exists in your project.&lt;/p&gt;
&lt;p&gt;You can look inside &lt;code&gt;package.json&lt;/code&gt; and see whether it is listed under &lt;code&gt;dependencies&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For an Expo project, it is usually better to install Expo-compatible packages with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For a regular React Native project, you would normally use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn add react-native-safe-area-context
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The exact command depends on your project, but the important part is this:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Do not assume a package is installed just because you used it in your code.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It is also possible that you copied code from another project, tutorial, or GitHub repository without installing all of the required dependencies.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-if-the-package-name-is-correct&quot;&gt;Check if the package name is correct&lt;/h3&gt;
&lt;p&gt;Another simple but common problem is importing the wrong package name.&lt;/p&gt;
&lt;p&gt;For example, you might write something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Something from &quot;react-native-something&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but the actual package name could be slightly different.&lt;/p&gt;
&lt;p&gt;Before reinstalling everything, check:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The package name in your import.&lt;/li&gt;
&lt;li&gt;The package name in &lt;code&gt;package.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The package name from the package documentation.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;One small typo can produce the same &quot;Unable to resolve module&quot; error as a completely missing dependency.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;reset-the-metro-cache&quot;&gt;Reset the Metro cache&lt;/h3&gt;
&lt;p&gt;If the file or package exists and the import looks correct, the next thing I would try is clearing Metro's cache.&lt;/p&gt;
&lt;p&gt;For Expo:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For React Native, you can try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native start --reset-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Metro caches information to make development faster, but occasionally the cache gets in the way after things change.&lt;/p&gt;
&lt;p&gt;For example, you might have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;renamed a file,&lt;/li&gt;
&lt;li&gt;moved a folder,&lt;/li&gt;
&lt;li&gt;installed a new package,&lt;/li&gt;
&lt;li&gt;changed an import,&lt;/li&gt;
&lt;li&gt;switched branches in Git.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the project is still behaving like the old version exists, clearing the cache is a quick thing to try.&lt;/p&gt;
&lt;p&gt;I would not start by deleting half the project, though. Resetting the cache is much less destructive.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-your-node-modules&quot;&gt;Check your &lt;code&gt;node_modules&lt;/code&gt;&lt;/h3&gt;
&lt;p&gt;Sometimes the dependency is listed in &lt;code&gt;package.json&lt;/code&gt;, but something went wrong during installation.&lt;/p&gt;
&lt;p&gt;This can happen after:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;switching branches,&lt;/li&gt;
&lt;li&gt;changing package versions,&lt;/li&gt;
&lt;li&gt;interrupting an installation,&lt;/li&gt;
&lt;li&gt;moving the project to another computer,&lt;/li&gt;
&lt;li&gt;or getting dependency conflicts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A common approach is to reinstall your dependencies.&lt;/p&gt;
&lt;p&gt;First, remove &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Then run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or, if you use Yarn:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;yarn install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, start the project again and clear Metro's cache.&lt;/p&gt;
&lt;p&gt;For Expo, that could look like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Try to use the same package manager consistently. If your project uses &lt;code&gt;package-lock.json&lt;/code&gt;, randomly switching between npm, Yarn, and other package managers can create confusing dependency problems.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-you-are-importing-from-the-correct-location&quot;&gt;Check whether you are importing from the correct location&lt;/h3&gt;
&lt;p&gt;This is especially easy to miss after moving files around.&lt;/p&gt;
&lt;p&gt;Imagine you originally had:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── screens/
│   └── HomeScreen.js
└── components/
    └── Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside &lt;code&gt;HomeScreen.js&lt;/code&gt;, you might import:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;../components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then later, you move &lt;code&gt;HomeScreen.js&lt;/code&gt; somewhere else:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;src/
├── app/
│   └── screens/
│       └── HomeScreen.js
└── components/
    └── Header.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The old import path may no longer be correct.&lt;/p&gt;
&lt;p&gt;You would need to update it accordingly:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;../../components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;When you see an &quot;Unable to resolve module&quot; error after reorganizing your project, check whether the import path still matches the new folder structure.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;be-careful-with-relative-paths&quot;&gt;Be careful with relative paths&lt;/h3&gt;
&lt;p&gt;Relative imports can become difficult to read when your project grows.&lt;/p&gt;
&lt;p&gt;You might end up with something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Something from &quot;../../../../components/Something&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At that point, it becomes much easier to accidentally use one &lt;code&gt;../&lt;/code&gt; too many or too few.&lt;/p&gt;
&lt;p&gt;If the error points to a long relative path, carefully trace it from the file where the import is written.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;../../../components/Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Do not just count the dots and slashes quickly. Actually check the location of the current file and follow the path step by step.&lt;/p&gt;
&lt;p&gt;It is boring, but it is often faster than trying random fixes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-whether-the-package-is-compatible-with-expo&quot;&gt;Check whether the package is compatible with Expo&lt;/h3&gt;
&lt;p&gt;This is more important if you are using Expo.&lt;/p&gt;
&lt;p&gt;You may find a React Native package online, install it successfully, and then run into problems because the version does not match your Expo SDK or the package needs native setup that your project does not currently support.&lt;/p&gt;
&lt;p&gt;When possible, use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install package-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;instead of blindly installing the latest version with npm.&lt;/p&gt;
&lt;p&gt;Expo can choose a version that is compatible with your current SDK.&lt;/p&gt;
&lt;p&gt;You can also run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to check your project for dependency and configuration issues.&lt;/p&gt;
&lt;p&gt;If Metro suddenly cannot resolve something after installing or updating a package, compatibility is worth checking.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;watch-out-for-renamed-or-removed-files&quot;&gt;Watch out for renamed or removed files&lt;/h3&gt;
&lt;p&gt;Another situation that can cause this error is when your code still imports something that no longer exists.&lt;/p&gt;
&lt;p&gt;For example, you rename:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OldButton.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;CustomButton.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but somewhere in the project, you still have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import OldButton from &quot;./OldButton&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The error might not happen immediately if that file is not loaded until you visit a specific screen.&lt;/p&gt;
&lt;p&gt;This is why I would search the project for the old filename.&lt;/p&gt;
&lt;p&gt;Your editor's global search is useful here. Search for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;OldButton
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and see whether any old imports are still left behind.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-index-files&quot;&gt;Check &lt;code&gt;index&lt;/code&gt; files&lt;/h3&gt;
&lt;p&gt;Sometimes the missing module is actually caused by an &lt;code&gt;index.js&lt;/code&gt; or &lt;code&gt;index.ts&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;components/
├── Button/
│   ├── Button.js
│   └── index.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You might import it like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Button from &quot;./components/Button&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That usually depends on Metro being able to resolve the folder's &lt;code&gt;index&lt;/code&gt; file correctly.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;index.js&lt;/code&gt; was deleted, renamed, or contains a broken export, your import setup may stop working.&lt;/p&gt;
&lt;p&gt;Check whether the folder contains the file Metro expects.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export { default } from &quot;./Button&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export { Button } from &quot;./Button&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The correct setup depends on whether you are using default or named exports.&lt;/p&gt;
&lt;p&gt;The important part is to check the whole import chain, not just the first file.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;default-exports-and-named-exports-are-a-different-problem-but-still-worth-checking&quot;&gt;Default exports and named exports are a different problem, but still worth checking&lt;/h3&gt;
&lt;p&gt;This usually causes a different error, but it can easily appear around the same time.&lt;/p&gt;
&lt;p&gt;For example, this is a default export:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export default Header;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it is imported like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import Header from &quot;./Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A named export looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;export const Header = () =&amp;gt; {
  return null;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And it should be imported like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { Header } from &quot;./Header&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you have already fixed the module resolution error but the app still fails afterward, this is one of the next things I would check.&lt;/p&gt;
&lt;p&gt;Sometimes you fix one problem and Metro finally gets far enough to show you the next one.&lt;/p&gt;
&lt;p&gt;That is just part of debugging.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;monorepos-can-make-module-resolution-more-complicated&quot;&gt;Monorepos can make module resolution more complicated&lt;/h3&gt;
&lt;p&gt;If you are using a monorepo, workspaces, shared packages, or code outside the main app folder, Metro module resolution can become more complicated.&lt;/p&gt;
&lt;p&gt;Your project might look something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;my-project/
├── apps/
│   └── mobile/
└── packages/
    └── shared/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the React Native or Expo app might need to import code from:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;packages/shared
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In these cases, a normal relative import may not be enough. Metro may need additional configuration to watch or resolve files outside the main project directory.&lt;/p&gt;
&lt;p&gt;If you only started seeing the error after introducing a workspace or shared package, I would focus on your Metro configuration instead of repeatedly reinstalling dependencies.&lt;/p&gt;
&lt;p&gt;Monorepo issues usually need a more specific fix because the correct setup depends heavily on how the project is structured.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;if-you-use-expo-router-check-your-imports-and-project-structure&quot;&gt;If you use Expo Router, check your imports and project structure&lt;/h3&gt;
&lt;p&gt;Expo Router adds another layer to how files are organized.&lt;/p&gt;
&lt;p&gt;For example, your routes may live inside:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and you may have layouts, route groups, and nested folders.&lt;/p&gt;
&lt;p&gt;If you recently moved or renamed route files, double-check your imports and references.&lt;/p&gt;
&lt;p&gt;Also make sure the required Expo Router packages are correctly installed for your current Expo setup.&lt;/p&gt;
&lt;p&gt;When something goes wrong inside a routing setup, the error message may make it look like a general Metro problem when the real issue is simply an incorrect import somewhere inside a route or layout.&lt;/p&gt;
&lt;p&gt;Again, start with the exact module Metro says it cannot find.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;do-not-immediately-delete-everything&quot;&gt;Do not immediately delete everything&lt;/h3&gt;
&lt;p&gt;When developers hit a module resolution error, a common reaction is to do all of this at once:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Delete node_modules
Delete package-lock.json
Clear Metro
Clear npm cache
Reinstall everything
Restart the computer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Sometimes that works.&lt;/p&gt;
&lt;p&gt;But it can also make debugging harder because now you have changed five different things and do not know what actually fixed the problem.&lt;/p&gt;
&lt;p&gt;I would usually check the error in this order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Read the exact missing module.&lt;/li&gt;
&lt;li&gt;Check whether the file or package exists.&lt;/li&gt;
&lt;li&gt;Check the spelling and capitalization.&lt;/li&gt;
&lt;li&gt;Check the import path.&lt;/li&gt;
&lt;li&gt;Check whether the dependency is installed.&lt;/li&gt;
&lt;li&gt;Clear the Metro cache.&lt;/li&gt;
&lt;li&gt;Reinstall dependencies if necessary.&lt;/li&gt;
&lt;li&gt;Check Expo or React Native version compatibility.&lt;/li&gt;
&lt;li&gt;Investigate Metro configuration for more complex project setups.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is usually much faster than throwing random commands at the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-quick-example&quot;&gt;A quick example&lt;/h3&gt;
&lt;p&gt;Imagine you get this error:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Unable to resolve module ./components/ProfileCard from App.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You check your project and see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;components/
└── Profilecard.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is the capitalization.&lt;/p&gt;
&lt;p&gt;Your import says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import ProfileCard from &quot;./components/ProfileCard&quot;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But the actual file is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Profilecard.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Rename the file so it matches:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ProfileCard.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then restart Metro if necessary:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That is a simple example, but it shows why reading the exact error matters.&lt;/p&gt;
&lt;p&gt;The error is not always asking you to reinstall your entire project. Sometimes Metro is simply telling you that it cannot find something because the path does not exactly match what exists on disk.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-3&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;&quot;Unable to resolve module&quot; errors can look more serious than they really are.&lt;/p&gt;
&lt;p&gt;Most of the time, the problem comes down to one of these things:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A wrong import path&lt;/li&gt;
&lt;li&gt;A missing package&lt;/li&gt;
&lt;li&gt;A typo in the package name&lt;/li&gt;
&lt;li&gt;A filename capitalization problem&lt;/li&gt;
&lt;li&gt;A stale Metro cache&lt;/li&gt;
&lt;li&gt;Broken or missing dependencies&lt;/li&gt;
&lt;li&gt;A file that was moved or renamed&lt;/li&gt;
&lt;li&gt;A more complex Metro configuration issue&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The biggest thing I have learned is to &lt;strong&gt;start with the exact module Metro says it cannot find&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Do not start by reinstalling the entire project.&lt;/p&gt;
&lt;p&gt;Check what Metro is looking for, where it is looking from, and whether that file or package actually exists. In many cases, the answer is much simpler than the size of the error message makes it seem.&lt;/p&gt;
&lt;p&gt;If your error is much messier than this—especially if the missing module is buried inside a large Expo, Metro, dependency, or build error—you can also paste the full error into &lt;strong&gt;FixMyError&lt;/strong&gt; and get help breaking down what is actually causing it.&lt;/p&gt;
&lt;p&gt;Sometimes the hard part is not finding a fix. It is figuring out which part of a long error log actually matters.&lt;/p&gt;
&lt;p&gt;Try it here: &lt;strong&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;https://www.fixmyerrorapp.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/25620/how-to-fix-unable-to-resolve-module-errors-in-react-native-and-expo</guid>
<pubDate>Fri, 28 Aug 2026 13:36:15 +0000</pubDate>
</item>
<item>
<title>React Native Developer Hiring Models: Freelance, Dedicated or Agency?</title>
<link>https://coderlegion.com/24605/react-native-developer-hiring-models-freelance-dedicated-or-agency</link>
<description>&lt;p&gt;Choosing a React Native developer sounds simple until the hiring model starts affecting your budget, control, and delivery timeline.&lt;/p&gt;
&lt;p&gt;Finding a React Native developer is not usually the difficult part. The harder decision is figuring out how that developer should work with your business.&lt;/p&gt;
&lt;p&gt;You could hire a freelancer for a specific feature, bring in a dedicated developer to work on the product long term, or partner with an agency that handles development as part of a larger team. All three options can work. The problem starts when the hiring model does not match the way your product needs to be built.&lt;/p&gt;
&lt;p&gt;If you already know that your project requires React Native expertise, understanding the available &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nebulatechit.com/hire-react-native-developer/&quot;&gt;React Native developer&lt;/a&gt; hiring options can help you evaluate the right type of engagement before you commit to a developer or team.&lt;/p&gt;
&lt;p&gt;The decision may look like a simple pricing comparison at first, but it can eventually affect development speed, communication, technical ownership, and ongoing support.&lt;/p&gt;
&lt;h2 id=&quot;what-goes-wrong-when-you-choose-the-wrong-hiring-model-&quot;&gt;What Goes Wrong When You Choose the Wrong Hiring Model?&lt;/h2&gt;
&lt;p&gt;The difference between a freelance React Native developer, a dedicated developer, and a development agency is about more than the amount you pay per hour.&lt;/p&gt;
&lt;p&gt;The problems usually appear after development is already underway.&lt;/p&gt;
&lt;p&gt;A few months into the project, availability can become an issue. A freelancer may have other client commitments, which can make it harder to handle an urgent bug, a new feature, or a release that suddenly needs more attention.&lt;/p&gt;
&lt;p&gt;A major release can expose gaps in your development setup. A React Native application may require testing, debugging, native integrations, performance optimization, and app-store preparation. If one person is responsible for everything, the workload can quickly become difficult to manage.&lt;/p&gt;
&lt;p&gt;Product growth can turn one developer into a bottleneck. An MVP might be manageable for one person, but adding payments, authentication, analytics, third-party APIs, new screens, and ongoing maintenance can change the workload considerably.&lt;/p&gt;
&lt;p&gt;Changing requirements can create friction. Once real users start using an app, the original specification rarely remains untouched. New feedback can lead to changes in features, priorities, or timelines, and some hiring arrangements are better suited to that flexibility than others.&lt;/p&gt;
&lt;p&gt;So instead of asking, “&lt;strong&gt;Which React Native developer is cheapest?&lt;/strong&gt;”, it is more useful to ask, “&lt;strong&gt;Which hiring model gives this project the right balance of flexibility, control, expertise, and support?&lt;/strong&gt;”&lt;/p&gt;
&lt;h2 id=&quot;freelance-react-native-developer-a-practical-choice-for-focused-work&quot;&gt;Freelance React Native Developer: A Practical Choice for Focused Work&lt;/h2&gt;
&lt;p&gt;Hiring a freelance React Native developer can make sense when you already know what needs to be built and the scope is relatively contained.&lt;/p&gt;
&lt;p&gt;For example, you might need someone to add a feature to an existing app, fix a group of bugs, build an MVP, integrate an API, or provide extra development capacity while your internal team handles the rest.&lt;/p&gt;
&lt;p&gt;The appeal is straightforward: you get access to React Native expertise without building a permanent team around the project.&lt;/p&gt;
&lt;p&gt;The limitation becomes more noticeable when the product keeps growing. If you suddenly need QA, another developer, project management, or ongoing technical support, the original freelance arrangement may no longer be enough.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A freelancer is worth considering when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have a clearly defined development task.&lt;/li&gt;
&lt;li&gt;The project is relatively small or short-term.&lt;/li&gt;
&lt;li&gt;You need additional development capacity.&lt;/li&gt;
&lt;li&gt;You already have someone managing the technical work.&lt;/li&gt;
&lt;li&gt;You need a specific React Native skill for a particular feature.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;dedicated-react-native-developer-better-for-products-that-keep-evolving&quot;&gt;Dedicated React Native Developer: Better for Products That Keep Evolving&lt;/h2&gt;
&lt;p&gt;A dedicated React Native developer is a different arrangement. Instead of bringing someone in for one isolated task, you have a developer working consistently on your product over a longer period.&lt;/p&gt;
&lt;p&gt;That continuity matters when the application is going to change frequently. The developer becomes familiar with the codebase, architecture, product requirements, release process, and technical decisions that have already been made.&lt;/p&gt;
&lt;p&gt;This can reduce the time spent explaining the same product context repeatedly and make it easier to keep development moving as new requirements appear.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A dedicated developer makes more sense when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The product will need continuous development.&lt;/li&gt;
&lt;li&gt;You expect regular feature releases&lt;/li&gt;
&lt;li&gt;You want more control over development priorities.&lt;/li&gt;
&lt;li&gt;The developer needs to understand your existing codebase deeply.&lt;/li&gt;
&lt;li&gt;Maintenance and improvements will continue after launch.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For a product that is still finding its direction, this model can also provide more flexibility than treating every new requirement as a separate project.&lt;/p&gt;
&lt;h2 id=&quot;react-native-development-agency-useful-when-the-project-needs-more-than-one-developer&quot;&gt;React Native Development Agency: Useful When the Project Needs More Than One Developer&lt;/h2&gt;
&lt;p&gt;An agency becomes relevant when the project is bigger than the mobile development work itself.&lt;/p&gt;
&lt;p&gt;Instead of hiring one person, you are working with a team that may include React Native developers, UI/UX designers, QA specialists, project managers, backend developers, or other technical resources.&lt;/p&gt;
&lt;p&gt;That can remove some of the coordination work from your side. You do not necessarily have to find a separate person for every discipline required to get the application from an idea to a production release.&lt;/p&gt;
&lt;p&gt;There is a trade-off, though. A broader team and managed delivery structure generally come with a higher overall engagement cost than hiring one developer directly. You also need to be comfortable with how the agency manages communication, resources, timelines, and technical decisions.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;An agency may be the better option when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You need an application developed from start to finish.&lt;/li&gt;
&lt;li&gt;Your company does not have a technical team.&lt;/li&gt;
&lt;li&gt;The project requires several different specialists.&lt;/li&gt;
&lt;li&gt;You need someone to coordinate the development process.&lt;/li&gt;
&lt;li&gt;You have a defined product goal but limited internal development resources.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;look-beyond-the-hiring-model-when-evaluating-a-react-native-developer&quot;&gt;Look Beyond the Hiring Model When Evaluating a React Native Developer&lt;/h2&gt;
&lt;p&gt;Choosing the engagement model is only one part of the decision. The developer or team still needs the technical ability to handle the requirements of the application.&lt;/p&gt;
&lt;p&gt;React Native development can involve considerably more than building screens and connecting components. For example, &lt;a rel=&quot;nofollow&quot; href=&quot;https://docs.aws.amazon.com/prescriptive-guidance/latest/patterns/build-a-serverless-react-native-mobile-app-by-using-aws-amplify.html&quot;&gt;AWS's React Native implementation&lt;/a&gt; guidance covers requirements involving React Native, JavaScript, Node.js, APIs, authentication, databases, iOS, and Android development. That is a useful reminder that technical evaluation should reflect the actual architecture and integrations your application will require. &lt;/p&gt;
&lt;p&gt;For a more complex application, ask about previous experience with API integrations, authentication, state management, native modules, performance issues, testing, deployment, and maintaining applications after launch.&lt;/p&gt;
&lt;p&gt;A portfolio can also be useful, but screenshots alone do not tell you what the developer actually contributed. Ask which parts they built, what technical problems they encountered, and how the application was maintained after release.&lt;/p&gt;
&lt;h2 id=&quot;match-the-hiring-model-to-where-your-product-is-right-now&quot;&gt;Match the Hiring Model to Where Your Product Is Right Now&lt;/h2&gt;
&lt;p&gt;The best choice can change as the application moves from an idea to a mature product.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Building an MVP?&lt;/strong&gt; A freelancer may be enough if the immediate goal is to test the concept without building a large development setup.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Developing the product continuously?&lt;/strong&gt; A dedicated React Native developer can provide the consistency needed for regular releases, improvements, and maintenance.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Launching a complete application without an internal technical team?&lt;/strong&gt; An agency may be more practical when development, design, testing, backend work, and project coordination all need to happen together.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Already have an internal development team?&lt;/strong&gt; Adding a dedicated React Native developer can fill a mobile-specific skills gap without changing the entire team structure.&lt;/p&gt;
&lt;p&gt;The hiring decision should follow the product rather than the other way around. Your scope, timeline, technical requirements, internal resources, and expected growth should determine the model you use.&lt;/p&gt;
&lt;h2 id=&quot;a-broader-look-at-hiring-mobile-developers&quot;&gt;A Broader Look at Hiring Mobile Developers&lt;/h2&gt;
&lt;p&gt;The same evaluation principle applies when comparing developers across different cross-platform frameworks. The framework itself is only part of the hiring decision; the developer's understanding of architecture, integrations, testing, deployment, and long-term maintenance also matters.&lt;/p&gt;
&lt;p&gt;For another perspective on evaluating mobile development talent before starting a project, this guide on &lt;a rel=&quot;nofollow&quot; href=&quot;https://dev.to/bharat_purohit_2077d198ac/what-to-know-before-you-hire-a-flutter-developer-for-your-next-mobile-app-3eao&quot;&gt;what to know before you hire a Flutter developer&lt;/a&gt; covers areas such as technical experience, project complexity, dedicated teams, maintenance, portfolios, and development costs.&lt;/p&gt;
&lt;p&gt;The specific framework may differ, but the underlying hiring question remains similar: does the developer or team have the experience required for the product you are actually trying to build?&lt;/p&gt;
&lt;h2 id=&quot;bottom-line-which-react-native-hiring-model-is-right-for-you-&quot;&gt;Bottom Line: Which React Native Hiring Model Is Right for You?&lt;/h2&gt;
&lt;p&gt;There is no universal winner among a freelancer, a dedicated React Native developer, and an agency. A freelancer can be the sensible choice for a focused task, a dedicated developer can provide the continuity an evolving product needs, and an agency can make sense when several specialists are required to deliver the application.&lt;/p&gt;
&lt;p&gt;The real mistake is choosing based only on the lowest initial price. A cheaper arrangement can become expensive if it creates delays, limits availability, or forces you to rebuild the development setup later.&lt;/p&gt;
&lt;p&gt;The better approach is to start with the product requirements, determine how much technical coverage you actually need, and then choose the hiring model that can support those requirements without creating unnecessary overhead. &lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/24605/react-native-developer-hiring-models-freelance-dedicated-or-agency</guid>
<pubDate>Fri, 14 Aug 2026 02:56:09 +0000</pubDate>
</item>
<item>
<title>How to Fix EAS Build Failed Errors in Expo</title>
<link>https://coderlegion.com/24440/how-to-fix-eas-build-failed-errors-in-expo</link>
<description>&lt;p&gt;There is a particular kind of Expo error that I think almost every developer eventually runs into.&lt;/p&gt;
&lt;p&gt;You make a change to your project, run your usual build command:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and instead of getting a nice &lt;code&gt;.apk&lt;/code&gt; or &lt;code&gt;.aab&lt;/code&gt; at the end, you get:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And that's it.&lt;/p&gt;
&lt;p&gt;Well, not really.&lt;/p&gt;
&lt;p&gt;If you open the build logs, you get several hundred lines of output containing Gradle tasks, Android dependencies, warnings, stack traces, environment information, and somewhere in the middle of all of that is the actual reason your build failed.&lt;/p&gt;
&lt;p&gt;I've been there more times than I'd like to admit.&lt;/p&gt;
&lt;p&gt;The frustrating part is that &lt;strong&gt;&quot;EAS Build failed&quot; isn't really an error by itself&lt;/strong&gt;. EAS is just telling you that something went wrong during the build. The actual problem can be a completely different issue.&lt;/p&gt;
&lt;p&gt;It might be an Android dependency conflict.&lt;/p&gt;
&lt;p&gt;It might be a missing environment variable.&lt;/p&gt;
&lt;p&gt;It might be an invalid &lt;code&gt;app.json&lt;/code&gt; configuration.&lt;/p&gt;
&lt;p&gt;It might be a native package that doesn't work with your current Expo SDK.&lt;/p&gt;
&lt;p&gt;Or it might be something as simple as a credentials problem.&lt;/p&gt;
&lt;p&gt;So instead of trying random fixes, let's go through how I approach these failures and how to find the actual cause.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;first-don-t-focus-on-eas-build-failed-&quot;&gt;First: Don't Focus on &quot;EAS Build Failed&quot;&lt;/h3&gt;
&lt;p&gt;When you see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;it's tempting to search for that exact phrase.&lt;/p&gt;
&lt;p&gt;The problem is that thousands of completely different problems can end with the same message.&lt;/p&gt;
&lt;p&gt;EAS Build is responsible for building your application. It isn't necessarily responsible for the thing that broke.&lt;/p&gt;
&lt;p&gt;For example, your log might eventually contain:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:mergeDexDebug'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's an Android dependency/build problem.&lt;/p&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Could not resolve all files for configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's pointing toward dependency resolution.&lt;/p&gt;
&lt;p&gt;Or you might see something related to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Missing environment variable
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's an environment/configuration problem.&lt;/p&gt;
&lt;p&gt;And if you see something related to signing credentials, you're dealing with an entirely different category of failure.&lt;/p&gt;
&lt;p&gt;The first thing I do is therefore &lt;strong&gt;ignore the generic &quot;Build failed&quot; message and find the first meaningful error underneath it.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-open-the-full-eas-build-logs&quot;&gt;1. Open the Full EAS Build Logs&lt;/h2&gt;
&lt;p&gt;Start by looking at the failed build in the EAS dashboard.&lt;/p&gt;
&lt;p&gt;You can also list recent builds from your terminal:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build:list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the failed build and open its logs.&lt;/p&gt;
&lt;p&gt;Don't just look at the last few lines.&lt;/p&gt;
&lt;p&gt;The last line might say:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but that doesn't tell you much.&lt;/p&gt;
&lt;p&gt;Instead, search through the logs for terms such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ERROR
FAILURE
Error:
Caused by:
Exception
Could not
Unable to
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful error is often somewhere above the final failure message.&lt;/p&gt;
&lt;p&gt;For example, imagine the bottom of your log looks like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;&amp;gt; Task :app:mergeDexRelease FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:mergeDexRelease'.

&amp;gt; com.android.builder.dexing.DexArchiveMergerException:
  Error while merging dex archives

BUILD FAILED

Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful information isn't really:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It's:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:mergeDexRelease'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and whatever caused the DEX merge to fail.&lt;/p&gt;
&lt;p&gt;That distinction saves a lot of time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-check-whether-the-problem-is-actually-your-expo-configuration&quot;&gt;2. Check Whether the Problem Is Actually Your Expo Configuration&lt;/h2&gt;
&lt;p&gt;One of the first things I check after an EAS build fails is the project's configuration.&lt;/p&gt;
&lt;p&gt;Depending on your setup, this can include:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;app.json
app.config.js
app.config.ts
eas.json
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A small configuration mistake can prevent the native project from being built correctly.&lt;/p&gt;
&lt;p&gt;For example, if you've recently changed:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;android&quot;: {
    &quot;package&quot;: &quot;com.example.myapp&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or added a plugin:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;plugins&quot;: [
    &quot;some-native-package&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and the build immediately started failing, that change is worth investigating.&lt;/p&gt;
&lt;p&gt;The same applies to permissions, native configuration, app identifiers, and Expo config plugins.&lt;/p&gt;
&lt;p&gt;If the failure started immediately after a configuration change, don't assume the EAS service itself is broken.&lt;/p&gt;
&lt;p&gt;Look at what changed in your project first.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-check-your-expo-sdk-and-native-dependencies&quot;&gt;3. Check Your Expo SDK and Native Dependencies&lt;/h2&gt;
&lt;p&gt;This is one of the biggest sources of trouble I've encountered with Expo projects.&lt;/p&gt;
&lt;p&gt;Expo SDK versions are tied to specific versions of React Native and a collection of native dependencies.&lt;/p&gt;
&lt;p&gt;You can have a perfectly valid JavaScript project and still break the native build by installing a package that doesn't fit your current environment.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install some-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;looks harmless.&lt;/p&gt;
&lt;p&gt;But if that package contains native Android or iOS code, it becomes part of your native build.&lt;/p&gt;
&lt;p&gt;That's where things can get interesting.&lt;/p&gt;
&lt;p&gt;Before randomly upgrading packages, check your project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This can identify common dependency and configuration problems.&lt;/p&gt;
&lt;p&gt;If you're using Expo, I also recommend using Expo's package installation command when appropriate:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install package-name
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;rather than blindly installing a version with npm.&lt;/p&gt;
&lt;p&gt;Expo can select a version that is compatible with your current SDK.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-if-the-error-mentions-gradle-investigate-the-android-build&quot;&gt;4. If the Error Mentions Gradle, Investigate the Android Build&lt;/h2&gt;
&lt;p&gt;Sometimes an EAS failure is actually an Android build failure hiding underneath Expo.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:mergeDexRelease'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Could not resolve all files for configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Duplicate class found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These are not really &quot;Expo errors.&quot;&lt;/p&gt;
&lt;p&gt;They're Android/Gradle dependency problems occurring during your Expo build.&lt;/p&gt;
&lt;p&gt;This distinction matters because searching for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EAS Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;will give you a huge number of unrelated results.&lt;/p&gt;
&lt;p&gt;Searching for:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task mergeDexRelease React Native
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is much more useful.&lt;/p&gt;
&lt;p&gt;That's generally how I approach these logs: &lt;strong&gt;follow the most specific error rather than the generic EAS failure.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-check-your-environment-variables&quot;&gt;5. Check Your Environment Variables&lt;/h2&gt;
&lt;p&gt;Another surprisingly common cause of failed builds is an environment variable that exists locally but doesn't exist in EAS.&lt;/p&gt;
&lt;p&gt;For example, your application might expect:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;API_URL
SUPABASE_URL
SUPABASE_ANON_KEY
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your local development environment works because those variables exist in your &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;Then you create an EAS build and suddenly something breaks.&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;p&gt;Because the remote build environment doesn't automatically have every variable from your local machine.&lt;/p&gt;
&lt;p&gt;Check your EAS environment configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas env:list
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure the variables your application actually needs are available in the environment being used for the build.&lt;/p&gt;
&lt;p&gt;This is especially important if the failure happens only on EAS while:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;works perfectly locally.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;6-check-your-eas-build-profile&quot;&gt;6. Check Your EAS Build Profile&lt;/h2&gt;
&lt;p&gt;Your &lt;code&gt;eas.json&lt;/code&gt; determines how your builds are configured.&lt;/p&gt;
&lt;p&gt;A typical configuration might look something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;build&quot;: {
    &quot;development&quot;: {
      &quot;developmentClient&quot;: true,
      &quot;distribution&quot;: &quot;internal&quot;
    },
    &quot;preview&quot;: {
      &quot;distribution&quot;: &quot;internal&quot;
    },
    &quot;production&quot;: {}
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the problem only happens with one profile, compare that profile with the one that works.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build --profile preview
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;might work while:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build --profile production
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;fails.&lt;/p&gt;
&lt;p&gt;That immediately gives you another clue.&lt;/p&gt;
&lt;p&gt;The problem may not be your entire project. It may be something specific to the build profile.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;7-don-t-ignore-credentials-errors&quot;&gt;7. Don't Ignore Credentials Errors&lt;/h2&gt;
&lt;p&gt;If the log mentions things such as:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;credentials
keystore
provisioning profile
certificate
signing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;don't start changing Gradle dependencies.&lt;/p&gt;
&lt;p&gt;You're probably dealing with app signing or build credentials instead.&lt;/p&gt;
&lt;p&gt;For Android, EAS can manage your app signing credentials for you.&lt;/p&gt;
&lt;p&gt;You can inspect the credentials with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas credentials
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If the error is specifically about signing, work through the credential configuration rather than treating it like a code or dependency problem.&lt;/p&gt;
&lt;p&gt;This is one of those situations where identifying the &lt;strong&gt;category of the error&lt;/strong&gt; is much more useful than knowing a particular command.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;8-if-it-worked-before-ask-what-changed&quot;&gt;8. If It Worked Before, Ask What Changed&lt;/h2&gt;
&lt;p&gt;This is probably my favorite debugging question:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;What changed immediately before the build started failing?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Don't underestimate how useful this is.&lt;/p&gt;
&lt;p&gt;If yesterday you had:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EAS build → SUCCESS
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and today you have:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;EAS build → FAILED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;something changed.&lt;/p&gt;
&lt;p&gt;Maybe you:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;installed a package&lt;/li&gt;
&lt;li&gt;upgraded Expo&lt;/li&gt;
&lt;li&gt;changed &lt;code&gt;app.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;changed &lt;code&gt;eas.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;modified environment variables&lt;/li&gt;
&lt;li&gt;changed an Android dependency&lt;/li&gt;
&lt;li&gt;added a config plugin&lt;/li&gt;
&lt;li&gt;changed native code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Start there.&lt;/p&gt;
&lt;p&gt;For example, if the failure appeared immediately after:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install some-package
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I would investigate that package before touching ten unrelated parts of the project.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;9-clean-up-before-rebuilding&quot;&gt;9. Clean Up Before Rebuilding&lt;/h2&gt;
&lt;p&gt;If you've already made changes trying to fix the problem, make sure you're not debugging an old local state.&lt;/p&gt;
&lt;p&gt;For a native project, a clean build can sometimes help:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
cd ..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Expo projects using prebuild, you may also need to regenerate native projects depending on your setup.&lt;/p&gt;
&lt;p&gt;But don't use cleaning as your first response to every error.&lt;/p&gt;
&lt;p&gt;If the build is failing because a dependency is genuinely incompatible, cleaning won't magically make the dependency compatible.&lt;/p&gt;
&lt;p&gt;It only removes cached/generated build artifacts.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-most-important-part-find-the-root-error&quot;&gt;The Most Important Part: Find the Root Error&lt;/h2&gt;
&lt;p&gt;This is the part that took me the longest to appreciate.&lt;/p&gt;
&lt;p&gt;When a build log contains hundreds of lines, the error at the bottom isn't necessarily the error you need to fix.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Task :app:assembleRelease FAILED
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;is not particularly useful.&lt;/p&gt;
&lt;p&gt;Neither is:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The useful part might be 30, 50, or even 200 lines above it.&lt;/p&gt;
&lt;p&gt;You might eventually find:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Duplicate class androidx.lifecycle.ViewModel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And suddenly the whole situation makes sense.&lt;/p&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Could not resolve com.google.firebase:...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you're dealing with a dependency resolution problem.&lt;/p&gt;
&lt;p&gt;Or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Missing environment variable: API_URL
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now you know exactly where to look.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The final error tells you that the build stopped. The underlying error tells you why.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-if-your-error-doesn-t-match-any-of-these-&quot;&gt;What If Your Error Doesn't Match Any Of These?&lt;/h2&gt;
&lt;p&gt;This is where things get much more frustrating.&lt;/p&gt;
&lt;p&gt;You can search the exact error message and find ten different Stack Overflow answers.&lt;/p&gt;
&lt;p&gt;One says to upgrade Gradle.&lt;/p&gt;
&lt;p&gt;Another says to delete &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Another says to run &lt;code&gt;expo prebuild --clean&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Someone else says to downgrade React Native.&lt;/p&gt;
&lt;p&gt;And suddenly you're changing five different things without knowing which one actually matters.&lt;/p&gt;
&lt;p&gt;I've made that mistake myself.&lt;/p&gt;
&lt;p&gt;The problem with large React Native and Expo build logs is that they often contain &lt;strong&gt;a lot of information but very little prioritization&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;You don't necessarily need another list of 15 possible fixes.&lt;/p&gt;
&lt;p&gt;You need to know:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Which part of this particular log is actually causing the failure?&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That's also the problem I wanted to solve with &lt;strong&gt;FixMyError&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Instead of manually digging through a huge React Native or Expo error log and trying to decide which line matters, you can paste the log into &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt; and get a focused diagnosis aimed at identifying the most likely cause and the first fix worth trying.&lt;/p&gt;
&lt;p&gt;It's particularly useful when your error doesn't fit neatly into one of the common categories above and you're staring at a wall of Gradle, Expo, or native build output wondering where to even start.&lt;/p&gt;
&lt;p&gt;You don't need to use it for every error. If the log clearly tells you what's wrong, fix it and move on.&lt;/p&gt;
&lt;p&gt;But when the useful information is buried somewhere inside a massive build log, that's exactly when a diagnostic tool becomes useful.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;a-simple-workflow-i-use-now&quot;&gt;A Simple Workflow I Use Now&lt;/h2&gt;
&lt;p&gt;When an Expo EAS build fails, I don't immediately start changing dependencies or upgrading packages.&lt;/p&gt;
&lt;p&gt;I go through this:&lt;/p&gt;
&lt;h4 id=&quot;1-find-the-actual-failure&quot;&gt;1. Find the actual failure&lt;/h4&gt;
&lt;p&gt;Don't stop at:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the underlying error.&lt;/p&gt;
&lt;h4 id=&quot;2-identify-the-category&quot;&gt;2. Identify the category&lt;/h4&gt;
&lt;p&gt;Is it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;configuration?&lt;/li&gt;
&lt;li&gt;dependency?&lt;/li&gt;
&lt;li&gt;Android/Gradle?&lt;/li&gt;
&lt;li&gt;iOS/Xcode?&lt;/li&gt;
&lt;li&gt;environment variables?&lt;/li&gt;
&lt;li&gt;credentials?&lt;/li&gt;
&lt;li&gt;native module compatibility?&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;3-ask-what-changed&quot;&gt;3. Ask what changed&lt;/h4&gt;
&lt;p&gt;What did you install, upgrade, or modify immediately before the failure?&lt;/p&gt;
&lt;h4 id=&quot;4-check-the-full-log&quot;&gt;4. Check the full log&lt;/h4&gt;
&lt;p&gt;Don't assume the last error line is the root cause.&lt;/p&gt;
&lt;h4 id=&quot;5-run-the-relevant-diagnostic-tools&quot;&gt;5. Run the relevant diagnostic tools&lt;/h4&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo-doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and inspect your EAS configuration and dependencies.&lt;/p&gt;
&lt;h4 id=&quot;6-change-one-thing-at-a-time&quot;&gt;6. Change one thing at a time&lt;/h4&gt;
&lt;p&gt;Don't simultaneously upgrade Expo, downgrade Gradle, delete &lt;code&gt;node_modules&lt;/code&gt;, regenerate native projects, and change your dependencies.&lt;/p&gt;
&lt;p&gt;If the build starts working afterward, you won't know what actually fixed it.&lt;/p&gt;
&lt;h4 id=&quot;7-if-the-log-is-still-unclear-diagnose-the-log-itself&quot;&gt;7. If the log is still unclear, diagnose the log itself&lt;/h4&gt;
&lt;p&gt;That's where a tool like FixMyError can save time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-4&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;EAS Build failed&lt;/code&gt; message can look like a single problem, but it really isn't.&lt;/p&gt;
&lt;p&gt;EAS is just the environment where the failure becomes visible.&lt;/p&gt;
&lt;p&gt;The actual cause might be hiding in:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Expo configuration&lt;/li&gt;
&lt;li&gt;native dependencies&lt;/li&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;Xcode&lt;/li&gt;
&lt;li&gt;environment variables&lt;/li&gt;
&lt;li&gt;credentials&lt;/li&gt;
&lt;li&gt;config plugins&lt;/li&gt;
&lt;li&gt;or a package that isn't compatible with your current SDK&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The biggest improvement you can make to your debugging process is to stop treating:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Build failed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;as the error.&lt;/p&gt;
&lt;p&gt;Treat it as the &lt;strong&gt;symptom&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Then work backward until you find the first specific failure that actually explains what happened.&lt;/p&gt;
&lt;p&gt;And if you're staring at a huge build log and can't tell which of the hundreds of lines actually matters, that's precisely the kind of situation where having something analyze the error for you can be useful.&lt;/p&gt;
&lt;p&gt;Happy debugging.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/24440/how-to-fix-eas-build-failed-errors-in-expo</guid>
<pubDate>Tue, 11 Aug 2026 11:46:41 +0000</pubDate>
</item>
<item>
<title>How to Fix `Execution failed for task ':app:mergeDexDebug'` in React Native</title>
<link>https://coderlegion.com/24096/how-to-fix-execution-failed-for-task-app-mergedexdebug-in-react-native</link>
<description>&lt;p&gt;You make a small change to your React Native project, install a new package, or upgrade a dependency. Everything looks fine until you try to build your Android app.&lt;/p&gt;
&lt;p&gt;Then Gradle stops with something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Execution failed for task ':app:mergeDexDebug'.

&amp;gt; com.android.builder.dexing.DexArchiveMergerException:
Error while merging dex archives
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or maybe you see:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Cannot fit requested classes in a single dex file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Duplicate class found
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, this error feels confusing because it happens during the Android build process, not inside your JavaScript or React Native code.&lt;/p&gt;
&lt;p&gt;Your components are fine. Your logic is fine.&lt;/p&gt;
&lt;p&gt;The problem is usually somewhere inside your Android dependencies.&lt;/p&gt;
&lt;p&gt;In this guide, we will go through what causes &lt;code&gt;mergeDexDebug&lt;/code&gt; failures, how to identify the real problem, and the fixes that actually work.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-does-mergedexdebug-actually-mean-&quot;&gt;What Does &lt;code&gt;mergeDexDebug&lt;/code&gt; Actually Mean?&lt;/h3&gt;
&lt;p&gt;When you build a React Native Android app, your JavaScript code is not the only thing being packaged.&lt;/p&gt;
&lt;p&gt;Your project also includes native Android dependencies from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Native modules&lt;/li&gt;
&lt;li&gt;Expo packages&lt;/li&gt;
&lt;li&gt;Firebase libraries&lt;/li&gt;
&lt;li&gt;AndroidX libraries&lt;/li&gt;
&lt;li&gt;Third-party native packages&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;During the Android build, Gradle converts these Java/Kotlin dependencies into DEX files that Android can run.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;mergeDexDebug&lt;/code&gt; task is the step where Android combines those generated DEX files together.&lt;/p&gt;
&lt;p&gt;When this step fails, it usually means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;there are too many methods to fit into one DEX file&lt;/li&gt;
&lt;li&gt;two dependencies contain the same classes&lt;/li&gt;
&lt;li&gt;dependency versions are conflicting&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cause-1-your-app-has-too-many-methods-64k-limit-&quot;&gt;Cause 1: Your App Has Too Many Methods (64K Limit)&lt;/h2&gt;
&lt;p&gt;One of the most common reasons for this error is Android's method limit.&lt;/p&gt;
&lt;p&gt;A single DEX file can only contain around 65,536 methods.&lt;/p&gt;
&lt;p&gt;If your project includes many libraries, you can eventually exceed this limit.&lt;/p&gt;
&lt;p&gt;The error usually looks like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Cannot fit requested classes in a single dex file
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;The number of method references in a .dex file cannot exceed 64K
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This happens frequently in projects using packages like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Firebase&lt;/li&gt;
&lt;li&gt;Google Play Services&lt;/li&gt;
&lt;li&gt;Maps&lt;/li&gt;
&lt;li&gt;Analytics libraries&lt;/li&gt;
&lt;li&gt;Multiple native modules&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;fix-enable-multidex&quot;&gt;Fix: Enable Multidex&lt;/h3&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;android/app/build.gradle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;defaultConfig {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Add:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;defaultConfig {
    multiDexEnabled true
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then add the multidex dependency:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;dependencies {
    implementation 'androidx.multidex:multidex:2.0.1'
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After that, clean your build:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
cd ..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then rebuild your app.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cause-2-duplicate-classes-between-dependencies&quot;&gt;Cause 2: Duplicate Classes Between Dependencies&lt;/h2&gt;
&lt;p&gt;Another common reason is dependency duplication.&lt;/p&gt;
&lt;p&gt;You may see something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Duplicate class androidx.lifecycle.ViewModel found in modules:

androidx.lifecycle:lifecycle-viewmodel:2.8.2

and

androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This means two libraries are trying to include different versions of the same Android classes.&lt;/p&gt;
&lt;p&gt;Gradle does not know which one should be used, so the build fails.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;fix-align-dependency-versions&quot;&gt;Fix: Align Dependency Versions&lt;/h3&gt;
&lt;p&gt;Check your Android dependencies.&lt;/p&gt;
&lt;p&gt;You can inspect your dependency tree with:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew app:dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for libraries that appear multiple times with different versions.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;androidx.lifecycle:lifecycle-viewmodel:2.8.2

androidx.lifecycle:lifecycle-viewmodel:2.6.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The solution is usually to make all related dependencies use the same version.&lt;/p&gt;
&lt;p&gt;After updating dependencies, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;and rebuild.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;cause-3-a-recently-installed-native-package-introduced-conflicts&quot;&gt;Cause 3: A Recently Installed Native Package Introduced Conflicts&lt;/h2&gt;
&lt;p&gt;Sometimes the problem appears right after installing a package.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install some-native-library
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Everything works until the next Android build.&lt;/p&gt;
&lt;p&gt;This happens because native packages are not only JavaScript code. They also add Android dependencies.&lt;/p&gt;
&lt;p&gt;A new package can introduce:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;conflicting AndroidX versions&lt;/li&gt;
&lt;li&gt;outdated libraries&lt;/li&gt;
&lt;li&gt;duplicate classes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the error appeared immediately after installing something new, that package is the first thing I would investigate.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-to-find-the-real-cause-faster&quot;&gt;How To Find The Real Cause Faster&lt;/h2&gt;
&lt;p&gt;Instead of guessing, get more information from Gradle.&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew app:assembleDebug --stacktrace
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The important part is usually not the final error line.&lt;/p&gt;
&lt;p&gt;The useful information is often several lines above it.&lt;/p&gt;
&lt;p&gt;Look for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;duplicate class names&lt;/li&gt;
&lt;li&gt;conflicting library versions&lt;/li&gt;
&lt;li&gt;dependency names&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The last line often only says:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:mergeDexDebug'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;but the actual reason appears earlier.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;clean-your-android-build-cache&quot;&gt;Clean Your Android Build Cache&lt;/h2&gt;
&lt;p&gt;After changing dependencies, Gradle may still use old build files.&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
cd ..
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For React Native CLI projects:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native run-android
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For Expo projects:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo run:android
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A surprising number of build problems disappear after a proper clean rebuild.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;preventing-future-mergedexdebug-errors&quot;&gt;Preventing Future &lt;code&gt;mergeDexDebug&lt;/code&gt; Errors&lt;/h2&gt;
&lt;p&gt;A few habits can save a lot of debugging time:&lt;/p&gt;
&lt;h3 id=&quot;keep-dependencies-updated&quot;&gt;Keep dependencies updated&lt;/h3&gt;
&lt;p&gt;Old native dependencies are a common source of Android build conflicts.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;avoid-installing-unnecessary-packages&quot;&gt;Avoid installing unnecessary packages&lt;/h3&gt;
&lt;p&gt;Every native package increases the complexity of your Android dependency tree.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;check-changes-after-adding-native-libraries&quot;&gt;Check changes after adding native libraries&lt;/h3&gt;
&lt;p&gt;If your build breaks immediately after installing a package, don't start changing random Gradle files.&lt;/p&gt;
&lt;p&gt;First check what dependency that package introduced.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;understand-the-error-before-applying-fixes&quot;&gt;Understand the error before applying fixes&lt;/h3&gt;
&lt;p&gt;A &lt;code&gt;mergeDexDebug&lt;/code&gt; failure is not one single problem.&lt;/p&gt;
&lt;p&gt;It is a symptom.&lt;/p&gt;
&lt;p&gt;The real cause could be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;too many methods&lt;/li&gt;
&lt;li&gt;duplicate classes&lt;/li&gt;
&lt;li&gt;incompatible versions&lt;/li&gt;
&lt;li&gt;a broken native dependency&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Finding the cause is usually faster than trying random fixes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;need-help-diagnosing-react-native-errors-&quot;&gt;Need Help Diagnosing React Native Errors?&lt;/h2&gt;
&lt;p&gt;Sometimes build errors are frustrating because the important clue is buried inside hundreds of lines of Gradle output.&lt;/p&gt;
&lt;p&gt;That is exactly why I built &lt;strong&gt;FixMyError&lt;/strong&gt; — a tool designed around the real debugging process developers go through.&lt;/p&gt;
&lt;p&gt;You can paste your React Native or Expo error log and get a focused diagnosis that explains what is likely causing the problem and what fix is worth trying first.&lt;/p&gt;
&lt;p&gt;Instead of searching through dozens of similar Stack Overflow answers, you can start with a clearer direction.&lt;/p&gt;
&lt;p&gt;You can check it out here:&lt;/p&gt;
&lt;p&gt;You can check it out here:&lt;/p&gt;
&lt;h3 id=&quot;fixmyerror&quot;&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;React Native Android build errors can look intimidating, but once you understand where the failure happens, most &lt;code&gt;mergeDexDebug&lt;/code&gt; problems become much easier to diagnose.&lt;/p&gt;
&lt;p&gt;The key is not memorizing commands.&lt;/p&gt;
&lt;p&gt;It is learning how to read what Gradle is actually telling you.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/24096/how-to-fix-execution-failed-for-task-app-mergedexdebug-in-react-native</guid>
<pubDate>Tue, 04 Aug 2026 12:27:52 +0000</pubDate>
</item>
<item>
<title>How to Fix &quot;Duplicate class ... found in modules&quot; in React Native &amp; Expo</title>
<link>https://coderlegion.com/23340/how-to-fix-duplicate-class-found-in-modules-in-react-native-expo</link>
<description>&lt;p&gt;If you've been developing with React Native or Expo for a while, chances are you've had a project that suddenly refuses to build for what seems like no reason.&lt;/p&gt;
&lt;p&gt;Maybe you installed a new package. Maybe you upgraded Expo. Maybe you only changed a single dependency. You confidently run your Android build, expecting it to compile like always...&lt;/p&gt;
&lt;p&gt;Instead, Gradle greets you with something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Execution failed for task ':app:checkDebugDuplicateClasses'.

Duplicate class androidx.lifecycle.ViewModelLazy found in modules lifecycle-viewmodel-2.8.2.aar and lifecycle-viewmodel-ktx-2.6.1.aar

Duplicate class com.google.android.gms.internal.measurement.zzab found in modules play-services-measurement-base-22.0.0.aar and play-services-measurement-impl-21.6.2.aar

Duplicate class ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, the error looks straightforward: &lt;em&gt;there are duplicate classes&lt;/em&gt;. Easy enough, right?&lt;/p&gt;
&lt;p&gt;Not exactly.&lt;/p&gt;
&lt;p&gt;In reality, this is one of those Android build errors that can send you down a rabbit hole of Gradle files, dependency trees and Stack Overflow threads, only to realize the real cause was something completely different.&lt;/p&gt;
&lt;p&gt;Let's break down what's actually happening and, more importantly, how to fix it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-this-error-happens&quot;&gt;Why this error happens&lt;/h2&gt;
&lt;p&gt;Every Android library included in your project contains compiled Java or Kotlin classes.&lt;/p&gt;
&lt;p&gt;When Gradle builds your application, it combines all of those libraries into a single APK or AAB.&lt;/p&gt;
&lt;p&gt;If two different dependencies contain the exact same class, Gradle doesn't know which version should be packaged.&lt;/p&gt;
&lt;p&gt;Instead of guessing, it stops the build with a &lt;strong&gt;Duplicate class&lt;/strong&gt; error.&lt;/p&gt;
&lt;p&gt;The difficult part is that the dependency causing the conflict usually isn't the one shown in the error.&lt;/p&gt;
&lt;p&gt;Very often, it's another package pulling in an older or incompatible version behind the scenes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-most-common-causes&quot;&gt;The most common causes&lt;/h2&gt;
&lt;p&gt;After seeing this error many times, these are usually the culprits.&lt;/p&gt;
&lt;h3 id=&quot;1-two-libraries-depend-on-different-versions-of-the-same-package&quot;&gt;1. Two libraries depend on different versions of the same package&lt;/h3&gt;
&lt;p&gt;This is by far the most common scenario.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Library A requires:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;androidx.lifecycle:lifecycle-viewmodel:2.8.2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;while Library B depends on:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;androidx.lifecycle:lifecycle-viewmodel:2.6.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Both versions get added to your dependency graph, eventually leading to duplicate classes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-an-outdated-react-native-package&quot;&gt;2. An outdated React Native package&lt;/h3&gt;
&lt;p&gt;Older React Native libraries often haven't been updated for newer AndroidX or Google Play Services versions.&lt;/p&gt;
&lt;p&gt;Even though your project is current, that one dependency may still reference libraries released years ago.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-expo-sdk-upgrades&quot;&gt;3. Expo SDK upgrades&lt;/h3&gt;
&lt;p&gt;After upgrading Expo, many native dependency versions change.&lt;/p&gt;
&lt;p&gt;If one third-party package hasn't caught up yet, Gradle may end up resolving incompatible versions.&lt;/p&gt;
&lt;p&gt;This is especially common immediately after major SDK releases.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-mixing-manual-native-configuration-with-expo-managed-dependencies&quot;&gt;4. Mixing manual native configuration with Expo-managed dependencies&lt;/h3&gt;
&lt;p&gt;Sometimes developers manually add Android dependencies that Expo already manages internally.&lt;/p&gt;
&lt;p&gt;The result is two copies of essentially the same library.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-to-fix-it&quot;&gt;How to fix it&lt;/h2&gt;
&lt;p&gt;There isn't one universal solution, but these steps solve the majority of cases.&lt;/p&gt;
&lt;h3 id=&quot;step-1-read-the-duplicated-class-carefully&quot;&gt;Step 1 — Read the duplicated class carefully&lt;/h3&gt;
&lt;p&gt;Don't focus on the entire error.&lt;/p&gt;
&lt;p&gt;Look specifically for the two modules involved.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;lifecycle-viewmodel-2.8.2

lifecycle-viewmodel-ktx-2.6.1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Those names usually point you toward the conflicting dependency.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-find-who-is-importing-it&quot;&gt;Step 2 — Find who is importing it&lt;/h3&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./gradlew app:dependencies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;or&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;./gradlew app:dependencyInsight --dependency lifecycle-viewmodel
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This shows which package is pulling each version into your project.&lt;/p&gt;
&lt;p&gt;It's much faster than guessing.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-update-outdated-packages&quot;&gt;Step 3 — Update outdated packages&lt;/h3&gt;
&lt;p&gt;If one library is significantly behind, updating it often resolves the conflict immediately.&lt;/p&gt;
&lt;p&gt;Always check whether a newer version exists before trying complicated Gradle workarounds.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-clean-the-project&quot;&gt;Step 4 — Clean the project&lt;/h3&gt;
&lt;p&gt;Sometimes Gradle continues using cached artifacts.&lt;/p&gt;
&lt;p&gt;Try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android

./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then rebuild.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-5-regenerate-native-files-expo-&quot;&gt;Step 5 — Regenerate native files (Expo)&lt;/h3&gt;
&lt;p&gt;If you're using Expo Prebuild:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo prebuild --clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This recreates the native Android project using your current configuration.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-6-force-dependency-versions-only-if-necessary-&quot;&gt;Step 6 — Force dependency versions (only if necessary)&lt;/h3&gt;
&lt;p&gt;Sometimes you need to force Gradle to use one specific version.&lt;/p&gt;
&lt;p&gt;For example:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-gradle&quot;&gt;configurations.all {
    resolutionStrategy {
        force &quot;androidx.lifecycle:lifecycle-viewmodel:2.8.2&quot;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Use this carefully.&lt;/p&gt;
&lt;p&gt;If two libraries genuinely require incompatible versions, forcing one version may create runtime problems later.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-to-avoid-this-error-in-the-future&quot;&gt;How to avoid this error in the future&lt;/h2&gt;
&lt;p&gt;While duplicate class errors can't always be prevented, these habits reduce the chances considerably.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep Expo SDKs reasonably up to date.&lt;/li&gt;
&lt;li&gt;Avoid abandoned React Native libraries.&lt;/li&gt;
&lt;li&gt;Update dependencies together instead of one at a time.&lt;/li&gt;
&lt;li&gt;Review package compatibility before major upgrades.&lt;/li&gt;
&lt;li&gt;Avoid manually adding Android dependencies unless absolutely necessary.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id=&quot;when-the-error-isn-t-actually-the-problem&quot;&gt;When the error isn't actually the problem&lt;/h2&gt;
&lt;p&gt;One thing that makes this error frustrating is that the duplicate classes are often just a symptom.&lt;/p&gt;
&lt;p&gt;The real issue might be:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;an outdated library,&lt;/li&gt;
&lt;li&gt;conflicting Firebase dependencies,&lt;/li&gt;
&lt;li&gt;mixed AndroidX versions,&lt;/li&gt;
&lt;li&gt;incompatible Google Play Services packages,&lt;/li&gt;
&lt;li&gt;or an incomplete Expo migration.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That's why simply searching the duplicated class name doesn't always lead to the correct fix.&lt;/p&gt;
&lt;p&gt;Understanding &lt;strong&gt;why&lt;/strong&gt; those dependencies ended up together is usually more important than the class itself.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-fixmyerror-can-help&quot;&gt;How FixMyError can help&lt;/h2&gt;
&lt;p&gt;Instead of manually digging through Gradle output, dependency trees and dozens of forum posts, you can paste the full error into &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The tool analyzes the build log, identifies the likely source of the conflict, explains what's happening in plain English, and suggests fixes based on the specific dependencies involved.&lt;/p&gt;
&lt;p&gt;Rather than treating every duplicate class error as identical, it tries to narrow the problem down to the actual cause behind your particular build failure.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-5&quot;&gt;Final thoughts&lt;/h2&gt;
&lt;p&gt;Duplicate class errors are among the most frustrating Android build issues because the message rarely points directly to the package that's responsible.&lt;/p&gt;
&lt;p&gt;The good news is that, in most cases, the problem comes down to dependency resolution rather than something fundamentally broken in your project.&lt;/p&gt;
&lt;p&gt;Once you identify which libraries are introducing conflicting versions, the fix usually becomes much more straightforward.&lt;/p&gt;
&lt;p&gt;If you're currently staring at a wall of Gradle output wondering where to begin, start by identifying the conflicting modules, trace where they're coming from, and work outward from there.&lt;/p&gt;
&lt;p&gt;It takes a bit of patience, but it's almost always easier than blindly trying random fixes from different Stack Overflow threads.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/23340/how-to-fix-duplicate-class-found-in-modules-in-react-native-expo</guid>
<pubDate>Thu, 23 Jul 2026 12:36:11 +0000</pubDate>
</item>
<item>
<title>How to fix `global._getAnimationTimestamp is not a function` After Upgrading to Expo SDK 55</title>
<link>https://coderlegion.com/22798/how-to-fix-global-getanimationtimestamp-is-not-a-function-after-upgrading-to-expo-sdk-55</link>
<description>&lt;p&gt;I recently spent far more time than I'd like to admit chasing down a hard crash after upgrading a project from Expo SDK 54 to 55.&lt;/p&gt;
&lt;p&gt;The app launched fine. Navigation worked. Everything looked normal.&lt;/p&gt;
&lt;p&gt;Then the moment a Reanimated animation fired, the app exploded with a red screen:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Render Error

global._getAnimationTimestamp is not a function (it is undefined)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In my case, the crash happened inside a &lt;code&gt;useEffect&lt;/code&gt; that called &lt;code&gt;withTiming()&lt;/code&gt; on a shared value when a tab gained focus.&lt;/p&gt;
&lt;p&gt;My first assumption was a dependency mismatch between &lt;code&gt;react-native-reanimated&lt;/code&gt; and &lt;code&gt;react-native-worklets&lt;/code&gt;. I checked versions, verified the dependency tree, and even started looking for Babel configuration issues.&lt;/p&gt;
&lt;p&gt;Everything looked correct.&lt;/p&gt;
&lt;p&gt;As it turns out, the problem wasn't in the JavaScript code at all.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-is-actually-happening-&quot;&gt;What Is Actually Happening?&lt;/h3&gt;
&lt;p&gt;At first glance, this error looks like a breaking change inside Reanimated or a bad project configuration.&lt;/p&gt;
&lt;p&gt;In reality, it's usually a sync problem between your JavaScript bundle and your native application binary.&lt;/p&gt;
&lt;p&gt;The important clue is the function name itself:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;_getAnimationTimestamp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This isn't a function your application calls directly. It's one of the internal functions Reanimated expects to find in the native runtime.&lt;/p&gt;
&lt;p&gt;Reanimated relies heavily on JSI (JavaScript Interface) to bridge JavaScript directly to native C++ code for performance-critical animation work.&lt;/p&gt;
&lt;p&gt;When you upgrade to Expo SDK 55, Reanimated gets upgraded as well (typically from 4.1.x to 4.2.x, depending on your project).&lt;/p&gt;
&lt;p&gt;Your Metro server immediately starts serving the updated JavaScript bundle.&lt;/p&gt;
&lt;p&gt;The problem is that your custom development client is still running the native code that was compiled before the upgrade.&lt;/p&gt;
&lt;p&gt;Now you have two different versions talking to each other:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The JavaScript side expects the newer Reanimated runtime.&lt;/li&gt;
&lt;li&gt;The native side is still exposing the older implementation.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The updated JavaScript bundle expects &lt;code&gt;_getAnimationTimestamp&lt;/code&gt; to exist.&lt;/p&gt;
&lt;p&gt;Your older native client doesn't provide that function yet.&lt;/p&gt;
&lt;p&gt;The moment Reanimated tries to use it, the app crashes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-reinstalling-dependencies-doesn-t-help&quot;&gt;Why Reinstalling Dependencies Doesn't Help&lt;/h3&gt;
&lt;p&gt;I went through the usual checklist first:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reinstall dependencies&lt;/li&gt;
&lt;li&gt;Verify package versions&lt;/li&gt;
&lt;li&gt;Check Babel configuration&lt;/li&gt;
&lt;li&gt;Clear &lt;code&gt;node_modules&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;npm install&lt;/code&gt; again&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of it fixed the issue.&lt;/p&gt;
&lt;p&gt;That's because the problem isn't living in your JavaScript dependencies.&lt;/p&gt;
&lt;p&gt;Your packages can be perfectly correct while your device or simulator is still running an outdated native binary.&lt;/p&gt;
&lt;p&gt;As long as the native client wasn't rebuilt after the upgrade, the crash will continue to happen.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;when-you-ll-usually-see-this-error&quot;&gt;When You'll Usually See This Error&lt;/h3&gt;
&lt;p&gt;One thing worth mentioning is that this mostly shows up when you're using a custom development client.&lt;/p&gt;
&lt;p&gt;If you're using Expo Go, Expo manages the native runtime for you, so JavaScript and native code generally stay in sync.&lt;/p&gt;
&lt;p&gt;This issue is much more common when you're working with:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom dev-clients&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expo run:android&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;expo run:ios&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Development builds generated through EAS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In those environments, it's entirely possible for Metro to be serving new JavaScript while your device is still running an older native build.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix&quot;&gt;The Fix&lt;/h3&gt;
&lt;p&gt;A normal JavaScript reload won't solve this.&lt;/p&gt;
&lt;p&gt;Pressing &lt;code&gt;r&lt;/code&gt; in the Metro terminal only refreshes the JavaScript bundle. It does not rebuild the native application.&lt;/p&gt;
&lt;p&gt;To fix the issue, you need both sides of the application running the same version.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-1-clear-metro-s-cache&quot;&gt;Step 1: Clear Metro's Cache&lt;/h4&gt;
&lt;p&gt;Stop Metro completely and restart it with a clean cache:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This removes the possibility of Metro serving stale transformed files.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-rebuild-the-native-client&quot;&gt;Step 2: Rebuild the Native Client&lt;/h4&gt;
&lt;p&gt;If you're building locally, generate a fresh native application:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo run:ios

# or

npx expo run:android
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This recompiles the native code using the versions currently installed in your project.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-rebuild-your-development-build-eas-&quot;&gt;Step 3: Rebuild Your Development Build (EAS)&lt;/h4&gt;
&lt;p&gt;If you're using EAS development builds, you'll need to generate a completely new build and install it on your device.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;eas build --profile development
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the build finishes, install the new IPA or APK and launch the updated client.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-fix-works&quot;&gt;Why This Fix Works&lt;/h3&gt;
&lt;p&gt;After rebuilding, both sides of the application are finally using the same version of Reanimated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Metro serves the updated JavaScript bundle.&lt;/li&gt;
&lt;li&gt;The native binary contains the matching JSI bindings.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once those versions line up again, Reanimated can find the functions it expects and the crash disappears.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;a-quick-note-on-debugging&quot;&gt;A Quick Note on Debugging&lt;/h3&gt;
&lt;p&gt;This was one of those bugs that initially looked like a dependency issue, then a Babel issue, then a Reanimated issue.&lt;/p&gt;
&lt;p&gt;In reality, it was just an out-of-date native build.&lt;/p&gt;
&lt;p&gt;Problems like this are one of the more frustrating parts of React Native because the error message rarely points to the actual cause. It's easy to spend hours looking at package versions when the real fix is simply rebuilding the native client.&lt;/p&gt;
&lt;p&gt;After spending enough evenings digging through stack traces, GitHub issues, and old forum threads, I ended up building a small tool called &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;Fix My Error&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;You paste in the error output, and it helps narrow down likely causes and fixes without having to bounce between ten different tabs looking for someone who hit the same problem three years ago.&lt;/p&gt;
&lt;p&gt;For this particular error, though, the fix turned out to be much simpler than I expected: clear Metro, rebuild the native client, and make sure your JavaScript bundle and native runtime are actually running the same version.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/22798/how-to-fix-global-getanimationtimestamp-is-not-a-function-after-upgrading-to-expo-sdk-55</guid>
<pubDate>Thu, 16 Jul 2026 09:53:20 +0000</pubDate>
</item>
<item>
<title>How to Fix: &quot;Namespace not specified&quot; Gradle 8 Error in React Native &amp; Expo</title>
<link>https://coderlegion.com/22158/how-to-fix-namespace-not-specified-gradle-8-error-in-react-native-expo</link>
<description>&lt;h2 id=&quot;fixing-the-namespace-not-specified-error-in-react-native-and-expo-android-builds&quot;&gt;Fixing the &quot;Namespace Not Specified&quot; Error in React Native and Expo Android Builds&lt;/h2&gt;
&lt;p&gt;If you recently upgraded a project to React Native 0.73+ or bumped your Expo SDK to version 50+, you may have run into an Android build error that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;FAILURE: Build failed with an exception.

* What went wrong:
A problem occurred configuring project ':react-native-some-old-library'.

&amp;gt; Namespace not specified for group 'react-native-some-old-library'
  and version '1.0.0' as per the Android Gradle Plugin 8.0+ guidelines.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At first glance, it looks like something is wrong with your app. In most cases, it isn't.&lt;/p&gt;
&lt;p&gt;The error is usually coming from an older dependency inside &lt;code&gt;node_modules&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Here's what's actually causing it and how to fix it properly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-root-cause&quot;&gt;The Root Cause&lt;/h3&gt;
&lt;p&gt;Before Android Gradle Plugin (AGP) 8.0, Android libraries could define their package name inside &lt;code&gt;AndroidManifest.xml&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;manifest xmlns:android=&quot;http://schemas.android.com/apk/res/android&quot;
    package=&quot;com.someoldlibrary&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Starting with AGP 8.0, that is no longer enough.&lt;/p&gt;
&lt;p&gt;Every Android module is now expected to define a &lt;code&gt;namespace&lt;/code&gt; inside its &lt;code&gt;build.gradle&lt;/code&gt; file.&lt;/p&gt;
&lt;p&gt;When you upgrade React Native or Expo, your Android tooling gets upgraded as well. The problem is that many older React Native libraries haven't been updated to follow the new requirement.&lt;/p&gt;
&lt;p&gt;As soon as Gradle encounters one of those libraries, it stops during configuration and the build fails.&lt;/p&gt;
&lt;p&gt;This started showing up much more frequently after Expo SDK 50 and newer React Native releases adopted newer Android tooling.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;before-you-change-anything&quot;&gt;Before You Change Anything&lt;/h3&gt;
&lt;p&gt;Look closely at the error message.&lt;/p&gt;
&lt;p&gt;Most of the time you'll see something like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A problem occurred configuring project ':react-native-some-old-library'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That project name is usually the library causing the issue.&lt;/p&gt;
&lt;p&gt;A common mistake is to start editing your app's own Gradle files. In most cases, the problem is actually inside the dependency listed in the error.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix-use-patch-package&quot;&gt;The Fix: Use patch-package&lt;/h3&gt;
&lt;p&gt;Since the problem comes from a third-party library, editing files directly inside &lt;code&gt;node_modules&lt;/code&gt; isn't a real fix.&lt;/p&gt;
&lt;p&gt;The next time you run &lt;code&gt;npm install&lt;/code&gt;, your changes will be overwritten.&lt;/p&gt;
&lt;p&gt;Instead, make the change once and save it using &lt;code&gt;patch-package&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id=&quot;step-1-find-the-package-name&quot;&gt;Step 1: Find the Package Name&lt;/h4&gt;
&lt;p&gt;Navigate to the library's manifest file:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules/react-native-some-old-library/android/src/main/AndroidManifest.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Look for the package attribute:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-xml&quot;&gt;&amp;lt;manifest
    package=&quot;com.someoldlibrary&quot;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Copy that value.&lt;/p&gt;
&lt;p&gt;You'll use it as the namespace in the next step.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-2-add-the-missing-namespace&quot;&gt;Step 2: Add the Missing Namespace&lt;/h4&gt;
&lt;p&gt;Open:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;node_modules/react-native-some-old-library/android/build.gradle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Find the &lt;code&gt;android {}&lt;/code&gt; block and add the namespace at the top:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-groovy&quot;&gt;android {
    namespace &quot;com.someoldlibrary&quot;

    compileSdkVersion safeExtGet('compileSdkVersion', 34)

    defaultConfig {
        minSdkVersion safeExtGet('minSdkVersion', 21)
        targetSdkVersion safeExtGet('targetSdkVersion', 34)
    }

    // ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure the namespace exactly matches the package name from the manifest.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-3-generate-a-patch&quot;&gt;Step 3: Generate a Patch&lt;/h4&gt;
&lt;p&gt;From your project root, run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx patch-package react-native-some-old-library
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This creates a file inside a new &lt;code&gt;patches&lt;/code&gt; directory containing the modification you just made.&lt;/p&gt;
&lt;p&gt;Commit that file to Git.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;step-4-apply-the-patch-automatically&quot;&gt;Step 4: Apply the Patch Automatically&lt;/h4&gt;
&lt;p&gt;Open &lt;code&gt;package.json&lt;/code&gt; and add a &lt;code&gt;postinstall&lt;/code&gt; script:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;{
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;expo start&quot;,
    &quot;android&quot;: &quot;expo run:android&quot;,
    &quot;ios&quot;: &quot;expo run:ios&quot;,
    &quot;postinstall&quot;: &quot;patch-package&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now whenever someone runs &lt;code&gt;npm install&lt;/code&gt;—whether it's you, another developer, CI, or EAS Build—the patch gets applied automatically.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;that-s-it&quot;&gt;That's It&lt;/h3&gt;
&lt;p&gt;Clear any caches if needed and run your Android build again.&lt;/p&gt;
&lt;p&gt;The build should now complete normally.&lt;/p&gt;
&lt;p&gt;More importantly, you won't have to keep making the same edit every time &lt;code&gt;node_modules&lt;/code&gt; gets reinstalled.&lt;/p&gt;
&lt;p&gt;If you're working with older React Native dependencies, this is usually the cleanest way to handle AGP 8 namespace issues without waiting for the library maintainer to release an update.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/22158/how-to-fix-namespace-not-specified-gradle-8-error-in-react-native-expo</guid>
<pubDate>Thu, 09 Jul 2026 12:45:41 +0000</pubDate>
</item>
<item>
<title>How to Fix Apple Silicon CocoaPods Errors Without Messy Terminal Aliases</title>
<link>https://coderlegion.com/22001/how-to-fix-apple-silicon-cocoapods-errors-without-messy-terminal-aliases</link>
<description>&lt;h3 id=&quot;the-crash&quot;&gt;The Crash&lt;/h3&gt;
&lt;p&gt;You pull down a fresh React Native repo, or you upgrade your Expo project, navigate to your ios directory, and run your standard installation command:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pod install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Instead of a clean build, your terminal drops a wall of text pointing to a compilation or architecture conflict:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;LoadError - dylib library not found for ffi_c - /Library/Ruby/Gems/2.6.0/gems/ffi-1.15.5/lib/ffi_c.bundle
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or you are hit with an explicit architecture clash during compilation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Ignoring ffi-1.15.5 because its extensions are not built.
Arch mismatch: x86_64 vs arm64
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-the-common-internet-advice-is-a-trap&quot;&gt;Why the Common Internet Advice is a Trap&lt;/h3&gt;
&lt;p&gt;If you search for this error on Stack Overflow or Reddit, the top answers almost always tell you to run your terminal in Rosetta mode or force compilation with an architecture flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo arch -x86_64 gem install ffi
arch -x86_64 pod install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Do not do this.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;While this might bypass the error temporarily, it introduces a highly unstable configuration to your machine. It forces an Apple Silicon Mac to use Intel x86 emulation for specific Ruby gems while running an underlying system interpreter that expects native ARM (arm64) code.&lt;/p&gt;
&lt;p&gt;This mixed-architecture setup will inevitably break your bundler, conflict with Node native modules, and cause random, hard-to-diagnose failures during local builds.&lt;/p&gt;
&lt;p&gt;The real problem isn't your hardware; it's that your environment is attempting to load global, system-level Intel configurations inside a modern ARM environment.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-fix-resolving-the-conflict-natively&quot;&gt;The Fix: Resolving the Conflict Natively&lt;/h3&gt;
&lt;p&gt;To resolve the issue permanently, you need to purge the conflicting configurations and ensure your dependency manager runs natively on Apple Silicon.&lt;/p&gt;
&lt;h4 id=&quot;step-1-purge-the-broken-system-gems&quot;&gt;Step 1: Purge the Broken System Gems&lt;/h4&gt;
&lt;p&gt;First, clear out any global, system-level gems that were compiled incorrectly under mixed architectures.&lt;/p&gt;
&lt;p&gt;Run the following command from your root directory to remove CocoaPods configurations managed by the system Ruby installation:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo gem uninstall cocoapods
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-2-install-the-native-arm-version-of-cocoapods-via-homebrew&quot;&gt;Step 2: Install the Native ARM Version of CocoaPods via Homebrew&lt;/h4&gt;
&lt;p&gt;Instead of relying on the default macOS system Ruby interpreter to manage your gems, install CocoaPods through Homebrew.&lt;/p&gt;
&lt;p&gt;Homebrew automatically detects your M-series architecture and fetches the dedicated, optimized native arm64 binary.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install cocoapods
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-3-isolate-your-ruby-environment-optional-but-highly-recommended-&quot;&gt;Step 3: Isolate Your Ruby Environment (Optional but Highly Recommended)&lt;/h4&gt;
&lt;p&gt;Using the default pre-installed macOS system Ruby (/usr/bin/ruby) frequently causes permissions and architecture locks.&lt;/p&gt;
&lt;p&gt;For a stable environment, switch to a local Ruby version manager like rbenv or chruby to handle your project dependencies cleanly.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Install rbenv via Homebrew
brew install rbenv ruby-build

# Initialize rbenv in your shell configuration
rbenv init

# Install and set a modern native Ruby version
rbenv install 3.2.2
rbenv global 3.2.2
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 id=&quot;step-4-clear-the-local-state-and-install-natively&quot;&gt;Step 4: Clear the Local State and Install Natively&lt;/h4&gt;
&lt;p&gt;Now that your environment relies on native binaries, clear your project's cached build configurations to ensure no stale artifacts remain:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd ios
pod deintegrate
rm -rf Podfile.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Finally, run your installation command normally.&lt;/p&gt;
&lt;p&gt;It will complete natively without requiring any arch -x86_64 prefixes or Rosetta terminal wrappers:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pod install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Wrestling with local environment drift, corrupted build paths, and conflicting architectures can pull you out of the development loop for hours.&lt;/p&gt;
&lt;p&gt;If you are tired of decoding cryptic terminal logs and tracking down configuration bugs by hand, take a look at &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;FixMyError&lt;/a&gt;. It is a clean diagnostic workspace built to parse your build outputs instantly, giving you clear solutions so you can get right back to building your app.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/22001/how-to-fix-apple-silicon-cocoapods-errors-without-messy-terminal-aliases</guid>
<pubDate>Mon, 06 Jul 2026 12:55:19 +0000</pubDate>
</item>
<item>
<title>How to Fix Expo's Cryptic Upgrade Trap: Android Resource Linking Failed (splashscreen_logo not found</title>
<link>https://coderlegion.com/21025/how-expos-cryptic-upgrade-trap-android-resource-linking-failed-splashscreenlogo-found</link>
<description>&lt;h2 id=&quot;how-to-fix-resource-drawable-splashscreen-logo-not-found-after-an-expo-sdk-upgrade&quot;&gt;How to Fix &quot;resource drawable/splashscreen_logo not found&quot; After an Expo SDK Upgrade&lt;/h2&gt;
&lt;p&gt;Upgrading your Expo SDK version to keep up with the latest App Store and Google Play requirements is usually a smooth process—until Gradle decides to punch you in the face with a cryptic asset compilation error.&lt;/p&gt;
&lt;p&gt;If you just kicked off an Android build or ran a prebuild, and your terminal suddenly spit out a wall of red text looking exactly like this, you are not alone:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;A failure occurred while executing com.android.build.gradle.internal.res.LinkApplicationAndroidResourcesTask$TaskAction
&amp;gt; Android resource linking failed
error: resource drawable/splashscreen_logo (aka com.yourcompany.app:drawable/splashscreen_logo) not found.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The worst part about this error is that it doesn't give you a file name, a line number, or any mention of Expo.&lt;/p&gt;
&lt;p&gt;It just drops a native Android resource compilation failure and leaves you to figure out the rest.&lt;/p&gt;
&lt;p&gt;Let's break down exactly why this happens during Expo upgrades and how to fix it in two minutes flat.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-this-happens-the-missing-asset-trap&quot;&gt;Why This Happens: The Missing Asset Trap&lt;/h2&gt;
&lt;p&gt;This issue is a classic side effect of how newer Expo SDK versions handle Continuous Native Generation (CNG) via the &lt;code&gt;expo-splash-screen&lt;/code&gt; plugin.&lt;/p&gt;
&lt;p&gt;In older versions of Expo, if you only wanted a solid background color for your splash screen, you could get away with just defining &lt;code&gt;backgroundColor&lt;/code&gt; in your config and skipping the logo image entirely.&lt;/p&gt;
&lt;p&gt;The native generator would handle it gracefully.&lt;/p&gt;
&lt;p&gt;However, in newer Expo SDK configurations, the prebuild engine has become much more strict.&lt;/p&gt;
&lt;p&gt;If the &lt;code&gt;image&lt;/code&gt; property is missing from your splash screen configuration, the Expo generator completely skips creating the native Android XML drawable references for the logo.&lt;/p&gt;
&lt;p&gt;When the Android Gradle plugin compiles the app layer, it looks into the generated styles for the splash screen layout, tries to link the &lt;code&gt;splashscreen_logo&lt;/code&gt; asset, finds a blank void, and crashes the entire build.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;how-to-fix-it-1&quot;&gt;How to Fix It&lt;/h2&gt;
&lt;p&gt;To resolve this, we need to explicitly force the Expo prebuild engine to map a valid drawable resource so Gradle stops complaining.&lt;/p&gt;
&lt;h3 id=&quot;step-1-update-your-app-configuration&quot;&gt;Step 1: Update Your App Configuration&lt;/h3&gt;
&lt;p&gt;Open your &lt;code&gt;app.json&lt;/code&gt; (or &lt;code&gt;app.config.js&lt;/code&gt;) file and locate your &lt;code&gt;plugins&lt;/code&gt; array.&lt;/p&gt;
&lt;p&gt;Make sure the &lt;code&gt;expo-splash-screen&lt;/code&gt; plugin block contains both a background color and an explicit path to a fallback image asset.&lt;/p&gt;
&lt;p&gt;Even if you don't actually want a logo on your splash screen, you need to provide a placeholder image (like a tiny 1x1 transparent or solid pixel matching your background color) to satisfy the native compiler.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-json&quot;&gt;&quot;plugins&quot;: [
  [
    &quot;expo-splash-screen&quot;,
    {
      &quot;backgroundColor&quot;: &quot;#ffffff&quot;,
      &quot;image&quot;: &quot;./assets/splash-icon.png&quot;
    }
  ]
]
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-clear-and-regenerate-native-directories&quot;&gt;Step 2: Clear and Regenerate Native Directories&lt;/h3&gt;
&lt;p&gt;Because native Android build directories heavily cache old configurations, simply changing &lt;code&gt;app.json&lt;/code&gt; isn't always enough to clear out the corrupted Gradle state.&lt;/p&gt;
&lt;p&gt;Run the following commands in your terminal to wipe the slate clean and force a fresh native generation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# If you are testing local native builds:
npx expo prebuild --clean

# Or if you are running a fresh release compilation:
npx expo run:android --variant release --no-build-cache
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the prebuild engine re-runs with the new configuration, it will successfully generate the &lt;code&gt;splashscreen_logo.xml&lt;/code&gt; drawable file inside your native directories, and Gradle will breeze right past the linking stage.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;interactive-reference-amp-live-fix&quot;&gt;Interactive Reference &amp;amp; Live Fix&lt;/h2&gt;
&lt;p&gt;If you want to view a fully verified configuration block or run this code through an interactive debugger to make sure your syntax matches up perfectly, you can check out the public resolution ledger here:&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.fixmyerrorapp.com&quot;&gt;Fix My Error&lt;/a&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-6&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Native Android build failures often look terrifying because the error message points to generated resources rather than the actual configuration mistake.&lt;/p&gt;
&lt;p&gt;In this case, the root cause is usually a missing splash screen image definition after upgrading to a newer Expo SDK version.&lt;/p&gt;
&lt;p&gt;Once you add a valid image path and regenerate your native directories, the build should proceed normally.&lt;/p&gt;
&lt;p&gt;Have you run into any other weird compilation errors while moving your project up to the latest Expo SDK?&lt;/p&gt;
&lt;p&gt;Drop them in the comments below and let's unblock them.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/21025/how-expos-cryptic-upgrade-trap-android-resource-linking-failed-splashscreenlogo-found</guid>
<pubDate>Sat, 20 Jun 2026 12:10:50 +0000</pubDate>
</item>
<item>
<title>How to Fix &quot;Module Could Not Be Found&quot; in React Native &amp; Expo</title>
<link>https://coderlegion.com/20703/how-to-fix-module-could-not-be-found-in-react-native-expo</link>
<description>&lt;p&gt;We’ve all been there.&lt;/p&gt;
&lt;p&gt;You find an awesome library, you run &lt;code&gt;npx expo install&lt;/code&gt;, you import it into your code, and you start your development server.&lt;/p&gt;
&lt;p&gt;You expect magic.&lt;/p&gt;
&lt;p&gt;Instead, your simulator turns blindingly red with an error that looks something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ERROR Invariant Violation: TurboModuleRegistry.getEnforcing(...):
'RNGestureHandlerModule' could not be found.

Verify that your native modules are linked correctly.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Your app is completely bricked, your Metro bundler is acting confused, and you’re left wondering why a package you just installed is allegedly missing from the face of the earth.&lt;/p&gt;
&lt;p&gt;Let's look at exactly why this happens and the 3-step checklist to clear it up without losing your sanity.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-root-cause-javascript-vs-native-code&quot;&gt;The Root Cause: JavaScript vs. Native Code&lt;/h3&gt;
&lt;p&gt;Modern Expo apps are beautifully split into two worlds:&lt;/p&gt;
&lt;h2 id=&quot;the-js-bundle-&quot;&gt;The JS Bundle:&lt;/h2&gt;
&lt;p&gt;Your components, logic, and regular styles. Metro can hot-reload this in milliseconds.&lt;/p&gt;
&lt;h2 id=&quot;the-native-layer-&quot;&gt;The Native Layer:&lt;/h2&gt;
&lt;p&gt;The underlying Kotlin/Java and Swift/Objective-C code that actually talks to the phone's hardware.&lt;/p&gt;
&lt;p&gt;When you install a package that uses native code (like &lt;code&gt;react-native-gesture-handler&lt;/code&gt;, &lt;code&gt;react-native-reanimated&lt;/code&gt;, or a map library), Metro cannot hot-reload native code into an active app binary.&lt;/p&gt;
&lt;p&gt;If you are running a pre-built Development Client or using Expo Go, it only knows about the native modules that were compiled the last time you built the app.&lt;/p&gt;
&lt;p&gt;It has no idea this new native module exists yet, so the registry panics and throws an &lt;code&gt;Invariant Violation&lt;/code&gt;.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-ultimate-un-brick-my-app-checklist&quot;&gt;The Ultimate &quot;Un-Brick My App&quot; Checklist&lt;/h2&gt;
&lt;p&gt;Next time this red screen of death pops up, run through these three steps in order.&lt;/p&gt;
&lt;h3 id=&quot;1-rebuild-the-app-binary-the-absolute-must-&quot;&gt;1. Rebuild the App Binary (The Absolute Must)&lt;/h3&gt;
&lt;p&gt;Simply restarting the Metro bundler won’t cut it.&lt;/p&gt;
&lt;p&gt;You need to recompile your native code so the new library gets bundled into the actual simulator app.&lt;/p&gt;
&lt;p&gt;Stop your server and run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# For Android Simulators / Devices
npx expo run:android

# For iOS Simulators
npx expo run:ios
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-force-a-clean-prebuild-if-using-custom-native-directories-&quot;&gt;2. Force a Clean Prebuild (If Using Custom Native Directories)&lt;/h3&gt;
&lt;p&gt;If you are managing your own &lt;code&gt;android&lt;/code&gt; or &lt;code&gt;ios&lt;/code&gt; directories and things get desynced, force Expo to regenerate them with the new native dependencies linked:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo prebuild --clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This removes the generated native projects and recreates them from your Expo configuration, ensuring newly installed native packages are correctly integrated.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-nuke-the-metro-cache&quot;&gt;3. Nuke the Metro Cache&lt;/h3&gt;
&lt;p&gt;Sometimes Metro holds onto a stale dependency graph like a grudge.&lt;/p&gt;
&lt;p&gt;If you’ve rebuilt the binary and it still complains, start your project while forcing a total cache clearance:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start -c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This clears Metro's cache and forces it to rebuild the dependency graph from scratch.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-7&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Native linking issues can feel incredibly frustrating because nothing appears wrong in your JavaScript code.&lt;/p&gt;
&lt;p&gt;The problem is usually that your app binary and your JavaScript bundle have fallen out of sync.&lt;/p&gt;
&lt;p&gt;Whenever you see errors like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;RNGestureHandlerModule could not be found&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Native module cannot be null&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;TurboModuleRegistry.getEnforcing(...) failed&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Module has not been registered&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run through this checklist:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Rebuild the app binary.&lt;/li&gt;
&lt;li&gt;Run a clean prebuild if you're using native directories.&lt;/li&gt;
&lt;li&gt;Clear the Metro cache.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Most of the time, one of those three steps will get you back up and running.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;a-quick-sidebar-for-tired-developers&quot;&gt;A Quick Sidebar for Tired Developers&lt;/h2&gt;
&lt;p&gt;If you're reading this at 2:00 AM while violently copy-pasting cryptic mobile stack traces into search engines, I feel your pain deeply.&lt;/p&gt;
&lt;p&gt;I got so tired of hunting down hidden Gradle errors and obscure CocoaPods issues that I decided to build a tool to automate the headache.&lt;/p&gt;
&lt;p&gt;It's called &lt;a rel=&quot;nofollow&quot; href=&quot;https://fixmyerrorapp.com&quot;&gt;Fix My Error&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I set up a completely registration-free sandbox right on the homepage using this exact native linkage error so you can see how it works in real time without handing over your data.&lt;/p&gt;
&lt;p&gt;If you like how it parses the logs, you can drop your own daily errors into the core engine with a free account (which gives you 5 free fixes per day), or check out the Pro tier if you're working in a heavy production environment.&lt;/p&gt;
&lt;p&gt;Give the sandbox a spin next time Expo decides to ruin your afternoon, and let me know if it helps your workflow.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;How do you usually handle these types of native linking bugs?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Drop a comment if this checklist worked for you, or let me know if there's an obscure Expo error that's currently driving you crazy.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/20703/how-to-fix-module-could-not-be-found-in-react-native-expo</guid>
<pubDate>Tue, 16 Jun 2026 11:45:20 +0000</pubDate>
</item>
<item>
<title>Your Expo App Only Speaks English. Here's How to Fix That in 10 Minutes and make it Global</title>
<link>https://coderlegion.com/20599/your-expo-app-only-speaks-english-heres-how-to-fix-that-in-10-minutes-and-make-it-global</link>
<description>&lt;h3 id=&quot;most-apps-speak-one-language-and-silently-lose-everyone-else-&quot;&gt;Most apps speak one language and silently lose everyone else.&lt;/h3&gt;
&lt;p&gt;If your app is English-only, you are telling a massive portion of the global market to meet you &lt;em&gt;halfway&lt;/em&gt;. If you want them to open your app and instantly feel at &lt;strong&gt;home&lt;/strong&gt;—in Spanish, French, Arabic, Hindi, or any of the 200+ global languages they call their own—you usually have to pay the standard &lt;em&gt;&quot;i18n tax.&quot;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Look at the traditional i18n package ecosystem on npm. It pulls in over &lt;strong&gt;500,000&lt;/strong&gt; active downloads and &lt;strong&gt;180,000&lt;/strong&gt; installs in last 7 days.&lt;/p&gt;
&lt;p&gt;The demand is massive, but the workflow is stuck in the stone age. Standard i18n forces you into a brutal key-management chore: you invent arbitrary keys, maintain separate massive JSON dictionary files, and manually translate arrays or copy-paste phrases one by one. If you typo a key, your user gets an ugly, broken placeholder string.&lt;/p&gt;
&lt;p&gt;We are developers. We are too busy for dictionary maintenance.&lt;br&gt;
That is why I built expo-polyglot-ai—a zero-runtime overhead localization engine that eliminates manual translations entirely.&lt;/p&gt;
&lt;h3 id=&quot;-the-code-is-the-key&quot;&gt;⚡ The Code IS the Key&lt;/h3&gt;
&lt;p&gt;With &lt;strong&gt;&lt;em&gt;expo-polyglot-a&lt;/em&gt;&lt;/strong&gt;i, you don’t abstract your screens into rigid backend tracking maps. You code naturally. You write plain, normal English directly into your UI layout, and let automated compilation hooks do the heavy lifting.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The Competitor vs. expo-polyglot-ai&lt;/strong&gt;&lt;br&gt;
&lt;em&gt;The Traditional i18n Chore:&lt;/em&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Breaks your code layout. Forces you to maintain 5 separate JSON files manually.
&amp;lt;Text&amp;gt;{t('auth.dashboard.profile_alert_text_v2')}&amp;lt;/Text&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;The expo-polyglot-ai Way:&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;// Write normal text. The build script automatically discovers, translates, and generates your local bundles.
&amp;lt;TranslateText&amp;gt;Welcome back to your security dashboard profile.&amp;lt;/TranslateText&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Why Drop It In?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; •&lt;strong&gt;No Dictionary Maintenance:&lt;/strong&gt; Just your existing app strings, your LLM of choice (like the Gemini gateway proxy), and a terminal command.&lt;br&gt;
•&lt;strong&gt;0ms Native Performance (Persistent Local Caching):&lt;/strong&gt; This isn't a slow runtime wrapper that makes users wait for an API call on every screen load. The translation happens at build time. Strings are cached locally, meaning each string is only ever translated once. Your user experiences lightning-fast, zero-network locale snapping.&lt;br&gt;
•&lt;strong&gt;Layout Defenses Built-In (autoShrink):&lt;/strong&gt; Different languages occupy different lengths (a short English phrase can turn into a massive sentence in French). By attaching an autoShrink property directly to our custom typography component, the engine micro-scales font sizes down proportionally to preserve your Tailwind/NativeWind layout constraints on smaller devices.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Shift to Autopilot in 5 Quick Steps&lt;/strong&gt;&lt;br&gt;
1.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install expo-polyglot-ai
npx expo install expo-localization @react-native-async-storage/async-storage
npx expo install @react-native-picker/picker  'This provides an easy way to create drop down menu list'

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;2. Replace  Text with TranslateText&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;jsx
import { TranslateText } from 'expo-polyglot-ai';

// Before
&amp;lt;Text&amp;gt;Welcome back!&amp;lt;/Text&amp;gt;

// After — now speaks French, Zulu, Arabic, Hindi... automatically
&amp;lt;TranslateText&amp;gt;Welcome back!&amp;lt;/TranslateText&amp;gt;
 

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Write all your strings in the your app inside these You Strings&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.Wrap the Root Layout&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-jsx&quot;&gt;import { Picker } from &quot;@react-native-picker/picker&quot;;
import {
  FlatList,
  SafeAreaView,
  StatusBar,
  StyleSheet,
  View,
} from &quot;react-native&quot;;
// Import your custom package components cleanly
import {
  TranslateText,
  TranslationProvider,
  useTranslation,
} from &quot;expo-polyglot-ai&quot;;

// Mock list data with various string lengths to test layout text expansion
const SAMPLE_MOCK_DATA = [
  { id: &quot;1&quot;, phrase: &quot;Welcome back to your security dashboard profile.&quot; },
  {
    id: &quot;2&quot;,
    phrase: &quot;Please tap application settings to modify your configurations.&quot;,
  },
  {
    id: &quot;3&quot;,
    phrase: &quot;Are you sure you want to confirm your active logout request?&quot;,
  },
];

// The developer defines what they built in their specific project
const MY_APP_BUNDLES = {
  fr: require(&quot;../../locales/fr.json&quot;),
  es: require(&quot;../../locales/es.json&quot;),
  yo: require(&quot;../../locales/yo.json&quot;),
};

Export default function MainAppScreen() {
  const { currentLanguage, changeLanguage } = useTranslation();

  return (
    &amp;lt;SafeAreaView style={styles.container}&amp;gt;
      &amp;lt;StatusBar barStyle=&quot;dark-content&quot; /&amp;gt;

      {/* Dynamic Translated Static Heading */}
      &amp;lt;TranslateText style={styles.heading}&amp;gt;Choose Language&amp;lt;/TranslateText&amp;gt;

      {/* 1. Language Dropdown Selector */}
      &amp;lt;View style={styles.pickerContainer}&amp;gt;
        &amp;lt;Picker
          selectedValue={currentLanguage}
          onValueChange={(langValue) =&amp;gt; changeLanguage(langValue)}
          style={styles.picker}
        &amp;gt;
          &amp;lt;Picker.Item label=&quot;English (Base)&quot; value=&quot;en&quot; /&amp;gt;
          &amp;lt;Picker.Item label=&quot;French (Français)&quot; value=&quot;fr&quot; /&amp;gt;
          &amp;lt;Picker.Item label=&quot;Spanish (Español)&quot; value=&quot;es&quot; /&amp;gt;
          &amp;lt;Picker.Item label=&quot;Yoruba (Èdè Yorùbá)&quot; value=&quot;yo&quot; /&amp;gt;
          &amp;lt;/Picker&amp;gt;
      &amp;lt;/View&amp;gt;

      &amp;lt;TranslateText style={styles.subHeading}&amp;gt;
        Dynamic Feed Content apears here
      &amp;lt;/TranslateText&amp;gt;

      {/* 2. FlatList implementation with automatic caching layer protection */}
      &amp;lt;FlatList
        data={SAMPLE_MOCK_DATA}
        keyExtractor={(item) =&amp;gt; item.id}
        contentContainerStyle={styles.listContainer}
        renderItem={({ item }) =&amp;gt; (
          &amp;lt;View style={styles.card}&amp;gt;
            {/* autoShrink is enabled here! If French/Yoruba text gets 
              too long, it safely shrinks the font to keep layouts unbroken.
            */}
          &amp;lt;TranslateText style={styles.itemText}&amp;gt;{item.phrase}&amp;lt;/TranslateText&amp;gt;
          &amp;lt;/View&amp;gt;
        )}
      /&amp;gt;
    &amp;lt;/SafeAreaView&amp;gt;
  );
}

export default function App() {
  return (
    /* 3. Wrap your root with the Provider. 
      Replace this URL string with your deployed Google Cloud Function Webhook endpoint!
    */
    &amp;lt;TranslationProvider
      apiUrl=&quot;https://&quot;  //url to your deployed agentic ai in the cloud
      bundles={MY_APP_BUNDLES}
      apiKey={&quot;Your X-API-Key&quot;}// Developers pass their own runtime API key safely via configuration or props!

    &amp;gt;
      &amp;lt;MainAppScreen /&amp;gt;
    &amp;lt;/TranslationProvider&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;4. Create the polyglot-ai-agent(Engine) using Langgraph and deploy it to GCP.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-py&quot;&gt;import functions_framework
import os
from typing import TypedDict, List
import json
from google import genai
from langgraph.graph import StateGraph, END

# === Single-string agent (unchanged) ===
class AgentState(TypedDict):
    text: str
    target_lang: str
    translation: str
    quality_check: bool
    iterations: int

client = genai.Client(api_key=os.getenv(&quot;GEMINI_API_KEY&quot;))

def translation_node(state: AgentState) -&amp;gt; dict:
    prompt = f&quot;&quot;&quot;
    You are an expert mobile UI string localization assistant. 
    Translate the following text into natural, fluent, context-aware {state['target_lang']}.
    Preserve technical variables and UI intent cleanly.
    Return ONLY the raw translated string.

    Text: &quot;{state['text']}&quot;
    &quot;&quot;&quot;
    response = client.models.generate_content(model='gemini-2.5-flash', contents=prompt)
    return {
        &quot;translation&quot;: response.text.strip(),
        &quot;iterations&quot;: state.get('iterations', 0) + 1
    }

def validation_node(state: AgentState) -&amp;gt; dict:
    if state.get('iterations', 0) &amp;gt;= 2:
        return {&quot;quality_check&quot;: True}

    validation_prompt = f&quot;&quot;&quot;
    Analyze this translated mobile application UI string. 
    Did the translation introduce unnecessary conversational phrasing, markdown, or text quotes? 
    Answer with exactly 'YES' if it is clean or 'NO' if it needs a rewrite.

    Translation: &quot;{state['translation']}&quot;
    &quot;&quot;&quot;
    response = client.models.generate_content(model='gemini-2.5-flash', contents=validation_prompt)
    is_clean = &quot;YES&quot; in response.text.upper()
    return {&quot;quality_check&quot;: is_clean}

def should_continue(state: AgentState):
    if state[&quot;quality_check&quot;]:
        return END
    return &quot;translator&quot;

workflow = StateGraph(AgentState)
workflow.add_node(&quot;translator&quot;, translation_node)
workflow.add_node(&quot;validator&quot;, validation_node)
workflow.set_entry_point(&quot;translator&quot;)
workflow.add_edge(&quot;translator&quot;, &quot;validator&quot;)
workflow.add_conditional_edges(&quot;validator&quot;, should_continue)
agent = workflow.compile()

# === Batch translation (new) ===
def translate_batch(texts: List[str], target_lang: str) -&amp;gt; List[str]:
    &quot;&quot;&quot;
    Translates a list of strings in a single Gemini call.
    Uses a numbered-list format so the model returns strings in order,
    and we can reliably split the response back into a list.
    &quot;&quot;&quot;
    numbered_input = &quot;\n&quot;.join(f&quot;{i+1}. {t}&quot; for i, t in enumerate(texts))

    prompt = f&quot;&quot;&quot;
You are an expert mobile UI string localization assistant.
Translate each of the following numbered UI strings into natural, fluent,
context-aware {target_lang}. Preserve technical variables and UI intent.

Return ONLY a JSON array of strings, in the exact same order as the input,
with no markdown, no code fences, no extra commentary. The array must have
exactly {len(texts)} elements.

Input strings:
{numbered_input}
&quot;&quot;&quot;

    response = client.models.generate_content(model='gemini-2.5-flash', contents=prompt)
    raw = response.text.strip()

    # Strip markdown code fences if the model added them anyway
    if raw.startswith(&quot;```&quot;):
        raw = raw.split(&quot;\n&quot;, 1)[1] if &quot;\n&quot; in raw else raw
        if raw.endswith(&quot;```&quot;):
            raw = raw[:-3]
        raw = raw.strip()
        if raw.startswith(&quot;json&quot;):
            raw = raw[4:].strip()

    try:
        translated = json.loads(raw)
        if not isinstance(translated, list):
            raise ValueError(&quot;Response is not a JSON array&quot;)
    except (json.JSONDecodeError, ValueError):
        # Fallback: if parsing fails, return originals untouched
        return texts

    # Pad/truncate to match input length defensively
    if len(translated) &amp;lt; len(texts):
        translated = translated + texts[len(translated):]
    elif len(translated) &amp;gt; len(texts):
        translated = translated[:len(texts)]

    return [str(t).strip() for t in translated]

@functions_framework.http
def polyglot_endpoint(request):
    if request.method == 'OPTIONS':
        headers = {
            'Access-Control-Allow-Origin': '*',
            'Access-Control-Allow-Methods': 'POST',
            'Access-Control-Allow-Headers': 'Content-Type,X-API-Key',
            'Access-Control-Max-Age': '3600'
        }
        return ('', 204, headers)

    headers = {'Access-Control-Allow-Origin': '*'}

    provided_key = request.headers.get('X-API-Key')
    expected_key = os.getenv(&quot;TRANSLATION_API_KEY&quot;)

    if not expected_key or provided_key != expected_key:
        return (json.dumps({&quot;error&quot;: &quot;Unauthorized&quot;}), 401, headers)

    try:
        request_json = request.get_json(silent=True)
        target_lang = request_json.get('target_lang')

        if not target_lang:
            return (json.dumps({&quot;error&quot;: &quot;Missing target_lang parameter&quot;}), 400, headers)

        # === Batch mode: { &quot;texts&quot;: [...], &quot;target_lang&quot;: &quot;...&quot; } ===
        texts = request_json.get('texts')
        if texts is not None:
            if not isinstance(texts, list) or not all(isinstance(t, str) for t in texts):
                return (json.dumps({&quot;error&quot;: &quot;'texts' must be an array of strings&quot;}), 400, headers)

            if len(texts) == 0:
                return (json.dumps({&quot;translated_texts&quot;: []}), 200, headers)

            translated_texts = translate_batch(texts, target_lang)
            return (json.dumps({&quot;translated_texts&quot;: translated_texts}), 200, headers)

        # === Single mode (existing behavior, unchanged) ===
        text = request_json.get('text')
        if not text:
            return (json.dumps({&quot;error&quot;: &quot;Missing input text or target_lang parameters&quot;}), 400, headers)

        initial_input = {&quot;text&quot;: text, &quot;target_lang&quot;: target_lang, &quot;iterations&quot;: 0}
        final_state = agent.invoke(initial_input)

        response_payload = {&quot;translated_text&quot;: final_state[&quot;translation&quot;]}
        return (json.dumps(response_payload), 200, headers)

    except Exception as e:
        return (json.dumps({&quot;error&quot;: str(e)}), 500, headers)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;5. Pop in build folder that holds code&lt;/strong&gt; for automatically extracting all your app strings and automatically translating all the strings to the languages you wish your app speaks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Extract-strings.js&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const fs = require(&quot;fs&quot;);
const path = require(&quot;path&quot;);

const SOURCE_DIR = process.argv[2] || &quot;./src&quot;;
const OUTPUT_FILE = process.argv[3] || &quot;strings.json&quot;;

const VALID_EXTENSIONS = [&quot;.js&quot;, &quot;.jsx&quot;, &quot;.ts&quot;, &quot;.tsx&quot;];

// Matches: &amp;lt;TranslateText ...&amp;gt;SOME TEXT&amp;lt;/TranslateText&amp;gt;
// Captures only simple string children (no JSX expressions/interpolation)
const TRANSLATE_TEXT_REGEX =
  /&amp;lt;TranslateText\b[^&amp;gt;]*&amp;gt;([^&amp;lt;{][\s\S]*?)&amp;lt;\/TranslateText&amp;gt;/g;

function walk(dir, files = []) {
  for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
    if (entry.name === &quot;node_modules&quot; || entry.name.startsWith(&quot;.&quot;)) continue;
    const fullPath = path.join(dir, entry.name);
    if (entry.isDirectory()) {
      walk(fullPath, files);
    } else if (VALID_EXTENSIONS.includes(path.extname(entry.name))) {
      files.push(fullPath);
    }
  }
  return files;
}

function extractFromFile(filePath, strings) {
  const content = fs.readFileSync(filePath, &quot;utf8&quot;);
  let match;
  while ((match = TRANSLATE_TEXT_REGEX.exec(content)) !== null) {
    let text = match[1];

    // Collapse whitespace/newlines, trim
    text = text.replace(/\s+/g, &quot; &quot;).trim();

    // Skip empty or expression-only content (e.g. {variable})
    if (!text || text.startsWith(&quot;{&quot;)) continue;

    if (!strings[text]) {
      strings[text] = { sources: [] };
    }
    const relPath = path.relative(process.cwd(), filePath);
    if (!strings[text].sources.includes(relPath)) {
      strings[text].sources.push(relPath);
    }
  }
}

function main() {
  if (!fs.existsSync(SOURCE_DIR)) {
    console.error(`Source directory not found: ${SOURCE_DIR}`);
    process.exit(1);
  }

  const files = walk(SOURCE_DIR);
  console.log(`Scanning ${files.length} files in ${SOURCE_DIR}...`);

  const strings = {};
  for (const file of files) {
    extractFromFile(file, strings);
  }

  const uniqueStrings = Object.keys(strings);
  console.log(`Found ${uniqueStrings.length} unique strings.`);

  // Output format: array of { text, sources }
  const output = uniqueStrings.map((text) =&amp;gt; ({
    text,
    sources: strings[text].sources,
  }));

  fs.writeFileSync(OUTPUT_FILE, JSON.stringify(output, null, 2));
  console.log(`Written to ${OUTPUT_FILE}`);
}

main();


&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Translate-bundle.jsx&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const fs = require(&quot;fs&quot;);
const path = require(&quot;path&quot;);

const API_URL =
  &quot;paste your deployed ai agent url here&quot;;

const STRINGS_FILE = process.argv[2] || &quot;strings.json&quot;;
const OUTPUT_DIR = process.argv[3] || &quot;./locales&quot;;
const API_KEY = process.argv[4];
const LANGUAGES = (process.argv[5] || &quot;fr,es,yo,zu,de,it&quot;)
  .split(&quot;,&quot;)
  .map((l) =&amp;gt; l.trim())
  .filter(Boolean);

// Delay between requests to avoid rate-limiting / quota issues (ms)
const REQUEST_DELAY_MS = 1200;

// Retry settings
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 3000;

function sleep(ms) {
  return new Promise((resolve) =&amp;gt; setTimeout(resolve, ms));
}

async function translateOne(text, targetLang) {
  for (let attempt = 1; attempt &amp;lt;= MAX_RETRIES; attempt++) {
    try {
      const response = await fetch(API_URL, {
        method: &quot;POST&quot;,
        headers: {
          &quot;Content-Type&quot;: &quot;application/json&quot;,
          &quot;X-API-Key&quot;: API_KEY,
        },
        body: JSON.stringify({ text, target_lang: targetLang }),
      });

      if (!response.ok) {
        const errBody = await response.text();
        throw new Error(`HTTP ${response.status}: ${errBody}`);
      }

      const data = await response.json();
      if (!data.translated_text) {
        throw new Error(&quot;No translated_text in response&quot;);
      }
      return data.translated_text;
    } catch (err) {
      console.warn(
        `  Attempt ${attempt}/${MAX_RETRIES} failed for &quot;${text.slice(0, 40)}...&quot; -&amp;gt; ${targetLang}: ${err.message}`,
      );
      if (attempt &amp;lt; MAX_RETRIES) {
        await sleep(RETRY_DELAY_MS);
      } else {
        throw err;
      }
    }
  }
}

async function main() {
  if (!API_KEY) {
    console.error(
      &quot;Missing API key. Usage: node translate-bundle.js &amp;lt;strings.json&amp;gt; &amp;lt;output-dir&amp;gt; &amp;lt;api-key&amp;gt; [languages]&quot;,
    );
    process.exit(1);
  }

  if (!fs.existsSync(STRINGS_FILE)) {
    console.error(`Strings file not found: ${STRINGS_FILE}`);
    process.exit(1);
  }

  const strings = JSON.parse(fs.readFileSync(STRINGS_FILE, &quot;utf8&quot;));
  const texts = strings.map((s) =&amp;gt; s.text);

  if (!fs.existsSync(OUTPUT_DIR)) {
    fs.mkdirSync(OUTPUT_DIR, { recursive: true });
  }

  console.log(
    `Translating ${texts.length} strings into ${LANGUAGES.length} languages (${LANGUAGES.join(&quot;, &quot;)})...`,
  );
  console.log(
    `Estimated time: ~${Math.ceil((texts.length * LANGUAGES.length * REQUEST_DELAY_MS) / 1000 / 60)} minutes\n`,
  );

  for (const lang of LANGUAGES) {
    const outputFile = path.join(OUTPUT_DIR, `${lang}.json`);

    // Load existing translations to resume
    let bundle = {};
    if (fs.existsSync(outputFile)) {
      bundle = JSON.parse(fs.readFileSync(outputFile, &quot;utf8&quot;));
      console.log(
        `[${lang}] Resuming — ${Object.keys(bundle).length} already translated.`,
      );
    } else {
      console.log(`[${lang}] Starting fresh.`);
    }

    let translatedCount = 0;
    let skippedCount = 0;

    for (let i = 0; i &amp;lt; texts.length; i++) {
      const text = texts[i];

      if (bundle[text]) {
        skippedCount++;
        continue;
      }

      try {
        const translated = await translateOne(text, lang);
        bundle[text] = translated;
        translatedCount++;

        console.log(
          `[${lang}] (${i + 1}/${texts.length}) &quot;${text.slice(0, 50)}${text.length &amp;gt; 50 ? &quot;...&quot; : &quot;&quot;}&quot; -&amp;gt; &quot;${translated.slice(0, 50)}${translated.length &amp;gt; 50 ? &quot;...&quot; : &quot;&quot;}&quot;`,
        );

        // Save progress after every translation (resumable)
        fs.writeFileSync(outputFile, JSON.stringify(bundle, null, 2));

        await sleep(REQUEST_DELAY_MS);
      } catch (err) {
        console.error(
          `[${lang}] FAILED for &quot;${text.slice(0, 50)}...&quot;: ${err.message}`,
        );
        console.error(
          `[${lang}] Skipping this string for now — re-run script later to retry.`,
        );
      }
    }

    console.log(
      `[${lang}] Done. Translated ${translatedCount} new, skipped ${skippedCount} cached. Total: ${Object.keys(bundle).length}\n`,
    );
  }

  console.log(&quot;All languages processed. Bundles written to:&quot;, OUTPUT_DIR);
}

main();



&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Bind the Automator (package.json)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;JSON&lt;br&gt;
&quot;scripts&quot;: {&lt;br&gt;
  &quot;translate&quot;: &quot;node src/build/extract-strings.js src/app strings.json &amp;amp;&amp;amp; node src/build/translate-bundle.js strings.json src/locales $EXPO_PUBLIC_POLYGLOT_API_KEY fr,es,de,ja&quot;&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;Run It&lt;br&gt;
Whenever you add copy, build new screens, or scale to new countries, just fire:&lt;br&gt;
Bash&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm run translate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The script analyzes your files incrementally, ignores previously translated pairs to protect your API usage, and builds missing language targets in under 30 seconds.&lt;/p&gt;
&lt;p&gt;And just like that your app is multi-lingual. When you build it, all translations will be there locally.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;🔥 Retention is Readability&lt;/strong&gt;&lt;br&gt;
Stop asking your international users to meet you in English. Because an app your users can read is an app they'll actually keep.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;📦 &lt;strong&gt;npm:&lt;/strong&gt; &lt;em&gt;npm install expo-polyglot-a&lt;/em&gt;i ⭐ &lt;em&gt;GitHub: &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Inspire00/expo-polyglot-ai&quot;&gt;github.com/Inspire00/expo-polyglot-ai&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;Built for Expo developers who'd rather ship features than maintain translation files.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Inspire00/expo-polyglot-ai/blob/main/images/englishscreen.jpg&quot;&gt;Example image 1&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Inspire00/expo-polyglot-ai/blob/main/images/frenchscreen.jpg&quot;&gt;Example image 2&lt;/a&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/Inspire00/expo-polyglot-ai/blob/main/images/german.jpg&quot;&gt;Example image 3&lt;/a&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/20599/your-expo-app-only-speaks-english-heres-how-to-fix-that-in-10-minutes-and-make-it-global</guid>
<pubDate>Sun, 14 Jun 2026 23:34:06 +0000</pubDate>
</item>
<item>
<title>Why Your Mobile App Works Perfectly Locally But Crashes Instantly in TestFlight</title>
<link>https://coderlegion.com/20265/why-your-mobile-app-works-perfectly-locally-but-crashes-instantly-in-testflight</link>
<description>&lt;p&gt;We’ve all been there. You spend weeks building a feature, testing it on your local simulator or a physical device running a development stream. Everything is butter. No lag, no warnings.&lt;/p&gt;
&lt;p&gt;You confidently bundle the app, ship it to TestFlight, wait for Apple to finish processing, download it... and the moment you tap the app icon, it instantly flashes and closes. Hard crash.&lt;/p&gt;
&lt;p&gt;Before you lose your mind or start blindly tweaking code, remember that production builds behave entirely differently than development environments.&lt;/p&gt;
&lt;p&gt;Here are the four most common reasons your app dies the literal second it hits production, and exactly how to fix them.&lt;/p&gt;
&lt;h2 id=&quot;1-the-missing-privacy-keys-string-info-plist-&quot;&gt;1. The Missing Privacy Keys String (&lt;code&gt;Info.plist&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;If your app requests permissions for features like Location Services (maps, background location), the Camera, or the Photo Library, Apple requires you to explicitly state why in your &lt;code&gt;Info.plist&lt;/code&gt; file using usage description keys (like &lt;code&gt;NSCameraUsageDescription&lt;/code&gt; or &lt;code&gt;NSLocationWhenInUseUsageDescription&lt;/code&gt;).&lt;/p&gt;
&lt;h4 id=&quot;the-gotcha&quot;&gt;The Gotcha&lt;/h4&gt;
&lt;p&gt;In a local development environment, sometimes a framework will let a missing string slide, fallback to a default, or catch the exception gracefully.&lt;/p&gt;
&lt;h4 id=&quot;the-production-reality&quot;&gt;The Production Reality&lt;/h4&gt;
&lt;p&gt;Apple’s iOS security layer will ruthlessly and instantly terminate the application binary on launch if your production code attempts to initialize a library requiring these permissions without the matching text string configured.&lt;/p&gt;
&lt;h4 id=&quot;the-fix-1&quot;&gt;The Fix&lt;/h4&gt;
&lt;p&gt;Double-check your &lt;code&gt;Info.plist&lt;/code&gt; (or your Expo &lt;code&gt;app.json&lt;/code&gt; plugins). Make sure every single hardware or permission API your code imports has a clear, user-facing explanation string attached.&lt;/p&gt;
&lt;h2 id=&quot;2-missing-push-notification-entitlements&quot;&gt;2. Missing Push Notification Entitlements&lt;/h2&gt;
&lt;p&gt;If your application includes code for push notifications (even if you haven’t fully wired up the backend yet), your binary needs specific clearance to launch.&lt;/p&gt;
&lt;h4 id=&quot;the-gotcha-1&quot;&gt;The Gotcha&lt;/h4&gt;
&lt;p&gt;Local builds often bypass strict entitlement checks, or run using a wildcard development provisioning profile that covers everything loosely.&lt;/p&gt;
&lt;h4 id=&quot;the-production-reality-1&quot;&gt;The Production Reality&lt;/h4&gt;
&lt;p&gt;When built for distribution, if your app contains code for handling remote notifications but the App Store Provisioning Profile doesn't explicitly have the &lt;strong&gt;Push Notifications&lt;/strong&gt; entitlement enabled, the OS will trigger a fatal launch mismatch exception.&lt;/p&gt;
&lt;h4 id=&quot;the-fix-2&quot;&gt;The Fix&lt;/h4&gt;
&lt;p&gt;Head to your Apple Developer Account under &lt;strong&gt;Identifiers&lt;/strong&gt;, verify that your App ID has &lt;strong&gt;Push Notifications&lt;/strong&gt; checked, and regenerate your production profile.&lt;/p&gt;
&lt;h2 id=&quot;3-dead-code-elimination-amp-aggressive-minification&quot;&gt;3. Dead Code Elimination &amp;amp; Aggressive Minification&lt;/h2&gt;
&lt;p&gt;When building locally, your JS bundling or native compilation keeps debug code, metadata, and helper functions intact.&lt;/p&gt;
&lt;p&gt;When you build for production, optimization tools like ProGuard/R8 (for Android native engines) or aggressive tree-shaking strip away &quot;unused&quot; code to shrink the binary size.&lt;/p&gt;
&lt;h4 id=&quot;the-gotcha-2&quot;&gt;The Gotcha&lt;/h4&gt;
&lt;p&gt;Sometimes, these optimization tools accidentally strip away native modules or reflection classes used by third-party packages, assuming they are dead code because they aren't explicitly referenced in the main thread.&lt;/p&gt;
&lt;h4 id=&quot;the-production-reality-2&quot;&gt;The Production Reality&lt;/h4&gt;
&lt;p&gt;The app boots up, looks for a compiled native library or native method bridge, finds a missing reference, and triggers a fatal crash right during initialization.&lt;/p&gt;
&lt;h4 id=&quot;the-fix-3&quot;&gt;The Fix&lt;/h4&gt;
&lt;p&gt;If you are using native dependencies, make sure your obfuscation/minify configuration files explicitly include rules to keep specific third-party library paths intact.&lt;/p&gt;
&lt;h2 id=&quot;4-broken-initialization-flow-environment-variables-&quot;&gt;4. Broken Initialization Flow (Environment Variables)&lt;/h2&gt;
&lt;p&gt;How does your app determine its backend URL or third-party service tokens?&lt;/p&gt;
&lt;p&gt;If you are relying on a local &lt;code&gt;.env&lt;/code&gt; file that is git-ignored, those values might not be making it into your production build machine or CI/CD pipeline.&lt;/p&gt;
&lt;h4 id=&quot;the-gotcha-3&quot;&gt;The Gotcha&lt;/h4&gt;
&lt;p&gt;The app builds successfully because the compiler doesn't care if a string variable is blank or &lt;code&gt;null&lt;/code&gt; at build time.&lt;/p&gt;
&lt;h4 id=&quot;the-production-reality-3&quot;&gt;The Production Reality&lt;/h4&gt;
&lt;p&gt;On launch, your application's root mounting sequence tries to parse an undefined API key or a &lt;code&gt;null&lt;/code&gt; base URL during setup. If your code doesn't have a fallback check, it throws a fatal JavaScript or runtime error before the first screen even renders.&lt;/p&gt;
&lt;h4 id=&quot;the-fix-4&quot;&gt;The Fix&lt;/h4&gt;
&lt;p&gt;Always verify that your build dashboard (like Expo Application Services, GitHub Actions, or local production scripts) has your production environment variables explicitly mapped before hitting compile.&lt;/p&gt;
&lt;h2 id=&quot;how-to-stop-guessing-and-find-the-proof&quot;&gt;How to Stop Guessing and Find the Proof&lt;/h2&gt;
&lt;p&gt;Stop guessing and changing random lines of code hoping for a miracle. Apple leaves an exact paper trail for immediate launch crashes.&lt;/p&gt;
&lt;h3 id=&quot;on-your-test-device&quot;&gt;On Your Test Device&lt;/h3&gt;
&lt;p&gt;Open up the TestFlight app on your iPhone, tap on the application name, scroll down to &lt;strong&gt;Crash Logs&lt;/strong&gt;, and you can view or share the exact file.&lt;/p&gt;
&lt;h3 id=&quot;inside-xcode&quot;&gt;Inside Xcode&lt;/h3&gt;
&lt;p&gt;Navigate to:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;Xcode &amp;gt; Window &amp;gt; Organizer &amp;gt; Crashes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Apple aggregates logs directly from TestFlight users here, often highlighting the exact line of compiled code that triggered the crash (look for terms like &lt;code&gt;SIGABRT&lt;/code&gt; or &lt;code&gt;EXC_CRASH&lt;/code&gt;).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;What's the absolute strangest &quot;works locally, breaks in production&quot; bug you've ever had to hunt down?&lt;/p&gt;
&lt;p&gt;Let's talk in the comments!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/20265/why-your-mobile-app-works-perfectly-locally-but-crashes-instantly-in-testflight</guid>
<pubDate>Thu, 11 Jun 2026 09:01:21 +0000</pubDate>
</item>
<item>
<title>The React Native Native-Dependency Trap: How to Fix Demanding Build Failures Without Nuking node_mod</title>
<link>https://coderlegion.com/20039/the-react-native-native-dependency-trap-demanding-build-failures-without-nuking-nodemod</link>
<description>&lt;p&gt;It’s the same story every time. You run a simple package upgrade, or you decide it's time to bump your Expo SDK version. Locally, JavaScript compiled perfectly. But the second you run a native build, your terminal explodes with hundreds of lines of red text.&lt;/p&gt;
&lt;p&gt;On iOS, it’s a cryptic &lt;code&gt;CocoaPods could not find compatible versions for pod&lt;/code&gt; or a sudden compilation failure in &lt;code&gt;AppDelegate.mm&lt;/code&gt;. On Android, it’s a fatal Gradle lifecycle error or a missing namespace exception.&lt;/p&gt;
&lt;p&gt;When native dependencies break, most developers fall back on the classic loop: delete &lt;code&gt;node_modules&lt;/code&gt;, delete &lt;code&gt;package-lock.json&lt;/code&gt;, clear cache, reinstall, and pray.&lt;/p&gt;
&lt;p&gt;But blind-nuking your files rarely fixes the underlying architectural conflict. Here is how to actually diagnose and surgically resolve native dependency hell in modern React Native and Expo apps.&lt;/p&gt;
&lt;h2 id=&quot;1-the-root-cause-transitive-dependency-syncing&quot;&gt;1. The Root Cause: Transitive Dependency Syncing&lt;/h2&gt;
&lt;p&gt;When you install a library like &lt;code&gt;react-native-reanimated&lt;/code&gt; or a native camera module, that library relies on specific versions of underlying native libraries (Pods or Android libraries).&lt;/p&gt;
&lt;p&gt;If two different third-party packages require the same native dependency but expect completely different versions, your package manager forces a compromise in JavaScript. But when the native build tool (Xcode or Gradle) steps in, it sees two conflicting native frameworks trying to occupy the same space.&lt;/p&gt;
&lt;h4 id=&quot;for-expo-users-always-prioritize-the-pinned-versions&quot;&gt;For Expo Users: Always Prioritize the Pinned Versions&lt;/h4&gt;
&lt;p&gt;Running &lt;code&gt;npm install&lt;/code&gt; can bypass Expo's guardrails. Always use:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo install --fix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This forces Expo to look at your current SDK version and automatically downgrade or upgrade conflicting community packages to their exact validated native counterparts.&lt;/p&gt;
&lt;h2 id=&quot;2-the-ios-podfile-lock-paradox&quot;&gt;2. The iOS Podfile.lock Paradox&lt;/h2&gt;
&lt;p&gt;If your team introduces a package or you pull down &lt;code&gt;main&lt;/code&gt; and suddenly iOS won't build, the culprit is usually an out-of-sync &lt;code&gt;Podfile.lock&lt;/code&gt;. Running &lt;code&gt;pod install&lt;/code&gt; blindly sometimes isn't enough if cached pods are conflicting.&lt;/p&gt;
&lt;h4 id=&quot;the-surgical-fix&quot;&gt;The Surgical Fix&lt;/h4&gt;
&lt;p&gt;Instead of deleting your whole project configuration, clear the native iOS build cache specifically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ios &amp;amp;&amp;amp; pod cache clean --all
rm -rf Pods Podfile.lock
pod install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This forces CocoaPods to re-evaluate the dependency tree from absolute scratch based on your current &lt;code&gt;package.json&lt;/code&gt;, without losing your local JS settings.&lt;/p&gt;
&lt;h2 id=&quot;3-the-android-gradle-namespace-meltdown&quot;&gt;3. The Android Gradle Namespace Meltdown&lt;/h2&gt;
&lt;p&gt;With newer versions of Gradle and React Native, the way native Android modules declare their packages has changed (moving entirely to &lt;code&gt;namespace&lt;/code&gt; inside &lt;code&gt;build.gradle&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If you are using an older, unmaintained community package, Gradle will completely fail to compile the app on launch.&lt;/p&gt;
&lt;h4 id=&quot;the-surgical-fix-1&quot;&gt;The Surgical Fix&lt;/h4&gt;
&lt;p&gt;Instead of waiting for an open-source maintainer to update a dead repository, you can use &lt;code&gt;patch-package&lt;/code&gt; or Expo Config Plugins to alter the third-party library’s &lt;code&gt;build.gradle&lt;/code&gt; file locally.&lt;/p&gt;
&lt;p&gt;Alternatively, ensure your &lt;code&gt;android/gradle.properties&lt;/code&gt; has the proper architecture properties enabled:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-properties&quot;&gt;android.useAndroidX=true
android.enableJetifier=true
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;triage-your-terminal-output&quot;&gt;Triage Your Terminal Output&lt;/h2&gt;
&lt;p&gt;The biggest mistake developers make is trying to read the very bottom of a failed build log.&lt;/p&gt;
&lt;p&gt;Xcode and Gradle put the actual error at the beginning of the failure block, while the bottom lines are just the generic system telling you the process exited with code &lt;code&gt;1&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Always scroll back up to locate the first root error flag.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I'm currently tracking down these weird native compilation bugs and building a database of exact solutions for them. If you are currently fighting a messy React Native or Expo native stack trace that makes absolutely no sense, I built a live beta engine to parse them and spit out precise resolutions over at &lt;a rel=&quot;nofollow&quot; href=&quot;https://fixmyerrorapp.com&quot;&gt;fix-my-error-app.com&lt;/a&gt;. Drop your errors in there if you're stuck, and let me know in the comments what your most hated native build error is!&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Drop your errors in there if you're stuck, and let me know in the comments what your most hated native build error is!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/20039/the-react-native-native-dependency-trap-demanding-build-failures-without-nuking-nodemod</guid>
<pubDate>Tue, 09 Jun 2026 09:26:33 +0000</pubDate>
</item>
<item>
<title>How to Fix Xcode C++ Compiler Errors in React Native (Yoga / glog / constexpr)</title>
<link>https://coderlegion.com/19813/how-to-fix-xcode-c-compiler-errors-in-react-native-yoga-glog-constexpr</link>
<description>&lt;p&gt;You just upgraded your local development environment, updated Xcode, or bumped a minor patch version in your React Native project. You open your terminal, run your standard iOS build command, and instead of a clean bundling process, the terminal throws a massive wall of raw C++ compiler text at you.&lt;/p&gt;
&lt;p&gt;The error logs likely point to internal framework files like &lt;code&gt;Yoga.cpp&lt;/code&gt;, &lt;code&gt;glog&lt;/code&gt;, or the &lt;code&gt;fmt&lt;/code&gt; library, screaming about &lt;code&gt;constexpr&lt;/code&gt; or &lt;code&gt;consteval&lt;/code&gt; issues, or complaining that a specific language standard identifier is missing.&lt;/p&gt;
&lt;p&gt;This is one of the single most frustrating traps in mobile development. Your JavaScript is perfect. Your React components are clean. But your entire build is dead because an underlying C++ compilation standard is fighting your new Xcode tooling.&lt;/p&gt;
&lt;h2 id=&quot;why-this-happens-to-javascript-developers&quot;&gt;Why This Happens to JavaScript Developers&lt;/h2&gt;
&lt;p&gt;React Native relies heavily on a core C++ layout engine called Yoga under the hood. When Apple updates Xcode, they also update the underlying compiler tools (Clang) and shift the default C++ language dialect standard forward (for example, enforcing strict C++20 or C++23 rules).&lt;/p&gt;
&lt;p&gt;If an older version of a library in your &lt;code&gt;node_modules&lt;/code&gt; uses a syntax decoration that the new compiler now considers illegal or deprecated, the native compilation step fails instantly. Because most mobile developers do not actively write pure C++, looking at a raw Clang compiler failure feels like looking at alien code.&lt;/p&gt;
&lt;p&gt;Nuking &lt;code&gt;DerivedData&lt;/code&gt; or running &lt;code&gt;pod install&lt;/code&gt; twenty times will not change the fact that the compiler rules have changed.&lt;/p&gt;
&lt;h2 id=&quot;the-solution-force-the-c-language-standard-backwards&quot;&gt;The Solution: Force the C++ Language Standard Backwards&lt;/h2&gt;
&lt;p&gt;Instead of attempting to manually modify source code deep inside your native &lt;code&gt;node_modules&lt;/code&gt; dependencies (which will just get overwritten the next time you install packages), you can use a CocoaPods post-install hook to force the compiler to accept the specific language dialect your dependencies need to pass the check.&lt;/p&gt;
&lt;p&gt;Open the &lt;code&gt;Podfile&lt;/code&gt; located inside your project's native &lt;code&gt;ios&lt;/code&gt; directory. Scroll down to the bottom where your &lt;code&gt;post_install&lt;/code&gt; loop lives, and inject this compiler flag override block:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ruby&quot;&gt;post_install do |installer|
  installer.pods_project.targets.each do |target|
    target.build_configurations.each do |config|
      # Force the compiler to use the stable C++17 standard for all sub-dependencies
      config.build_settings['CLANG_CXX_LANGUAGE_STANDARD'] = 'c++17'
    end
  end
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;By adding this configuration script, CocoaPods will automatically modify the native build files for every single sub-dependency when you run your installation pipeline, forcing Xcode to evaluate the underlying C++ libraries under a compatible standard framework.&lt;/p&gt;
&lt;h2 id=&quot;clear-the-native-caches-and-rebuild&quot;&gt;Clear the Native Caches and Rebuild&lt;/h2&gt;
&lt;p&gt;Once the project configurations are updated, you must completely destroy the old build artifacts that were compiled under the conflicting settings, or Xcode will continue to throw the same syntax exception.&lt;/p&gt;
&lt;p&gt;Run this quick command chain in your project terminal to wipe the native cache layers and execute a clean compilation:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Clear Xcode's local compilation cache
rm -rf ~/Library/Developer/Xcode/DerivedData

# Re-evaluate the Podfile with the new compiler hook
cd ios
pod install --repo-update

# Return to root and boot the iOS simulator
cd ..
npx react-native run-ios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;As soon as the native build pipelines evaluate your sub-dependencies through the consistent compiler standard, the cryptic layout exceptions will vanish and your bundle will build cleanly.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19813/how-to-fix-xcode-c-compiler-errors-in-react-native-yoga-glog-constexpr</guid>
<pubDate>Sat, 06 Jun 2026 09:39:12 +0000</pubDate>
</item>
<item>
<title>How to Fix Gradle Error: Unsupported class file major version (React Native &amp; Expo)</title>
<link>https://coderlegion.com/19548/how-to-fix-gradle-error-unsupported-class-file-major-version-react-native-expo</link>
<description>&lt;p&gt;If your React Native or Expo Android compilation suddenly crashes with a massive terminal stack trace pointing to an &quot;unsupported class file major version&quot; or claims a class file has the wrong version (e.g., &lt;code&gt;wrong version 65.0, should be 61.0&lt;/code&gt;), your environment is caught in a silent version mismatch.&lt;/p&gt;
&lt;p&gt;This error almost always triggers right after you upgrade your Expo SDK, bump your React Native core version, or update Android Studio. &lt;/p&gt;
&lt;p&gt;Here is exactly why this happens, how to decode the cryptic internal version numbers, and how to get your build compiling cleanly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-did-this-happen-suddenly-&quot;&gt;Why Did This Happen Suddenly?&lt;/h3&gt;
&lt;p&gt;When React Native or Expo upgrades their framework architecture, they also upgrade the required version of the &lt;strong&gt;Android Gradle Plugin (AGP)&lt;/strong&gt; inside your project. &lt;/p&gt;
&lt;p&gt;Each version of AGP requires a strict minimum version of the Java Development Kit (JDK) to run the compilation pipeline. If your system's global terminal runtime environment or your IDE is still pointing to an older Java runtime, the build engine throws an immediate exception during the semantic analysis phase.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-version-decoder-cheat-sheet&quot;&gt;The Version Decoder Cheat Sheet&lt;/h3&gt;
&lt;p&gt;Java’s compiler doesn't map version numbers logically to their public marketing names. When the Gradle stack trace throws a decimal number at you, use this index to figure out what version your code is demanding:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 66.0&lt;/strong&gt; = Requires Java 22&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 65.0&lt;/strong&gt; = Requires Java 21 (Common in newer 2025/2026 native modules)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 61.0&lt;/strong&gt; = Requires Java 17 (The baseline standard for modern React Native)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Class file major version 55.0&lt;/strong&gt; = Requires Java 11 (Legacy)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If your terminal says &lt;code&gt;wrong version 65.0, should be 61.0&lt;/code&gt;, your build script encountered a dependency compiled for JDK 21, but your system is executing the build toolchain using JDK 17.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-1-find-the-exact-path-to-your-target-jdk&quot;&gt;Step 1: Find the Exact Path to Your Target JDK&lt;/h3&gt;
&lt;p&gt;Before making adjustments, you need to locate where the correct JDK version is actually installed on your machine. &lt;/p&gt;
&lt;h4 id=&quot;on-macos&quot;&gt;On macOS&lt;/h4&gt;
&lt;p&gt;If you use Homebrew or standard installers, your JDK runtimes are stored under the virtual machine path. You can list all installed environments by running:&lt;br&gt;
&lt;code&gt;/usr/libexec/java_home -V&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Copy the path to the required version. It will look similar to this:&lt;br&gt;
&lt;code&gt;/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home&lt;/code&gt;&lt;/p&gt;
&lt;h4 id=&quot;on-windows-1&quot;&gt;On Windows&lt;/h4&gt;
&lt;p&gt;By default, standard Java installers or Android Studio bundle their JDK environments inside program directories. Check these common locations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C:\Program Files\Java\jdk-17\&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;C:\Program Files\Android\Android Studio\jbr\&lt;/code&gt; (Android Studio's embedded Java runtime)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-2-apply-the-explicit-project-override-recommended-&quot;&gt;Step 2: Apply the Explicit Project Override (Recommended)&lt;/h3&gt;
&lt;p&gt;Instead of fighting global system environment variables that might break older projects on your machine, you can force this specific React Native project to use the correct runtime directory.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to your project's native android folder: &lt;code&gt;cd android&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Open the &lt;code&gt;gradle.properties&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Add the following property at the bottom, supplying the absolute path you copied in Step 1:&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;org.gradle.java.home=/Insert/Your/Actual/Target/JDK/Path/Here&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Note: Windows users must escape backslashes in this file, format it like: &lt;code&gt;C:\\Program Files\\Java\\jdk-17&lt;/code&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-3-align-android-studio-with-your-terminal&quot;&gt;Step 3: Align Android Studio with Your Terminal&lt;/h3&gt;
&lt;p&gt;A massive source of frustration is when the project builds fine in the terminal via CLI, but crashes the second you open it in Android Studio (or vice versa). You must align the IDE's build path:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your project folder inside &lt;strong&gt;Android Studio&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;strong&gt;Settings&lt;/strong&gt; (or &lt;strong&gt;Preferences&lt;/strong&gt; on macOS) &amp;gt; &lt;strong&gt;Build, Execution, Deployment&lt;/strong&gt; &amp;gt; &lt;strong&gt;Build Tools&lt;/strong&gt; &amp;gt; &lt;strong&gt;Gradle&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Locate the &lt;strong&gt;Gradle JDK&lt;/strong&gt; dropdown menu.&lt;/li&gt;
&lt;li&gt;Change it from the default system runtime to match the exact version your project requires (e.g., JDK 17 or JDK 21).&lt;/li&gt;
&lt;li&gt;Click Apply and hit &lt;strong&gt;Sync Project with Gradle Files&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr&gt;
&lt;h3 id=&quot;step-4-clear-the-aggressive-build-cache&quot;&gt;Step 4: Clear the Aggressive Build Cache&lt;/h3&gt;
&lt;p&gt;Gradle caches compilation footprints deeply. If you change your Java version without clearing the cache, Gradle will try to read the old, mismatched class files and throw the same error again. &lt;/p&gt;
&lt;p&gt;Wipe the execution deck completely by running these commands in your root directory:&lt;/p&gt;
&lt;h2 id=&quot;clean-the-native-android-directories&quot;&gt;Clean the native android directories&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;return-to-root-and-clear-bundler-caches&quot;&gt;Return to root and clear bundler caches&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd ..
npx react-native start --clear
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the development server reboots and paths match up seamlessly across your terminal, properties file, and IDE, run your run command (npx react-native run-android or npx expo run:android) and the version mismatch error will be gone.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19548/how-to-fix-gradle-error-unsupported-class-file-major-version-react-native-expo</guid>
<pubDate>Wed, 03 Jun 2026 08:49:58 +0000</pubDate>
</item>
<item>
<title>Fixing Gradle TLS handshake_failure on Maven Central (Android Builds)</title>
<link>https://coderlegion.com/19512/fixing-gradle-tls-handshakefailure-on-maven-central-android-builds</link>
<description>&lt;p&gt;If your Android native build is constantly failing with a TLS handshake error when trying to download dependencies from Maven Central, the root cause might be an invisible environment variable polluting your Java settings. &lt;/p&gt;
&lt;p&gt;The most frustrating part of this error is that your local internet connection is usually completely fine. &lt;/p&gt;
&lt;h3 id=&quot;the-quick-diagnosis&quot;&gt;The Quick Diagnosis&lt;/h3&gt;
&lt;p&gt;Open your terminal and test the connection directly to the Maven repository using curl:&lt;/p&gt;
&lt;p&gt;curl -I &lt;a rel=&quot;nofollow&quot; href=&quot;https://repo1.maven.org/maven2/&quot;&gt;https://repo1.maven.org/maven2/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If curl connects successfully and returns an HTTP 200, but your Gradle build still throws a &lt;code&gt;Received fatal alert: handshake_failure&lt;/code&gt;, your network is not the problem. Gradle itself is failing to establish the secure line.&lt;/p&gt;
&lt;h3 id=&quot;the-root-cause-1&quot;&gt;The Root Cause&lt;/h3&gt;
&lt;p&gt;An environment variable called &lt;code&gt;GRADLE_OPTS&lt;/code&gt; is likely injecting broken SSL or JSSE flags into your build process. Specifically, flags like &lt;code&gt;-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT&lt;/code&gt; force Gradle to look at system-specific certificate stores that conflict with standard Java behavior, breaking the handshake completely.&lt;/p&gt;
&lt;h3 id=&quot;the-fix-5&quot;&gt;The Fix&lt;/h3&gt;
&lt;p&gt;To resolve this permanently, you need to wipe out the polluted configuration so Gradle can fall back to its clean JDK defaults.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open your system &lt;strong&gt;Environment Variables&lt;/strong&gt; settings.&lt;/li&gt;
&lt;li&gt;Check both the &lt;strong&gt;User variables&lt;/strong&gt; and the &lt;strong&gt;System/Machine variables&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Look for &lt;code&gt;GRADLE_OPTS&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;Delete the variable entirely from both locations.&lt;/li&gt;
&lt;li&gt;Close your IDE, restart your terminal, and run your build again.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once those injected flags are gone, Gradle will use standard JDK defaults, and your dependency downloads will start succeeding immediately. &lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;I've been spending a lot of my spare time building out a diagnostic tool over at &lt;a rel=&quot;nofollow&quot; href=&quot;https://fix-my-error.vercel.app&quot;&gt;FixMyError&lt;/a&gt; to map out environment bugs like this automatically. I just put a live, free sandbox preview on the homepage if you want to see how the engine breaks down terminal stack traces before you ever make an account.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19512/fixing-gradle-tls-handshakefailure-on-maven-central-android-builds</guid>
<pubDate>Tue, 02 Jun 2026 15:56:09 +0000</pubDate>
</item>
<item>
<title>How to fix 'RNGestureHandlerModule could not be found' in React Native &amp; Expo</title>
<link>https://coderlegion.com/19360/how-to-fix-rngesturehandlermodule-could-not-be-found-in-react-native-expo</link>
<description>&lt;p&gt;Ever run a fresh compilation on a React Native project, change absolutely nothing, and watch your simulator immediately crash with this terminal masterpiece?&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-text&quot;&gt;ERROR Invariant Violation: TurboModuleRegistry.getEnforcing(...): 'RNGestureHandlerModule' could not be found. Verify that your native modules are linked correctly.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It usually happens right after initializing a navigation library or an interactive UI package that relies on &lt;code&gt;react-native-gesture-handler&lt;/code&gt; under the hood. &lt;/p&gt;
&lt;p&gt;If you are stuck looking at this stack trace right now, here is exactly why it is broken and how to resolve it in two minutes.&lt;/p&gt;
&lt;h4 id=&quot;the-root-cause-javascript-is-floating-alone&quot;&gt;The Root Cause: JavaScript Is Floating Alone&lt;/h4&gt;
&lt;p&gt;When you run standard web frameworks, importing a package updates your node dependency tree and just works. In React Native, complex packages have two distinct layers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;The JavaScript API layer&lt;/strong&gt; (the code you write in your editor).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The Native Architecture layer&lt;/strong&gt; (the actual Swift/Objective-C or Java/Kotlin binaries compiled into the iOS/Android app wrappers).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This error occurs because your Metro bundler successfully loaded the new JavaScript components, but your local native build directory has no idea those new binaries exist yet. The framework is trying to call native routines that simply weren't compiled during your last native build cycle.&lt;/p&gt;
&lt;h4 id=&quot;how-to-fix-it-2&quot;&gt;How to Fix It&lt;/h4&gt;
&lt;h5 id=&quot;1-for-expo-development-builds-most-common-&quot;&gt;1. For Expo Development Builds (Most Common)&lt;/h5&gt;
&lt;p&gt;If you are using Expo and running prebuilds, simply running a standard terminal refresh won't pass the native binary threshold. You need to trigger a full native binary compilation sequence to force Expo to link the missing native modules folder structure into your binary targets:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# For Android simulators/devices:
npx expo run:android

# For iOS simulators/devices:
npx expo run:ios
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-for-bare-react-native-projects&quot;&gt;2. For Bare React Native Projects&lt;/h5&gt;
&lt;p&gt;If you are managing your own native folders directly, you need to clear your build cache and explicitly map the cocoapods dependency layer for iOS:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# Clear out your local build artifacts
watchman watch-del-all &amp;amp;&amp;amp; rm -rf node_modules &amp;amp;&amp;amp; npm install

# Re-link native iOS libraries
cd ios &amp;amp;&amp;amp; pod install &amp;amp;&amp;amp; cd ..

# Rebuild your active runtime binary
npx react-native run-ios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once the compilation pipeline finishes creating the new application layout, the native module registration bridge hooks up properly, and your navigation screens will load without a hitch.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Hopefully, this saves you from losing an hour down a stack trace rabbit hole today. &lt;/p&gt;
&lt;p&gt;If you run into other cryptic Expo or React Native native errors while building, I put together a quick web diagnostic tool called &lt;a rel=&quot;nofollow&quot; href=&quot;https://fix-my-error.vercel.app&quot;&gt;FixMyError.dev&lt;/a&gt; that contextually reverse-engineers stack traces into human-readable resolutions like this one. There's a free live sandbox on the landing page if you just want to test it out next time your terminal starts throwing errors. &lt;/p&gt;
&lt;p&gt;Back to building!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/19360/how-to-fix-rngesturehandlermodule-could-not-be-found-in-react-native-expo</guid>
<pubDate>Sun, 31 May 2026 17:13:39 +0000</pubDate>
</item>
<item>
<title>How to fix &quot;Gradle build daemon disappeared unexpectedly&quot; in React Native &amp; Expo</title>
<link>https://coderlegion.com/18873/how-to-fix-gradle-build-daemon-disappeared-unexpectedly-in-react-native-expo</link>
<description>&lt;p&gt;If you’ve been building mobile apps with React Native or Expo long enough, you’ve definitely hit that brick wall where a standard debug build works perfectly, but the moment you run &lt;code&gt;eas build&lt;/code&gt; or try to compile a production AAB/APK for the Play Store, the terminal throws a generic, frustrating crash:&lt;/p&gt;
&lt;p&gt;&quot;Gradle build daemon disappeared unexpectedly (it may have been killed or may have crashed)&quot;&lt;/p&gt;
&lt;p&gt;What makes this error so frustrating is that the terminal output rarely tells you &lt;em&gt;why&lt;/em&gt; the daemon vanished. &lt;/p&gt;
&lt;h4 id=&quot;the-root-cause-out-of-memory-oom-&quot;&gt;The Root Cause: Out-of-Memory (OOM)&lt;/h4&gt;
&lt;p&gt;When you trigger a release build, the Node process responsible for bundling your JavaScript files, assets, and third-party native modules spikes heavily in memory. By default, Android's JVM (Java Virtual Machine) doesn't allocate enough heap space to handle these massive compilation steps for modern, dependency-heavy apps. &lt;/p&gt;
&lt;p&gt;When the local or CI machine hits its limit, it forcefully kills the Gradle daemon to protect system stability. &lt;/p&gt;
&lt;h4 id=&quot;the-3-step-surgical-fix&quot;&gt;The 3-Step Surgical Fix&lt;/h4&gt;
&lt;p&gt;Instead of blindly deleting your &lt;code&gt;.gradle&lt;/code&gt; cache or reinstalling Android Studio, you can resolve this permanently by updating your configuration parameters.&lt;/p&gt;
&lt;h5 id=&quot;1-allocate-more-heap-space&quot;&gt;1. Allocate More Heap Space&lt;/h5&gt;
&lt;p&gt;Navigate to your project's &lt;code&gt;android/gradle.properties&lt;/code&gt; file and look for or append the &lt;code&gt;org.gradle.jvmargs&lt;/code&gt; line. Increase the maximum heap size (&lt;code&gt;-Xmx&lt;/code&gt;) to at least 4GB or 8GB depending on your app size:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;org.gradle.daemon=true
org.gradle.jvmargs=-Xmx4g -XX:MaxPermSize=2048m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;2-configure-eas-build-if-using-expo-&quot;&gt;2. Configure EAS Build (If using Expo)&lt;/h5&gt;
&lt;p&gt;If you are running your builds via Expo Application Services (EAS), you need to tell the remote builders to utilize a larger resource class. Open your &lt;code&gt;eas.json&lt;/code&gt; file and specify a larger builder instance under your production profile:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;{
  &quot;build&quot;: {
    &quot;production&quot;: {
      &quot;android&quot;: {
        &quot;resourceClass&quot;: &quot;large&quot;
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h5 id=&quot;3-clear-and-reset&quot;&gt;3. Clear and Reset&lt;/h5&gt;
&lt;p&gt;Once the files are saved, kill any lingering zombie daemons and clear the old build artifacts so Gradle can start fresh:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
./gradlew cleanBuildCache
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;taming-the-rest-of-your-terminal-logs&quot;&gt;Taming the rest of your terminal logs&lt;/h4&gt;
&lt;p&gt;Native Android and iOS build logs can easily stretch to hundreds of lines of total noise, hiding the true dependency mismatch. &lt;/p&gt;
&lt;p&gt;If you are currently fighting a different native module error or a breaking CocoaPods mismatch, I built a free developer utility called FixMyError that parses raw terminal dumps instantly. It strips out the conversational AI fluff and gives you the exact surgical terminal command or config change needed to unblock your build layout in 3 seconds.&lt;/p&gt;
&lt;p&gt;Give it a spin if you are currently stuck: &lt;a rel=&quot;nofollow&quot; href=&quot;https://fix-my-error.vercel.app&quot;&gt;Try FixMyError Free&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Let me know in the comments if increasing the JVM heap size successfully unblocked your production build!&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/18873/how-to-fix-gradle-build-daemon-disappeared-unexpectedly-in-react-native-expo</guid>
<pubDate>Mon, 25 May 2026 10:18:22 +0000</pubDate>
</item>
<item>
<title>Cross-Platform Music App Development Using Flutter and React Native</title>
<link>https://coderlegion.com/17799/cross-platform-music-app-development-using-flutter-and-react-native</link>
<description>&lt;h2 id=&quot;quick-overview&quot;&gt;Quick Overview&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Flutter (Dart) and React Native (JavaScript) are the top frameworks&lt;br&gt;
for launching music apps on iOS and Android from a single codebase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Both support essential music features, such as streaming, background &lt;br&gt;
playback, waveform visualization, and media controls, through &lt;br&gt;
well-maintained plugin ecosystems.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Audio performance, low latency, and real-time processing are the&lt;br&gt;
main technical challenges in cross-platform music development.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The right framework depends on your team's skills, the app's&lt;br&gt;
complexity, and performance requirements; there is no&lt;br&gt;
one-size-fits-all solution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Choosing a cross-platform approach can reduce development time and&lt;br&gt;
costs by 30 to 50 percent compared to building separate native iOS&lt;br&gt;
and Android apps.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You have a great idea for a music app. It could be a tool for collaborative playlists, a practice companion for musicians, or a complete streaming experience. You've done your research and have a development plan. Then reality hits: do you build it for iOS first, Android, or both at the same time? If you choose both, does that mean you need two separate teams, two separate codebases, and twice the budget? &lt;/p&gt;
&lt;p&gt;This is the exact problem that has led developers to cross-platform frameworks. Right now, Flutter and React Native are the two main players in this field. This article explains how each framework approaches music app development, the trade-offs involved, and how to make the best choice for your project.&lt;/p&gt;
&lt;h2 id=&quot;why-cross-platform-is-a-smart-choice-for-music-apps&quot;&gt;Why Cross-Platform Is a Smart Choice for Music Apps&lt;/h2&gt;
&lt;p&gt;Before exploring frameworks, note that cross-platform development offers benefits for complex music apps involving real-time audio, background playback, hardware controls, Bluetooth switching, and low-latency features. While native development historically excels here, the gap with cross-platform has narrowed. &lt;/p&gt;
&lt;p&gt;Platforms like Flutter and React Native have developed to the point where most music app experiences feel just like native ones in everyday use. And for teams working with &lt;a rel=&quot;nofollow&quot; href=&quot;https://wpwebinfotech.com/music/&quot;&gt;music app development services&lt;/a&gt;, the ability to ship one codebase to two platforms without sacrificing quality is a genuine competitive advantage.&lt;/p&gt;
&lt;p&gt;The economics are clear: one cross-platform team costs less than two native teams, with streamlined maintenance and simultaneous feature releases. For startups and mid-sized companies, this advantage can determine a project's viability. &lt;/p&gt;
&lt;h2 id=&quot;flutter-in-music-app-development-essential-insights&quot;&gt;Flutter in Music App Development: Essential Insights&lt;/h2&gt;
&lt;p&gt;Flutter, supported by Google, uses Dart and the Skia rendering engine, now replaced by Impeller. Instead of relying on the host platform's UI components, Flutter renders every pixel itself. This approach benefits music apps.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Visual consistency:&lt;/strong&gt; Waveform displays, equalizer animations, album&lt;br&gt;
art carousels, and custom playback controls look the same on iOS and&lt;br&gt;
Android, eliminating UI rendering frustration across platforms.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audio handling:&lt;/strong&gt; Since Flutter lacks a built-in audio engine, you'll&lt;br&gt;
need packages like just_audio, audio_service, and flutter_sound.&lt;br&gt;
just_audio supports streaming, gapless playback, pitch shifting, and&lt;br&gt;
audio effects, suitable for most music apps. audio_service manages&lt;br&gt;
background playback and media notifications, essential for any music&lt;br&gt;
app.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Performance:&lt;/strong&gt; Flutter's compiled Dart code and rendering pipeline&lt;br&gt;
ensure smooth animations and a responsive UI, but can lag during&lt;br&gt;
heavy platform-channel communication. Handling real-time native audio&lt;br&gt;
processing may cause delays due to Dart-native exchange. &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ideal use cases:&lt;/strong&gt; They include streaming, music discovery, podcast,&lt;br&gt;
music education, and DJ mixing apps that don't need very low-latency&lt;br&gt;
audio (under 10 ms).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;react-native-for-music-app-development-essential-insights&quot;&gt;React Native for Music App Development: Essential Insights&lt;/h2&gt;
&lt;p&gt;React Native, maintained by Meta, uses JavaScript or TypeScript and connects to native components. The newer &quot;New Architecture&quot; adopts JSI instead of the old bridge, significantly narrowing the performance gap with native development.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The JavaScript ecosystem:&lt;/strong&gt; If your team already works with React or&lt;br&gt;
JavaScript, the learning curve is much shorter. For &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.cnvrtool.com/blog/react-native-app-development/&quot;&gt;React Native app&lt;br&gt;
development&lt;/a&gt;, the large npm ecosystem means there are libraries for&lt;br&gt;
almost everything a music app might need, from audio processing to&lt;br&gt;
music-theory tools.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audio libraries:&lt;/strong&gt; react-native-track-player is the preferred tool for&lt;br&gt;
streaming and queue management, supporting background playback, lock&lt;br&gt;
screen controls, and CarPlay/Android Auto. react-native-sound is good&lt;br&gt;
for basic audio, while react-native-audio-toolkit manages recording.&lt;br&gt;
For advanced DSP, native modules are used.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; React Native uses native components, so your app adopts&lt;br&gt;
each platform's look and feel. This works well for standard UI&lt;br&gt;
patterns but can be inconsistent for custom designs.   &lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ideal use cases:&lt;/strong&gt; It includes music players, streaming services,&lt;br&gt;
social    platforms, learning apps, and apps where React or&lt;br&gt;
JavaScript skills    are important.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;flutter-vs-react-native-for-music-apps&quot;&gt;Flutter vs. React Native for Music Apps&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=14613520242586826734&quot; alt=&quot;&quot;&gt;&lt;br&gt;
&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=16029373426471469384&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;h2 id=&quot;key-technical-considerations-specific-to-music-apps&quot;&gt;Key Technical Considerations Specific to Music Apps&lt;/h2&gt;
&lt;p&gt;Regardless of which framework you choose, certain challenges are common in cross-platform music development.  &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Background audio playback:&lt;/strong&gt; Both frameworks handle this with dedicated&lt;br&gt;
packages: audio_service for Flutter and react-native-track-player for&lt;br&gt;
React Native, but require careful configuration of platform-specific&lt;br&gt;
entitlements and permissions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Low-latency audio:&lt;/strong&gt; For apps requiring latency below ~20ms, such as&lt;br&gt;
real-time tuners or live tools, cross-platform frameworks may reach&lt;br&gt;
their limits, and custom native modules might be necessary regardless&lt;br&gt;
of the framework.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audio streaming and buffering:&lt;/strong&gt; Managing an app's state is crucial.&lt;br&gt;
Music streaming apps must handle network issues, adaptive bitrate,&lt;br&gt;
and pre-buffering. Both frameworks support this, but implementation&lt;br&gt;
is complex.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Waveform visualization:&lt;/strong&gt; It uses significant CPU resources. &lt;a rel=&quot;nofollow&quot; href=&quot;https://verygood.ventures/blog/mastering-custompainter-in-flutter-from-svgs-to-racetracks/&quot;&gt;Flutter&lt;br&gt;
CustomPainter&lt;/a&gt; and React Native's canvas options, via&lt;br&gt;
react-native-skia or WebGL, handle this. Optimization is vital,&lt;br&gt;
especially on mid-range Android devices.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Choosing between Flutter and React Native isn't about picking the better tool but the right one for your team, timeline, and features. Flutter offers visual consistency and performance predictability, while React Native excels in ecosystem familiarity and native platform integration. Both can produce high-quality cross-platform music apps indistinguishable from native ones.&lt;/p&gt;
&lt;p&gt;Understanding your app's technical needs early is crucial. If real-time audio is essential, evaluate how frameworks handle native modules. For streaming or discovery, either framework works, and cross-platform saves are substantial. The best framework is the one your team confidently uses. Choose it, build effectively, and let the music speak.&lt;/p&gt;
&lt;h2 id=&quot;faqs-1&quot;&gt;FAQs&lt;/h2&gt;
&lt;h3 id=&quot;1-can-flutter-or-react-native-handle-real-time-audio-processing-for-music-apps-&quot;&gt;1. Can Flutter or React Native handle real-time audio processing for music apps?&lt;/h3&gt;
&lt;p&gt;Both handle standard audio tasks well with plugins, but lack out-of-the-box ultra-low-latency (under 10ms) support. Features like live tuning or MIDI require native code in Swift or Kotlin. Still, both frameworks can stream and play without native tweaks.  &lt;/p&gt;
&lt;h3 id=&quot;2-which-is-better-for-a-music-streaming-app-flutter-or-react-native-&quot;&gt;2. Which is better for a music streaming app, Flutter or React Native?&lt;/h3&gt;
&lt;p&gt;Both work well. Flutter provides consistent UI and smooth animations, while React Native's react-native-track-player offers a stronger streaming ecosystem with CarPlay and Android Auto support. React Native teams ship faster; Flutter users may prioritize visual appeal.  &lt;/p&gt;
&lt;h3 id=&quot;3-how-do-cross-platform-music-apps-handle-background-playback-&quot;&gt;3. How do cross-platform music apps handle background playback?&lt;/h3&gt;
&lt;p&gt;Both frameworks handle background audio, lock screen controls, and media notifications via dedicated packages: audio_service in Flutter and react-native-track-player in React Native. You must manually configure platform permissions and test on real devices, as emulators often poorly handle background audio.  &lt;/p&gt;
&lt;h3 id=&quot;4-is-flutter-or-react-native-faster-to-develop-a-music-app-with-&quot;&gt;4. Is Flutter or React Native faster to develop a music app with?&lt;/h3&gt;
&lt;p&gt;Your team's expertise influences the choice. JavaScript developers may prefer React Native for speed, while Flutter's clear structure eases Dart's learning curve and scalability. Both frameworks can cut development time by 30-50% versus native iOS and Android.  &lt;/p&gt;
&lt;h3 id=&quot;5-what-are-the-main-limitations-of-cross-platform-frameworks-for-music-apps-&quot;&gt;5. What are the main limitations of cross-platform frameworks for music apps?&lt;/h3&gt;
&lt;p&gt;Main limitations include low-latency audio, hardware integration, and delayed access to new APIs. Complex audio features often need native modules. But for most consumer music apps like streaming, playback, and visualization, these issues are generally not significant.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/17799/cross-platform-music-app-development-using-flutter-and-react-native</guid>
<pubDate>Thu, 14 May 2026 07:43:33 +0000</pubDate>
</item>
<item>
<title>5 React Native Errors That Cost Me Hours (And How I Finally Fixed Them)</title>
<link>https://coderlegion.com/16986/5-react-native-errors-that-cost-me-hours-and-how-i-finally-fixed-them</link>
<description>&lt;h2 id=&quot;5-react-native-errors-that-cost-me-hours-and-how-i-finally-fixed-them-&quot;&gt;5 React Native Errors That Cost Me Hours (And How I Finally Fixed Them)&lt;/h2&gt;
&lt;p&gt;When I started building my React Native app, I thought the hard part would be designing the app itself.&lt;/p&gt;
&lt;p&gt;Turns out, debugging the environment was the real boss fight.&lt;/p&gt;
&lt;p&gt;I spent hours — sometimes entire days — fighting build failures, version mismatches, Gradle issues, and errors that made absolutely no sense at first glance. Some of them looked impossible to solve unless you were already an Android engineer.&lt;/p&gt;
&lt;p&gt;The worst part is that many tutorials online either skip these issues entirely or give solutions that don’t actually work.&lt;/p&gt;
&lt;p&gt;So I decided to write the post I wish I had when I started.&lt;/p&gt;
&lt;p&gt;These are 5 real React Native errors I personally ran into while building my app, and exactly how I fixed them.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;1-gradle-tls-handshake-failure&quot;&gt;1. Gradle TLS Handshake Failure&lt;/h2&gt;
&lt;h3 id=&quot;the-error&quot;&gt;The Error&lt;/h3&gt;
&lt;p&gt;This one nearly made me quit for the day.&lt;/p&gt;
&lt;p&gt;Every time I tried building the Android app, Gradle failed while downloading dependencies from Maven Central with SSL/TLS handshake errors.&lt;/p&gt;
&lt;p&gt;The logs looked something like this:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Or sometimes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;Could not GET 'https://repo.maven.apache.org/...'
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;why-it-happened&quot;&gt;Why It Happened&lt;/h3&gt;
&lt;p&gt;At first I thought:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maven Central was down&lt;/li&gt;
&lt;li&gt;my internet was unstable&lt;/li&gt;
&lt;li&gt;Java was broken&lt;/li&gt;
&lt;li&gt;Gradle was corrupted&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;None of those were the real issue.&lt;/p&gt;
&lt;p&gt;The actual problem was an environment variable called &lt;code&gt;GRADLE_OPTS&lt;/code&gt; that was injecting custom SSL settings into Gradle.&lt;/p&gt;
&lt;p&gt;It included flags like:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;-Djavax.net.ssl.trustStoreType=WINDOWS-ROOT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Curl requests worked perfectly, but Gradle kept failing because its SSL configuration was being overridden.&lt;/p&gt;
&lt;h3 id=&quot;how-i-fixed-it&quot;&gt;How I Fixed It&lt;/h3&gt;
&lt;p&gt;I removed the &lt;code&gt;GRADLE_OPTS&lt;/code&gt; environment variable completely from both:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User Environment Variables&lt;/li&gt;
&lt;li&gt;System Environment Variables&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Then I restarted the terminal and rebuilt the project.&lt;/p&gt;
&lt;p&gt;Immediately after that, Gradle downloads started working again.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;Sometimes the problem is not React Native itself.&lt;/p&gt;
&lt;p&gt;Sometimes the environment around it is sabotaging the build process.&lt;/p&gt;
&lt;p&gt;And when debugging network-related Gradle errors, always compare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;browser behavior&lt;/li&gt;
&lt;li&gt;curl behavior&lt;/li&gt;
&lt;li&gt;Gradle behavior&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If only Gradle fails, something in the Java/Gradle SSL configuration is probably interfering.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;2-expo-and-react-native-version-mismatch&quot;&gt;2. Expo and React Native Version Mismatch&lt;/h2&gt;
&lt;h3 id=&quot;the-error-1&quot;&gt;The Error&lt;/h3&gt;
&lt;p&gt;This one caused random crashes, dependency warnings, and builds failing for reasons that seemed unrelated.&lt;/p&gt;
&lt;p&gt;I had packages that technically installed correctly, but internally they were incompatible with my Expo SDK version.&lt;/p&gt;
&lt;h3 id=&quot;why-it-happened-1&quot;&gt;Why It Happened&lt;/h3&gt;
&lt;p&gt;React Native ecosystems are extremely version-sensitive.&lt;/p&gt;
&lt;p&gt;You cannot always:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;install the latest package&lt;/li&gt;
&lt;li&gt;upgrade random dependencies&lt;/li&gt;
&lt;li&gt;mix versions freely&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expo SDKs expect very specific versions of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Native&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Expo packages&lt;/li&gt;
&lt;li&gt;Gradle plugins&lt;/li&gt;
&lt;li&gt;Android tooling&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even a small mismatch can create chaos.&lt;/p&gt;
&lt;h3 id=&quot;how-i-fixed-it-1&quot;&gt;How I Fixed It&lt;/h3&gt;
&lt;p&gt;I stopped manually installing random versions and aligned everything with the Expo SDK requirements.&lt;/p&gt;
&lt;p&gt;I rebuilt the Android folder using the correct setup and standardized the project around:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Native 0.80.2&lt;/li&gt;
&lt;li&gt;Expo SDK 53&lt;/li&gt;
&lt;li&gt;AGP 8.6.1&lt;/li&gt;
&lt;li&gt;Kotlin 2.0.21&lt;/li&gt;
&lt;li&gt;Gradle 8.13&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After everything matched properly, the project became dramatically more stable.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-1&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;In React Native development, version consistency matters more than having the newest package.&lt;/p&gt;
&lt;p&gt;A stable stack beats a cutting-edge stack every single time.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;3-android-build-files-breaking-everything&quot;&gt;3. Android Build Files Breaking Everything&lt;/h2&gt;
&lt;h3 id=&quot;the-error-2&quot;&gt;The Error&lt;/h3&gt;
&lt;p&gt;At one point, Android builds completely stopped working because of Gradle configuration problems.&lt;/p&gt;
&lt;p&gt;The project would either:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;fail immediately&lt;/li&gt;
&lt;li&gt;fail during dependency resolution&lt;/li&gt;
&lt;li&gt;fail during configuration phase&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And the logs were massive.&lt;/p&gt;
&lt;h3 id=&quot;why-it-happened-2&quot;&gt;Why It Happened&lt;/h3&gt;
&lt;p&gt;React Native Android builds rely on multiple interconnected files:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;settings.gradle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;build.gradle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gradle.properties&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;app-level Gradle configs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;One incorrect line can break the entire build system.&lt;/p&gt;
&lt;p&gt;I also learned that newer React Native versions have different setup requirements compared to older tutorials online.&lt;/p&gt;
&lt;p&gt;Following outdated tutorials made things worse.&lt;/p&gt;
&lt;h3 id=&quot;how-i-fixed-it-2&quot;&gt;How I Fixed It&lt;/h3&gt;
&lt;p&gt;I rebuilt the Android configuration using the proper modern structure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;correct &lt;code&gt;includeBuild&lt;/code&gt; setup&lt;/li&gt;
&lt;li&gt;Expo autolinking configuration&lt;/li&gt;
&lt;li&gt;AndroidX enabled&lt;/li&gt;
&lt;li&gt;Hermes enabled&lt;/li&gt;
&lt;li&gt;New Architecture configuration aligned correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also stopped randomly changing Gradle files without understanding what they did.&lt;/p&gt;
&lt;p&gt;That alone saved me future debugging time.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-2&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;When debugging Android builds:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;avoid panic-editing files&lt;/li&gt;
&lt;li&gt;change one thing at a time&lt;/li&gt;
&lt;li&gt;keep backups of working configs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And most importantly:&lt;br&gt;
older React Native tutorials can become outdated very quickly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;4-metro-bundler-cache-weirdness&quot;&gt;4. Metro Bundler Cache Weirdness&lt;/h2&gt;
&lt;h3 id=&quot;the-error-3&quot;&gt;The Error&lt;/h3&gt;
&lt;p&gt;Sometimes the app behaved as if my code changes didn’t exist.&lt;/p&gt;
&lt;p&gt;I would:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;modify files&lt;/li&gt;
&lt;li&gt;save changes&lt;/li&gt;
&lt;li&gt;rebuild the app&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;…and somehow the old behavior remained.&lt;/p&gt;
&lt;p&gt;At first I genuinely thought I was losing my mind.&lt;/p&gt;
&lt;h3 id=&quot;why-it-happened-3&quot;&gt;Why It Happened&lt;/h3&gt;
&lt;p&gt;Metro bundler caching can occasionally become inconsistent, especially after:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;dependency changes&lt;/li&gt;
&lt;li&gt;package upgrades&lt;/li&gt;
&lt;li&gt;Android rebuilds&lt;/li&gt;
&lt;li&gt;native configuration updates&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The cache can preserve outdated state even after rebuilding.&lt;/p&gt;
&lt;h3 id=&quot;how-i-fixed-it-3&quot;&gt;How I Fixed It&lt;/h3&gt;
&lt;p&gt;I cleared everything:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Metro cache&lt;/li&gt;
&lt;li&gt;node_modules&lt;/li&gt;
&lt;li&gt;Gradle cache&lt;/li&gt;
&lt;li&gt;build folders&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Commands like these became lifesavers:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx expo start --clear
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And sometimes:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After cleaning the environment properly, the app started reflecting changes correctly again.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-3&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;Never underestimate caching problems.&lt;/p&gt;
&lt;p&gt;If behavior makes absolutely no sense, clear the caches before wasting hours debugging imaginary problems.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;5-the-works-on-one-device-but-crashes-on-another-problem&quot;&gt;5. The “Works on One Device but Crashes on Another” Problem&lt;/h2&gt;
&lt;h3 id=&quot;the-error-4&quot;&gt;The Error&lt;/h3&gt;
&lt;p&gt;This one was incredibly frustrating because the app seemed completely fine at first.&lt;/p&gt;
&lt;p&gt;It worked on one device.&lt;br&gt;
Then suddenly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;crashed on another device&lt;/li&gt;
&lt;li&gt;failed on Android builds&lt;/li&gt;
&lt;li&gt;behaved differently after rebuilding&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sometimes the exact same code would work one day and fail the next after changing dependencies.&lt;/p&gt;
&lt;h3 id=&quot;why-it-happened-4&quot;&gt;Why It Happened&lt;/h3&gt;
&lt;p&gt;The real issue was inconsistency between:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gradle versions&lt;/li&gt;
&lt;li&gt;Android Gradle Plugin versions&lt;/li&gt;
&lt;li&gt;Kotlin versions&lt;/li&gt;
&lt;li&gt;React Native requirements&lt;/li&gt;
&lt;li&gt;Expo SDK compatibility&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;At one point, parts of the Android environment were aligned while others were outdated or incompatible.&lt;/p&gt;
&lt;p&gt;React Native Android builds are surprisingly sensitive to tooling mismatches.&lt;/p&gt;
&lt;p&gt;Even if the app compiles, hidden incompatibilities can still create unstable behavior.&lt;/p&gt;
&lt;h3 id=&quot;how-i-fixed-it-4&quot;&gt;How I Fixed It&lt;/h3&gt;
&lt;p&gt;I standardized the entire Android stack instead of fixing things one-by-one.&lt;/p&gt;
&lt;p&gt;I rebuilt the project around a stable configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React Native 0.80.2&lt;/li&gt;
&lt;li&gt;Expo SDK 53&lt;/li&gt;
&lt;li&gt;AGP 8.6.1&lt;/li&gt;
&lt;li&gt;Kotlin 2.0.21&lt;/li&gt;
&lt;li&gt;Gradle 8.13&lt;/li&gt;
&lt;li&gt;compileSdk 35&lt;/li&gt;
&lt;li&gt;targetSdk 35&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I also cleaned and regenerated parts of the Android setup to remove leftover configuration issues from older versions.&lt;/p&gt;
&lt;p&gt;Once everything matched properly, the builds became dramatically more reliable.&lt;/p&gt;
&lt;h3 id=&quot;what-i-learned-4&quot;&gt;What I Learned&lt;/h3&gt;
&lt;p&gt;A lot of React Native debugging problems are actually ecosystem consistency problems.&lt;/p&gt;
&lt;p&gt;You can spend hours trying to patch symptoms individually when the real fix is making the entire toolchain compatible from top to bottom.&lt;/p&gt;
&lt;p&gt;Now whenever I hit strange Android issues, the first thing I check is:&lt;br&gt;
“Are all my versions actually meant to work together?”&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;final-thoughts-8&quot;&gt;Final Thoughts&lt;/h2&gt;
&lt;p&gt;Before building this app, I underestimated how much software development is actually debugging.&lt;/p&gt;
&lt;p&gt;Not writing code.&lt;br&gt;
Not designing features.&lt;/p&gt;
&lt;p&gt;Debugging.&lt;/p&gt;
&lt;p&gt;But strangely enough, this process also made me improve faster than any tutorial ever could.&lt;/p&gt;
&lt;p&gt;Every painful issue forced me to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;understand the stack better&lt;/li&gt;
&lt;li&gt;read logs more carefully&lt;/li&gt;
&lt;li&gt;stay patient under frustration&lt;/li&gt;
&lt;li&gt;stop blindly copying fixes from random forums&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;And honestly, solving these problems feels more rewarding than writing simple features.&lt;/p&gt;
&lt;p&gt;If you're currently fighting React Native errors that make no sense:&lt;br&gt;
you're definitely not alone.&lt;/p&gt;
&lt;p&gt;Sometimes one line of configuration can cost you six hours.&lt;/p&gt;
&lt;p&gt;And sometimes the fix is deleting a single environment variable.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/16986/5-react-native-errors-that-cost-me-hours-and-how-i-finally-fixed-them</guid>
<pubDate>Thu, 07 May 2026 19:35:56 +0000</pubDate>
</item>
<item>
<title>Why React Native Builds Break After Updating Dependencies (And How to Fix It)</title>
<link>https://coderlegion.com/15184/why-react-native-builds-break-after-updating-dependencies-and-how-to-fix-it</link>
<description>&lt;h3 id=&quot;why-react-native-builds-break-after-updating-dependencies-and-how-to-fix-it-&quot;&gt;Why React Native Builds Break After Updating Dependencies (And How to Fix It)&lt;/h3&gt;
&lt;p&gt;If you've ever updated your dependencies and suddenly your React Native Android build stops working… you're not alone.&lt;/p&gt;
&lt;p&gt;Everything worked before.&lt;/p&gt;
&lt;p&gt;You change a few versions, run the build again—and now you're hit with cryptic Gradle errors, build failures, or crashes.&lt;/p&gt;
&lt;p&gt;This post breaks down &lt;strong&gt;why this happens&lt;/strong&gt; and how to actually fix it.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;the-real-problem&quot;&gt;The Real Problem&lt;/h3&gt;
&lt;p&gt;React Native projects depend on a fragile ecosystem of:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gradle&lt;/li&gt;
&lt;li&gt;Android Gradle Plugin (AGP)&lt;/li&gt;
&lt;li&gt;Java (JDK)&lt;/li&gt;
&lt;li&gt;Firebase / native SDKs&lt;/li&gt;
&lt;li&gt;Third-party libraries&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you update even one dependency, you can accidentally break compatibility between them.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;common-symptoms-after-updating&quot;&gt;Common Symptoms After Updating&lt;/h3&gt;
&lt;p&gt;You might see errors like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Execution failed for task ':app:compileDebugJavaWithJavac'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Duplicate class found in modules&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Could not determine Java version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;A problem occurred configuring project ':app'&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;App works before update, fails after&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The problem is rarely obvious from the error message.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;why-this-happens&quot;&gt;Why This Happens&lt;/h3&gt;
&lt;h4 id=&quot;1-version-mismatch&quot;&gt;1. Version Mismatch&lt;/h4&gt;
&lt;p&gt;Some libraries require:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Specific Gradle versions  &lt;/li&gt;
&lt;li&gt;Specific Android Gradle Plugin versions  &lt;/li&gt;
&lt;li&gt;Specific Java versions  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If one gets upgraded and others don’t → build breaks.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-transitive-dependency-conflicts&quot;&gt;2. Transitive Dependency Conflicts&lt;/h4&gt;
&lt;p&gt;When you install or update a package, it may bring its own dependencies.&lt;/p&gt;
&lt;p&gt;Sometimes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Two libraries depend on different versions of the same module&lt;/li&gt;
&lt;li&gt;Gradle can't resolve the conflict → build failure&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-cached-corrupted-builds&quot;&gt;3. Cached / Corrupted Builds&lt;/h4&gt;
&lt;p&gt;Gradle caches aggressively.&lt;/p&gt;
&lt;p&gt;After updates:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Old compiled artifacts remain&lt;/li&gt;
&lt;li&gt;Build system gets inconsistent → weird errors&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-breaking-changes-in-libraries&quot;&gt;4. Breaking Changes in Libraries&lt;/h4&gt;
&lt;p&gt;Some updates introduce:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Removed APIs&lt;/li&gt;
&lt;li&gt;Changed configurations&lt;/li&gt;
&lt;li&gt;New requirements (e.g. minSdk, compileSdk)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-to-fix-it-step-by-step-&quot;&gt;How to Fix It (Step-by-Step)&lt;/h3&gt;
&lt;h4 id=&quot;1-clean-everything&quot;&gt;1. Clean Everything&lt;/h4&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd android
./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then delete:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;node_modules&lt;/li&gt;
&lt;li&gt;android/.gradle&lt;/li&gt;
&lt;li&gt;android/build&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Reinstall:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h4 id=&quot;2-verify-java-jdk-version&quot;&gt;2. Verify Java (JDK) Version&lt;/h4&gt;
&lt;p&gt;React Native (especially newer versions) typically requires:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;JDK 17&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Check:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;java -version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mismatch here breaks builds instantly.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;3-align-gradle-agp-versions&quot;&gt;3. Align Gradle + AGP Versions&lt;/h4&gt;
&lt;p&gt;Check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;android/build.gradle&lt;/li&gt;
&lt;li&gt;gradle-wrapper.properties&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Make sure:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Gradle version matches AGP requirements&lt;/li&gt;
&lt;li&gt;You're not mixing incompatible versions&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;4-check-compilesdk-targetsdk&quot;&gt;4. Check compileSdk / targetSdk&lt;/h4&gt;
&lt;p&gt;In android/app/build.gradle:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;compileSdkVersion 34
targetSdkVersion 34
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Some libraries require newer SDK versions.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;5-look-for-duplicate-dependencies&quot;&gt;5. Look for Duplicate Dependencies&lt;/h4&gt;
&lt;p&gt;If you see:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Duplicate class found in modules
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You may need to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Exclude dependencies&lt;/li&gt;
&lt;li&gt;Align versions manually&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h4 id=&quot;6-reset-environment-variables-advanced-but-important-&quot;&gt;6. Reset Environment Variables (Advanced but Important)&lt;/h4&gt;
&lt;p&gt;One issue I ran into:&lt;/p&gt;
&lt;p&gt;A broken GRADLE_OPTS configuration caused TLS handshake failures and dependency download issues.&lt;/p&gt;
&lt;p&gt;Fix:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove custom GRADLE_OPTS&lt;/li&gt;
&lt;li&gt;Let Gradle use default JDK settings&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is rarely mentioned—but can completely break builds.&lt;/p&gt;
&lt;hr&gt;
&lt;h4 id=&quot;7-rebuild-from-scratch&quot;&gt;7. Rebuild from Scratch&lt;/h4&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx react-native run-android
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;key-insight&quot;&gt;Key Insight&lt;/h3&gt;
&lt;p&gt;React Native builds don’t usually break randomly.&lt;/p&gt;
&lt;p&gt;They break because: &lt;strong&gt;your environment and dependency versions are no longer aligned&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-advice&quot;&gt;Final Advice&lt;/h3&gt;
&lt;p&gt;When updating dependencies:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Don’t update everything at once  &lt;/li&gt;
&lt;li&gt;Upgrade incrementally  &lt;/li&gt;
&lt;li&gt;Test after each change  &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This makes debugging much easier.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;If you're dealing with a specific error after an update, feel free to share it—I’ve probably hit it before &lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/15184/why-react-native-builds-break-after-updating-dependencies-and-how-to-fix-it</guid>
<pubDate>Sun, 19 Apr 2026 16:39:23 +0000</pubDate>
</item>
<item>
<title>React Native Android Build Failed: Could Not Resolve Dependency (Real Causes)</title>
<link>https://coderlegion.com/14696/react-native-android-build-failed-could-not-resolve-dependency-real-causes</link>
<description>&lt;p&gt;React Native Android builds can fail for a lot of reasons—but one of the most common (and confusing) ones is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Could not resolve dependency&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you’ve seen this, you probably also noticed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the error message is long&lt;/li&gt;
&lt;li&gt;it looks very “Gradle-heavy”&lt;/li&gt;
&lt;li&gt;and it doesn’t clearly tell you what to fix&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here’s what this error actually means—and how to approach it without wasting hours.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;what-could-not-resolve-dependency-really-means&quot;&gt;What “Could not resolve dependency” really means&lt;/h3&gt;
&lt;p&gt;This error usually appears when Gradle tries to fetch or link a dependency and fails.&lt;/p&gt;
&lt;p&gt;But the important part is this:&lt;/p&gt;
&lt;p&gt; it’s not always a network issue&lt;/p&gt;
&lt;p&gt;Most of the time, it’s caused by something in your project setup.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-version-mismatch-most-common-cause-&quot;&gt;1. Version mismatch (most common cause)&lt;/h3&gt;
&lt;p&gt;This is the #1 reason behind this error.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a library requires a newer React Native version&lt;/li&gt;
&lt;li&gt;your Gradle or Android plugin version is outdated&lt;/li&gt;
&lt;li&gt;Kotlin version doesn’t match project setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Even a small mismatch can break dependency resolution.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-repository-configuration-issues&quot;&gt;2. Repository configuration issues&lt;/h3&gt;
&lt;p&gt;Gradle needs to know &lt;strong&gt;where to download dependencies from&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Check your &lt;code&gt;build.gradle&lt;/code&gt; files for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mavenCentral()&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;google()&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a required repository is missing, Gradle simply won’t find the dependency.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-incorrect-or-missing-dependency-declaration&quot;&gt;3. Incorrect or missing dependency declaration&lt;/h3&gt;
&lt;p&gt;Sometimes the issue is simpler than it looks:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;typo in dependency name&lt;/li&gt;
&lt;li&gt;wrong version specified&lt;/li&gt;
&lt;li&gt;dependency removed or renamed upstream&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In these cases, Gradle is doing exactly what it should—it just can’t find what you asked for.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-caching-problems&quot;&gt;4. Caching problems&lt;/h3&gt;
&lt;p&gt;Yes—sometimes it really is cache.&lt;/p&gt;
&lt;p&gt;You might have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;corrupted Gradle cache&lt;/li&gt;
&lt;li&gt;stale dependencies&lt;/li&gt;
&lt;li&gt;partial downloads&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cleaning can help here:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;But keep in mind:&lt;br&gt;
 if the issue is structural, cleaning won’t fix it&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-network-proxy-issues&quot;&gt;5. Network / proxy issues&lt;/h3&gt;
&lt;p&gt;Less common, but still possible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;blocked connections&lt;/li&gt;
&lt;li&gt;corporate proxies&lt;/li&gt;
&lt;li&gt;SSL issues&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If Gradle can’t reach the repository, dependency resolution fails.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-autolinking-side-effects&quot;&gt;6. Autolinking side effects&lt;/h3&gt;
&lt;p&gt;Sometimes this error shows up after installing a new package.&lt;/p&gt;
&lt;p&gt;What’s happening:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the package introduces native dependencies&lt;/li&gt;
&lt;li&gt;Gradle tries to resolve them&lt;/li&gt;
&lt;li&gt;something in the chain fails&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This is why the error often appears right after adding a library.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;how-to-debug-it-faster&quot;&gt;How to debug it faster&lt;/h3&gt;
&lt;p&gt;Instead of reacting to the full error message, do this:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Look for the &lt;strong&gt;exact dependency name&lt;/strong&gt; that failed&lt;/li&gt;
&lt;li&gt;Check its &lt;strong&gt;version compatibility&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Verify your &lt;strong&gt;repositories&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Connect it to the &lt;strong&gt;last change you made&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This approach is much faster than trying random fixes.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-9&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;“Could not resolve dependency” sounds like a simple error—but it can come from multiple causes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;version mismatches&lt;/li&gt;
&lt;li&gt;missing repositories&lt;/li&gt;
&lt;li&gt;incorrect dependencies&lt;/li&gt;
&lt;li&gt;caching issues&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you treat it as a category of problems instead of a single issue, debugging becomes much easier.&lt;/p&gt;
&lt;p&gt;And in most cases, the fix is simpler than it first appears.&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14696/react-native-android-build-failed-could-not-resolve-dependency-real-causes</guid>
<pubDate>Mon, 13 Apr 2026 16:06:32 +0000</pubDate>
</item>
<item>
<title>React Native Android Autolinking Issues: Common Problems and What to Check</title>
<link>https://coderlegion.com/14097/react-native-android-autolinking-issues-common-problems-and-what-to-check</link>
<description>&lt;p&gt;Autolinking in React Native is supposed to simplify native module integration—but when it breaks, it can be frustrating and time-consuming to debug.&lt;/p&gt;
&lt;p&gt;If your Android build is failing or a package isn’t being recognized, autolinking is often the root cause.&lt;/p&gt;
&lt;p&gt;Here are the most common issues and what you should check.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;1-package-not-properly-installed&quot;&gt;1. Package not properly installed&lt;/h3&gt;
&lt;p&gt;Before diving deeper, make sure the dependency is correctly installed.&lt;/p&gt;
&lt;p&gt;Check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt; includes the library&lt;/li&gt;
&lt;li&gt;&lt;code&gt;node_modules&lt;/code&gt; contains it&lt;/li&gt;
&lt;li&gt;You’ve run:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install
# or
yarn install
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If something feels off, delete and reinstall:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;rm -rf node_modules
npm install
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;2-missing-or-outdated-gradle-sync&quot;&gt;2. Missing or outdated Gradle sync&lt;/h3&gt;
&lt;p&gt;Autolinking depends on Gradle picking up native modules.&lt;/p&gt;
&lt;p&gt;Try:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;cd android
./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then rebuild the project:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native run-android
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;3-incorrect-react-native-cli-detection&quot;&gt;3. Incorrect React Native CLI detection&lt;/h3&gt;
&lt;p&gt;Sometimes the React Native CLI doesn’t properly detect the module.&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npx react-native config
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Check if your dependency appears under:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;dependencies&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;platforms.android&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If it’s missing, autolinking is not working correctly.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;4-manual-linking-leftovers&quot;&gt;4. Manual linking leftovers&lt;/h3&gt;
&lt;p&gt;If the project was upgraded or previously used manual linking, conflicts can occur.&lt;/p&gt;
&lt;p&gt;Check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MainApplication.java&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;settings.gradle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app/build.gradle&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Remove any old manual linking entries.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;5-incompatible-library-version&quot;&gt;5. Incompatible library version&lt;/h3&gt;
&lt;p&gt;Some libraries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;don’t support your React Native version&lt;/li&gt;
&lt;li&gt;require additional setup&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Always verify:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the library’s documentation&lt;/li&gt;
&lt;li&gt;compatibility with your RN version&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;6-android-specific-configuration-missing&quot;&gt;6. Android-specific configuration missing&lt;/h3&gt;
&lt;p&gt;Some packages still require manual steps.&lt;/p&gt;
&lt;p&gt;Look for instructions like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;adding permissions in &lt;code&gt;AndroidManifest.xml&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;modifying &lt;code&gt;build.gradle&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;adding custom native code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Autolinking doesn’t cover everything.&lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;7-caching-issues&quot;&gt;7. Caching issues&lt;/h3&gt;
&lt;p&gt;React Native and Gradle caching can cause stale builds.&lt;/p&gt;
&lt;p&gt;Try clearing everything:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;watchman watch-del-all
rm -rf node_modules
rm -rf android/build
rm -rf android/app/build
npm install
cd android &amp;amp;&amp;amp; ./gradlew clean
&lt;/code&gt;&lt;/pre&gt;
&lt;hr&gt;
&lt;h3 id=&quot;final-thoughts-10&quot;&gt;Final thoughts&lt;/h3&gt;
&lt;p&gt;Autolinking issues usually come down to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;installation problems&lt;/li&gt;
&lt;li&gt;Gradle not syncing correctly&lt;/li&gt;
&lt;li&gt;leftover manual configurations&lt;/li&gt;
&lt;li&gt;version incompatibilities&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of guessing, go step by step through these checks—you’ll usually find the issue faster.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;If you’re working a lot with React Native Android builds, you’ll notice most errors fall into a few recurring patterns like these.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Tutorials</category>
<guid isPermaLink="true">https://coderlegion.com/14097/react-native-android-autolinking-issues-common-problems-and-what-to-check</guid>
<pubDate>Mon, 06 Apr 2026 16:56:36 +0000</pubDate>
</item>
<item>
<title>React Native Quote Audit - USA</title>
<link>https://coderlegion.com/12312/react-native-quote-audit-usa</link>
<description>&lt;p&gt;&lt;strong&gt;A Quick Test to See if your Quote is Missing Money.&lt;/strong&gt;&lt;br&gt;
If you are building a React Native app in the USA, a quote can look fine and still be missing big items.&lt;/p&gt;
&lt;p&gt;Missing items do not stay missing.&lt;br&gt;
They show up later as extra invoices, delays, and stress.&lt;/p&gt;
&lt;p&gt;This post is a simple quote audit.&lt;br&gt;
You can use it in 10 minutes before you sign.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Download the printable checklist PDF&lt;/strong&gt;&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.budventure.technology/blog/react-native-app-development-cost-usa&quot;&gt;React Native Budget Checklist USA 2026&lt;/a&gt;&lt;br&gt;
Use it while reading the quote. Use it again during kickoff.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Section 1&lt;br&gt;
---------&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;A Quick Test&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Is your quote missing money?&lt;/strong&gt;&lt;br&gt;
Answer yes or no.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Does the quote show a clear list of what is included&lt;/li&gt;
&lt;li&gt;Does it show what is not included&lt;/li&gt;
&lt;li&gt;Does it include testing time and bug fixing time&lt;/li&gt;
&lt;li&gt;Does it include store release work for iOS and Android&lt;/li&gt;
&lt;li&gt;Does it include payment failure cases and double order prevention&lt;/li&gt;
&lt;li&gt;Does it include device testing on more than two phones&lt;/li&gt;
&lt;li&gt;Does it include who owns accounts, code, and access&lt;/li&gt;
&lt;li&gt;Does it include change request rules and pricing&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If you said no to three or more, the quote is likely missing money.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Section 2&lt;br&gt;
---------&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;The 5 line items that are usually missing from quotes&lt;/strong&gt;&lt;br&gt;
These five items are where cost and time jump.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Testing and bug fixing&lt;/strong&gt;&lt;br&gt;
Many quotes say development, but they hide testing.&lt;/p&gt;
&lt;p&gt;What this should include&lt;br&gt;
QA testing time&lt;br&gt;
Bug fixing time&lt;br&gt;
Retesting time&lt;br&gt;
Basic regression testing before release&lt;/p&gt;
&lt;p&gt;If it is missing, you will pay later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Device list and device testing&lt;/strong&gt;&lt;br&gt;
Testing on one iPhone and one Android is not real testing.&lt;/p&gt;
&lt;p&gt;What this should include&lt;br&gt;
A short device list&lt;br&gt;
At least one mid range Android phone&lt;br&gt;
A plan for bugs that happen on one device only&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Store release work&lt;/strong&gt;&lt;br&gt;
Many quotes end at build ready.&lt;br&gt;
Real work still remains.&lt;/p&gt;
&lt;p&gt;What this should include&lt;br&gt;
Signing and build setup&lt;br&gt;
Store listing details&lt;br&gt;
Screenshots&lt;br&gt;
Privacy forms&lt;br&gt;
Review notes&lt;br&gt;
Buffer time for rejection and resubmission&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Payments and failure cases&lt;/strong&gt;&lt;br&gt;
If your app has payments, the happy path is not enough.&lt;/p&gt;
&lt;p&gt;What this should include&lt;br&gt;
Payment failed handling&lt;br&gt;
Payment success but order not confirmed&lt;br&gt;
User taps pay again&lt;br&gt;
Status checks to stop duplicate orders&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Upload failure handling&lt;/strong&gt;&lt;br&gt;
If your app has uploads, failures will happen.&lt;/p&gt;
&lt;p&gt;What this should include&lt;br&gt;
Slow internet handling&lt;br&gt;
Retry steps&lt;br&gt;
Clear error messages&lt;br&gt;
Safe continue steps so users do not get stuck&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Section 3&lt;br&gt;
---------&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Questions to ask your developer before you sign&lt;/strong&gt;&lt;br&gt;
Ask these questions in a call.&lt;br&gt;
If answers are vague, expect trouble.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scope and plan&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What is out of scope for version one&lt;/li&gt;
&lt;li&gt;What are the hard screens in this app &lt;/li&gt;
&lt;li&gt;Which parts have the most risk&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;QA and devices&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What is your test device list&lt;/li&gt;
&lt;li&gt;How many rounds of QA are included&lt;/li&gt;
&lt;li&gt;Is bug fixing included or billed extra&lt;/li&gt;
&lt;li&gt;What happens when a bug only happens on one Android model&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Payments and uploads&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How will you stop duplicate orders if payment is slow&lt;/li&gt;
&lt;li&gt;How will you handle payment success but order not updated&lt;/li&gt;
&lt;li&gt;How will uploads work on slow internet&lt;/li&gt;
&lt;li&gt;What happens when users deny camera or photo access&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Release and ownership&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Who owns the Apple and Google store accounts&lt;/li&gt;
&lt;li&gt;Who owns the code repo&lt;/li&gt;
&lt;li&gt;Will you hand over all keys and access at the end&lt;/li&gt;
&lt;li&gt;How many days do you keep for store review and fixes&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Change control&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;How do you price a change request&lt;/li&gt;
&lt;li&gt;What counts as a change&lt;/li&gt;
&lt;li&gt;Who approves changes and how fast&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the team cannot answer these clearly, the quote is not ready.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Section 4&lt;br&gt;
---------&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Red flags checklist&lt;/strong&gt;&lt;br&gt;
Here are the red flags. If you see these, pause.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 1&lt;/strong&gt;&lt;br&gt;
One total number with no phases&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 2&lt;/strong&gt;&lt;br&gt;
No QA time mentioned&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 3&lt;/strong&gt;&lt;br&gt;
No device list mentioned&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 4&lt;/strong&gt;&lt;br&gt;
No store release work mentioned&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 5&lt;/strong&gt;&lt;br&gt;
Payments included but no talk about failure cases&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 6&lt;/strong&gt;&lt;br&gt;
Uploads included but no talk about slow internet failures&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 7&lt;/strong&gt;&lt;br&gt;
No clear list of what is included and not included&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 8&lt;/strong&gt;&lt;br&gt;
No mention of who owns the repo and accounts&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Red flag 9&lt;/strong&gt;&lt;br&gt;
No change request rules&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Section 5&lt;br&gt;
---------&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Example&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;Medicine delivery app quote audit&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Example app&lt;br&gt;
A healthcare shopping app that sells medicines and daily use products.&lt;/p&gt;
&lt;p&gt;Core features&lt;br&gt;
Product list&lt;br&gt;
Product page&lt;br&gt;
Cart&lt;br&gt;
Checkout&lt;br&gt;
Order tracking&lt;br&gt;
Prescription upload for some medicines&lt;br&gt;
Notifications for order updates&lt;/p&gt;
&lt;p&gt;Now the audit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What many quotes cover&lt;/strong&gt;&lt;br&gt;
Screens and basic flows&lt;br&gt;
Basic backend endpoints&lt;br&gt;
Basic login&lt;br&gt;
Basic checkout screen&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What many quotes miss&lt;/strong&gt;&lt;br&gt;
Prescription upload details&lt;br&gt;
Camera and photo permissions&lt;br&gt;
Users deny access&lt;br&gt;
Slow internet upload failures&lt;br&gt;
Retry steps and clear messages&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Payment edge cases&lt;/strong&gt;&lt;br&gt;
Payment success but order status not updated&lt;br&gt;
Users retry and create duplicate orders&lt;br&gt;
Need status checks and duplicate order prevention&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Device testing&lt;/strong&gt;&lt;br&gt;
Android device differences&lt;br&gt;
Notifications blocked by battery settings&lt;br&gt;
Camera issues on certain models&lt;/p&gt;
&lt;p&gt;Store release&lt;br&gt;
Privacy forms&lt;br&gt;
Permission text&lt;br&gt;
Store review changes&lt;br&gt;
Resubmission buffer&lt;/p&gt;
&lt;p&gt;If the quote does not mention these items, the quote is missing money.&lt;/p&gt;
&lt;p&gt;This is why a simple app can move from 8 weeks to 14 weeks.&lt;br&gt;
Not because of new screens.&lt;br&gt;
Because missing items show up late.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Section 6&lt;br&gt;
---------&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;What a good quote looks like&lt;/strong&gt;&lt;br&gt;
A good quote is split into clear parts. Each part has a line item and a clear output.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. Discovery&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
Requirements list&lt;br&gt;
User flows&lt;br&gt;
Out of scope list&lt;br&gt;
Hard screens list&lt;br&gt;
Risk list&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
A written scope document&lt;br&gt;
A clear list of what is included and not included&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Design&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
Key screens&lt;br&gt;
All states for key screens&lt;br&gt;
Error messages&lt;br&gt;
Permission screens&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
Final screen designs&lt;br&gt;
A simple flow for each key feature&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Development&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
App build work&lt;br&gt;
Backend work&lt;br&gt;
Admin work if needed&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
Working builds you can test&lt;br&gt;
Access to the code repo&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Integrations&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
Payments&lt;br&gt;
Push notifications&lt;br&gt;
Analytics&lt;br&gt;
Uploads&lt;br&gt;
Maps or other tools&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
A list of all tools used&lt;br&gt;
Proof that each tool works on iOS and Android&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. QA&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
Device list&lt;br&gt;
Test plan for key flows&lt;br&gt;
Bug fixing time&lt;br&gt;
Retesting time&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
A test report or bug list&lt;br&gt;
A stable build before release&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;6. Release&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
Signing and build setup&lt;br&gt;
Store listing tasks&lt;br&gt;
Screenshots&lt;br&gt;
Privacy forms&lt;br&gt;
Time buffer for review and resubmission&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
Store ready builds&lt;br&gt;
A release checklist&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;7. Handover&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
Repo access&lt;br&gt;
Accounts access&lt;br&gt;
Keys&lt;br&gt;
Basic documentation&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
Full ownership and access&lt;br&gt;
A handover checklist&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;8. Change rules&lt;/strong&gt;&lt;br&gt;
What it should include&lt;br&gt;
What counts as a change&lt;br&gt;
How changes are priced&lt;br&gt;
Who approves changes&lt;/p&gt;
&lt;p&gt;What you should receive&lt;br&gt;
Clear change request rules in writing&lt;/p&gt;
&lt;p&gt;If your quote does not look like this, ask for a rewrite.&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/12312/react-native-quote-audit-usa</guid>
<pubDate>Mon, 02 Mar 2026 14:30:00 +0000</pubDate>
</item>
<item>
<title>I built an open-source IRC client for Android – AndroidIRCX</title>
<link>https://coderlegion.com/10712/i-built-an-open-source-irc-client-for-android-androidircx</link>
<description>&lt;p&gt;Hey everyone &lt;br&gt;
I’ve been working on an open-source IRC client for Android called AndroidIRCX, and I wanted to share it with the community.&lt;br&gt;
The goal was to create a modern, lightweight, and privacy-focused IRC app, while preserving the classic IRC experience. The app is actively maintained and continues to evolve based on user feedback.&lt;br&gt;
Key features:&lt;br&gt;
 Real-time notifications&lt;br&gt;
 Optional app lock (PIN / biometrics)&lt;br&gt;
 Supports irc:// and ircs:// deep links&lt;br&gt;
 Multiple servers and channels&lt;br&gt;
 Reliable auto-reconnect logic&lt;br&gt;
 ZNC (bouncer) ready&lt;br&gt;
 IRCv3 protocol support&lt;br&gt;
 End-to-end encryption (E2EE)&lt;br&gt;
 Clean, simple UI with no unnecessary tracking&lt;br&gt;
The entire project is fully open source, and contributions, issues, and pull requests are very welcome.&lt;br&gt;
 GitHub:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/AndroidIRCx/AndroidIRCx&quot;&gt;https://github.com/AndroidIRCx/AndroidIRCx&lt;/a&gt;&lt;br&gt;
 Google Play:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://play.google.com/store/apps/details?id=com.androidircx&quot;&gt;https://play.google.com/store/apps/details?id=com.androidircx&lt;/a&gt;&lt;br&gt;
 Website:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://androidircx.com&quot;&gt;https://androidircx.com&lt;/a&gt;&lt;br&gt;
This started mostly as a passion project and as a way to keep IRC usable and relevant on modern Android devices.&lt;br&gt;
I’d love to hear your feedback, suggestions, or criticism &lt;br&gt;
Thanks for reading!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/10712/i-built-an-open-source-irc-client-for-android-androidircx</guid>
<pubDate>Thu, 29 Jan 2026 13:38:36 +0000</pubDate>
</item>
<item>
<title>React Native to Microservices</title>
<link>https://coderlegion.com/4223/react-native-to-microservices</link>
<description>&lt;p&gt;In the React Native world we pretty much only worry about calling an endpoint and receiving some data from such endpoint. We are usually blind to all the work that happens in retrieving this data. In this post I am going to trace the api call from when it leaves the device up until the response is received, in a microservices context — naive point of view.&lt;/p&gt;
&lt;p&gt;The api call goes to a domain name server to resolve where this api call should go. If I am in Italy, the api call will go to the nearest load balancer around Italy. This is enabled by having geolocation routing.&lt;/p&gt;
&lt;p&gt;After the nearest load balancer has been identified. The api call is taken to this load balancer. A load balancer makes sure that when too many users are on their beds watching tiktoks the servers are not overloaded with too many requests at once. It distributes the requests in a manageable manner.&lt;/p&gt;
&lt;p&gt;The load balancers can also route the api request to the server that is responsible for processing the request. For example, /videos/1 should go to the videos server instead of the pictures server. The load balancer will then take the /videos/1 request to the videos server.&lt;/p&gt;
&lt;p&gt;Once the api request has arrived at the videos server, this server will also communicate with other microservices, maybe analytics, views count, metadata, related videos, and such. The Synchronous part of the request will return to the react native app, the video data and metadata for example, on the other hand, the asynchronous part, analytics for e.g., usually make use of something called message queues to communicate with the other microservices.&lt;/p&gt;
&lt;p&gt;Message queues ares queues usually read by other microservices, analytics, metadata, recommendations, each can be subscribed to a message queue. Message queues are ideal because if the service goes down, the message queue will hold the message until the service comes back up and acknowledges the message was processed. This prevent’s losing messages along the way.&lt;/p&gt;
&lt;p&gt;This is a high level overview, there is so much in between these processes; ssl, caching, routing algorithm, message exchange, producer, broker, CAP theorem, rate limiting, and so much more.&lt;/p&gt;
</description>
<category>Full-stack Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4223/react-native-to-microservices</guid>
<pubDate>Tue, 22 Jul 2025 21:14:32 +0000</pubDate>
</item>
<item>
<title>Building Scalable React Native Apps with Nucleux and Supabase</title>
<link>https://coderlegion.com/4010/building-scalable-react-native-apps-with-nucleux-and-supabase</link>
<description>&lt;p&gt;Combining Supabase's powerful backend services with Nucleux's atomic state management creates a clean, scalable architecture for React Native applications. This guide shows you how to structure authentication and API services using class-based stores that naturally organize your application logic.&lt;/p&gt;
&lt;h3 id=&quot;the-architecture-pattern&quot;&gt;The Architecture Pattern&lt;/h3&gt;
&lt;p&gt;We'll build a three-layer architecture that separates concerns cleanly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ApiService&lt;/code&gt;&lt;/strong&gt;: Handles Supabase client configuration and lifecycle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SessionStore&lt;/code&gt;&lt;/strong&gt;: Manages authentication state and user sessions  &lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Auth&lt;/code&gt; Component&lt;/strong&gt;: Provides the user interface for authentication&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This pattern leverages Nucleux's dependency injection to create a clean separation where each layer has a single responsibility.&lt;/p&gt;
&lt;h3 id=&quot;setting-up-the-api-service&quot;&gt;Setting Up the API Service&lt;/h3&gt;
&lt;p&gt;First, create a centralized API service that manages the Supabase client:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/services/ApiService.ts
import AsyncStorage from &quot;@react-native-async-storage/async-storage&quot;;
import { createClient, SupabaseClient } from &quot;@supabase/supabase-js&quot;;
import { Store } from &quot;nucleux&quot;;
import { AppState, NativeEventSubscription, Platform } from &quot;react-native&quot;;
import &quot;react-native-url-polyfill/auto&quot;;

if (
  process.env.EXPO_PUBLIC_SUPABASE_URL === undefined ||
  process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY === undefined
) {
  throw new Error(&quot;Missing critical environment variables&quot;);
}

class ApiService extends Store {
  public apiClient: SupabaseClient;
  private appStateSubscription: NativeEventSubscription;

  constructor() {
    super();

    this.apiClient = createClient(
      process.env.EXPO_PUBLIC_SUPABASE_URL || &quot;&quot;,
      process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY || &quot;&quot;,
      {
        auth: {
          storage: AsyncStorage,
          autoRefreshToken: true,
          persistSession: true,
          detectSessionInUrl: false,
        },
      }
    );

    this.appStateSubscription = AppState.addEventListener(&quot;change&quot;, (state) =&amp;gt; {
      if (state === &quot;active&quot;) {
        this.apiClient.auth.startAutoRefresh();
      } else {
        this.apiClient.auth.stopAutoRefresh();
      }
    });
  }

  destroy() {
    this.appStateSubscription.remove();
    super.destroy();
  }
}

export default ApiService;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Key Benefits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Centralized Configuration&lt;/strong&gt;: Single place to manage Supabase setup&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lifecycle Management&lt;/strong&gt;: Properly manages token refresh based on app state&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;creating-the-session-store&quot;&gt;Creating the Session Store&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;SessionStore&lt;/code&gt; handles all authentication logic while staying completely decoupled from UI concerns:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-ts&quot;&gt;// src/stores/SessionStore.ts
import ApiService from &quot;@/services/ApiService&quot;;
import { Session, Subscription } from &quot;@supabase/supabase-js&quot;;
import { Store } from &quot;nucleux&quot;;

class SessionStore extends Store {
  public session = this.atom&amp;lt;Session | null&amp;gt;(null);
  public initialized = this.atom&amp;lt;boolean&amp;gt;(false);

  private apiService = this.inject(ApiService);
  private authSubscription: Subscription;

  constructor() {
    super();
    
    this.getSession();
    
    this.authSubscription = this.apiService.apiClient.auth.onAuthStateChange(
      (_, session) =&amp;gt; {
        this.setSession(session);
      }
    ).data.subscription;
  }

  private async getSession() {
    try {
      const { data: { session } } = await this.apiService.apiClient.auth.getSession();
      this.setSession(session);
    } finally {
      this.initialized.value = true;
    }
  }

  private setSession(session: Session | null) {
    this.session.value = session;
  }

  public async signIn(email: string, password: string) {
    return this.apiService.apiClient.auth.signInWithPassword({
      email,
      password,
    });
  }

  public async signOut() {
    return this.apiService.apiClient.auth.signOut();
  }

  destroy() {
    this.authSubscription.unsubscribe();
    super.destroy();
  }
}

export default SessionStore;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;Architecture Highlights:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dependency Injection&lt;/strong&gt;: &lt;code&gt;this.inject(ApiService)&lt;/code&gt; automatically provides the API client&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Atomic State&lt;/strong&gt;: Only components using &lt;code&gt;session&lt;/code&gt; or &lt;code&gt;initialized&lt;/code&gt; re-render when these values change&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Event Handling&lt;/strong&gt;: Supabase auth changes automatically update the atomic state&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clean API&lt;/strong&gt;: Simple methods for sign in/out that any component can use&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;building-the-auth-component&quot;&gt;Building the Auth Component&lt;/h3&gt;
&lt;p&gt;Finally, create a simple authentication component that consumes the session store:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-tsx&quot;&gt;// components/Auth.tsx
import SessionStore from &quot;@/stores/SessionStore&quot;;
import { useStore, useValue } from &quot;nucleux&quot;;
import React, { useState } from &quot;react&quot;;
import { Alert, StyleSheet, Text, TextInput, TouchableOpacity, View } from &quot;react-native&quot;;

export default function Auth() {
  const sessionStore = useStore(SessionStore);
  const session = useValue(sessionStore.session);
  const initialized = useValue(sessionStore.initialized);

  const [email, setEmail] = useState(&quot;&quot;);
  const [password, setPassword] = useState(&quot;&quot;);
  const [loading, setLoading] = useState(false);

  const handleSignIn = async () =&amp;gt; {
    if (!email || !password) {
      Alert.alert(&quot;Error&quot;, &quot;Please fill in all fields&quot;);
      return;
    }

    setLoading(true);
    try {
      const { error } = await sessionStore.signIn(email, password);
      if (error) Alert.alert(&quot;Error&quot;, error.message);
    } finally {
      setLoading(false);
    }
  };

  const handleSignOut = async () =&amp;gt; {
    const { error } = await sessionStore.signOut();
    if (error) Alert.alert(&quot;Error&quot;, error.message);
  };

  if (!initialized) {
    return &amp;lt;Text&amp;gt;Loading...&amp;lt;/Text&amp;gt;;
  }

  if (session) {
    return (
      &amp;lt;View style={styles.container}&amp;gt;
        &amp;lt;Text style={styles.title}&amp;gt;Welcome back!&amp;lt;/Text&amp;gt;
        &amp;lt;Text&amp;gt;Signed in as: {session.user.email}&amp;lt;/Text&amp;gt;
        &amp;lt;TouchableOpacity style={styles.button} onPress={handleSignOut}&amp;gt;
          &amp;lt;Text style={styles.buttonText}&amp;gt;Sign Out&amp;lt;/Text&amp;gt;
        &amp;lt;/TouchableOpacity&amp;gt;
      &amp;lt;/View&amp;gt;
    );
  }

  return (
    &amp;lt;View style={styles.container}&amp;gt;
      &amp;lt;Text style={styles.title}&amp;gt;Sign In&amp;lt;/Text&amp;gt;
      
      &amp;lt;TextInput
        style={styles.input}
        placeholder=&quot;Email&quot;
        value={email}
        onChangeText={setEmail}
        autoCapitalize=&quot;none&quot;
        keyboardType=&quot;email-address&quot;
      /&amp;gt;
      
      &amp;lt;TextInput
        style={styles.input}
        placeholder=&quot;Password&quot;
        value={password}
        onChangeText={setPassword}
        secureTextEntry
      /&amp;gt;
      
      &amp;lt;TouchableOpacity 
        style={[styles.button, loading &amp;amp;&amp;amp; styles.buttonDisabled]} 
        onPress={handleSignIn}
        disabled={loading}
      &amp;gt;
        &amp;lt;Text style={styles.buttonText}&amp;gt;
          {loading ? &quot;Signing In...&quot; : &quot;Sign In&quot;}
        &amp;lt;/Text&amp;gt;
      &amp;lt;/TouchableOpacity&amp;gt;
    &amp;lt;/View&amp;gt;
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 20, justifyContent: &quot;center&quot; },
  title: { fontSize: 24, fontWeight: &quot;bold&quot;, marginBottom: 20, textAlign: &quot;center&quot; },
  input: { borderWidth: 1, borderColor: &quot;#ddd&quot;, padding: 12, marginBottom: 10, borderRadius: 5 },
  button: { backgroundColor: &quot;#007AFF&quot;, padding: 12, borderRadius: 5, marginTop: 10 },
  buttonDisabled: { opacity: 0.5 },
  buttonText: { color: &quot;white&quot;, textAlign: &quot;center&quot;, fontWeight: &quot;bold&quot; },
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;why-this-architecture-works&quot;&gt;Why This Architecture Works&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;1. Clear Separation of Concerns&lt;/strong&gt;&lt;br&gt;
Each layer has a single responsibility, making the code easier to test and maintain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Automatic Reactivity&lt;/strong&gt;&lt;br&gt;
Components automatically re-render when authentication state changes, without manual subscription management.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Reusable Business Logic&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;SessionStore&lt;/code&gt; can be used across multiple components without duplicating authentication logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Type Safety&lt;/strong&gt;&lt;br&gt;
TypeScript ensures you catch authentication-related bugs at compile time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;5. Easy Testing&lt;/strong&gt;&lt;br&gt;
Each store can be unit tested independently of React components.&lt;/p&gt;
&lt;h3 id=&quot;the-result&quot;&gt;The Result&lt;/h3&gt;
&lt;p&gt;This architecture gives you a robust foundation for React Native apps that need authentication. The &lt;code&gt;SessionStore&lt;/code&gt; becomes the single source of truth for authentication state, while the component layer focuses purely on user interface concerns.&lt;/p&gt;
&lt;p&gt;Want to explore more patterns? Check out the complete &lt;a rel=&quot;nofollow&quot; href=&quot;https://nucleux.dev&quot;&gt;Nucleux documentation&lt;/a&gt; and see how this same approach scales to complex applications with multiple data stores and API services.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Ready to build your next React Native app? Try combining &lt;a rel=&quot;nofollow&quot; href=&quot;https://nucleux.dev&quot;&gt;Nucleux&lt;/a&gt; with &lt;a rel=&quot;nofollow&quot; href=&quot;https://supabase.com&quot;&gt;Supabase&lt;/a&gt; for a powerful, type-safe development experience.&lt;/em&gt;&lt;/p&gt;
</description>
<category>Mobile Dev</category>
<guid isPermaLink="true">https://coderlegion.com/4010/building-scalable-react-native-apps-with-nucleux-and-supabase</guid>
<pubDate>Tue, 15 Jul 2025 03:35:09 +0000</pubDate>
</item>
<item>
<title>Starting a new web app with a React project, should you go with React JavaScript or TypeScript?</title>
<link>https://coderlegion.com/3445/starting-new-web-app-with-react-project-should-you-go-with-react-javascript-or-typescript</link>
<description>&lt;p&gt;Deciding between &lt;strong&gt;React with JavaScript&lt;/strong&gt; and &lt;strong&gt;React with TypeScript&lt;/strong&gt; is an important early choice. &lt;/p&gt;
&lt;hr&gt;
&lt;h3 id=&quot;go-with-react-typescript-if-&quot;&gt;Go with &lt;strong&gt;React + TypeScript&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;You want type safety&lt;/strong&gt; – TypeScript helps catch bugs during development by checking types.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your project will grow in complexity or involve a team&lt;/strong&gt; – It’s much easier to maintain and refactor code with TypeScript.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You want better editor support (autocomplete, refactoring, etc.)&lt;/strong&gt; – TS improves the development experience in VS Code and similar editors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You’re comfortable learning/using TypeScript&lt;/strong&gt; or are willing to invest a bit of time upfront to learn it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;pros-&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Fewer runtime errors&lt;/li&gt;
&lt;li&gt;Better documentation via types&lt;/li&gt;
&lt;li&gt;Easier onboarding for new developers&lt;/li&gt;
&lt;li&gt;Huge ecosystem support (React + TS is now the standard in many teams)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;go-with-react-javascript-if-&quot;&gt;Go with &lt;strong&gt;React + JavaScript&lt;/strong&gt; if:&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;You want to move quickly and keep things simple&lt;/strong&gt;, especially for prototypes or MVPs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You’re not yet familiar with TypeScript&lt;/strong&gt; and don’t want the learning curve at the moment.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The project is small or throwaway&lt;/strong&gt;, and long-term maintainability is not a concern.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id=&quot;pros--1&quot;&gt;Pros:&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Faster to start&lt;/li&gt;
&lt;li&gt;No need to learn types or manage TS config&lt;/li&gt;
&lt;li&gt;Slightly simpler build tooling&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h3 id=&quot;recommendation-tl-dr-&quot;&gt;Recommendation (TL;DR):&lt;/h3&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Use React + TypeScript&lt;/strong&gt; unless you have a strong reason not to.&lt;br&gt;
It's now the default for many professional React projects and will pay off as your codebase grows.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
<category>Frontend Dev</category>
<guid isPermaLink="true">https://coderlegion.com/3445/starting-new-web-app-with-react-project-should-you-go-with-react-javascript-or-typescript</guid>
<pubDate>Tue, 24 Jun 2025 07:48:50 +0000</pubDate>
</item>
<item>
<title>Create a Video Chat App in React Native with Pre-built API</title>
<link>https://coderlegion.com/2889/create-a-video-chat-app-in-react-native-with-pre-built-api</link>
<description>&lt;p&gt;Want to create a video chat app like Zoom, Google Meet or MS Teams for your business? In this tutorial, we'll walk through how to build a 1:1 video calling app.&lt;/p&gt;
&lt;p&gt;We will start with creating a video call feature where one user can initiate a video call and the other can receive, accept or reject it.&lt;/p&gt;
&lt;p&gt;The app will be built in React Native and we'll use a &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.mirrorfly.com/react-native-chat-sdk.php&quot;&gt;pre-built video call solution from MirrorFly&lt;/a&gt;.&lt;br&gt;
If you already have an app, you can use the below steps to add the video chat feature. If not, you can use this sample app, upon which you'll add the video calling features as instructed below. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Since we'll build the app in React Native, we'll need the below pre-requisites:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Node.js: &amp;gt;= 18.20.4 &lt;/li&gt;
&lt;li&gt;npm: &amp;gt;= 10.7.0&lt;/li&gt;
&lt;li&gt;React Native: &amp;gt;= 0.73.0 and &amp;lt;=0.75.4&lt;/li&gt;
&lt;li&gt;SDK License Key Firebase Cloud Messaging (FCM) setup for Android&lt;/li&gt;
&lt;li&gt;VOIP push notification support for iOS&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; This post assumes you have the basic understanding on handling React Native.&lt;/p&gt;
&lt;h3 id=&quot;step-1-install-the-sdk&quot;&gt;Step 1: Install the SDK&lt;/h3&gt;
&lt;p&gt;Install the SDK from npm:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm install mirrorfly-reactnative-sdk
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The SDK will have all the methods to initialize the call, register the user, connect and make calls.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { SDK } from 'mirrorfly-reactnative-sdk';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After installation, this line imports the SDK to get the methods initializeSDK, register, connect, getJid, into your project&lt;/p&gt;
&lt;h3 id=&quot;step-2-adding-peer-dependencies&quot;&gt;Step 2: Adding Peer Dependencies&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;{
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  &quot;react-native-webrtc&quot;: &quot;124.0.4&quot;,&lt;br&gt;
  &quot;react-native-background-timer&quot;: &quot;^2.4.1&quot;,&lt;br&gt;
  &quot;react-native-permissions&quot;: &quot;^5.2.1&quot;&lt;br&gt;
}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;To support the SDK to run without any errors, you'll need the following npm packages:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;react-native-webrtc:&lt;/strong&gt; This package provides the JavaScript access to&lt;br&gt;
native WebRTC APIs for enabling real-time audio and video streams on&lt;br&gt;
your app. It exports objects like RTCPeerConnection, MediaStream,&lt;br&gt;
MediaDevices, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-native-background-timer:&lt;/strong&gt; This keeps your app awake when calls&lt;br&gt;
are ongoing. This package is the one that provides Methods like&lt;br&gt;
setInterval, setTimeout, clearInterval, etc., that continue to work&lt;br&gt;
even when the app is minimized.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;react-native-permissions:&lt;/strong&gt; This package is responsible for initiating&lt;br&gt;
requests for mic, camera, and notification permissions from the OS.&lt;br&gt;
It checks and asks for runtime permissions with methods like&lt;br&gt;
check(), request(), and openSettings().&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id=&quot;step-3-initializing-the-sdk&quot;&gt;Step 3: Initializing the SDK&lt;/h3&gt;
&lt;p&gt;When you initialize the SDK, you need it to listen to the connection status. For this you'll need to define a callback as mentioned below. &lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const connectionListener = (response) =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  if (response.status === &quot;CONNECTED&quot;) {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(&quot;Connected to MirrorFly&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  } else if (response.status === &quot;DISCONNECTED&quot;) {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;console.log(&quot;Disconnected from MirrorFly&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  }&lt;br&gt;
};&lt;/p&gt;
&lt;p&gt;This line logs a message when the client connects or disconnects from the server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const initializeObj = {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  apiBaseUrl: &quot;&lt;a rel=&quot;nofollow&quot; href=&quot;https://api-preprod-sandbox.mirrorfly.com/api/v1&quot;&gt;https://api-preprod-sandbox.mirrorfly.com/api/v1&lt;/a&gt;&quot;,&lt;br&gt;
  licenseKey: &quot;YOUR_LICENSE_KEY_HERE&quot;,&lt;br&gt;
  isTrialLicenseKey: true,&lt;br&gt;
  callbackListeners: {&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;connectionListener,
// Add more listeners if needed (e.g., incomingCallListener)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  },&lt;br&gt;
};&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;apiBaseUrl:&lt;/strong&gt; URL of the backend you are connecting to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;licenseKey:&lt;/strong&gt; Your SDK key for authentication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;isTrialLicenseKey:&lt;/strong&gt; Boolean to indicate trial mode.&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;callbackListeners:&lt;/strong&gt; List of event listeners your app reacts to.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;await SDK.initializeSDK(initializeObj);&lt;/p&gt;
&lt;p&gt;Now, this line initializes the SDK with your configuration and sets up event listeners. Most importantly, this must be called before you use other functions in the SDK like register() or connect().&lt;/p&gt;
&lt;h3 id=&quot;step-4-registering-a-new-user&quot;&gt;Step 4: Registering a New User&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;You'll need to import modules for:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;FCM (Android push token) &lt;/li&gt;
&lt;li&gt;VOIP push notifications (iOS)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For this, use this line:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import messaging from '@react-native-firebase/messaging';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;import RNVoipPushNotification from 'react-native-voip-push-notification';&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Here,&lt;/strong&gt; &lt;br&gt;
addEventListener('register', callback): Listens for the VOIP token from iOS.&lt;br&gt;
messaging().getToken(): Gets the FCM token from Firebase for Android.&lt;br&gt;
SDK.register(...): Registers a new user using:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;user123:&lt;/strong&gt; Unique user ID&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;fcmToken:&lt;/strong&gt; Required for push calls on Android&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;voipToken:&lt;/strong&gt; Required for VOIP on iOS&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;DEV&lt;/strong&gt; ? false : true: Sets IS_PRODUCTION based on dev or release mode&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;RNVoipPushNotification.addEventListener('register', async (voipToken) =&amp;gt; {
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;  const fcmToken = await messaging().getToken();&lt;br&gt;
  await SDK.register('user123', fcmToken, voipToken, &lt;strong&gt;DEV&lt;/strong&gt; ? false : true);&lt;br&gt;
});&lt;/p&gt;
&lt;p&gt;Next, to receive VoIP push notification on iOS, you need to trigger the registration process by adding this line:&lt;/p&gt;
&lt;p&gt;RNVoipPushNotification.registerVoipToken();&lt;/p&gt;
&lt;h3 id=&quot;step-5-connecting-the-user&quot;&gt;Step 5: Connecting the User&lt;/h3&gt;
&lt;p&gt;Using the credentials obtained from the register() step, this line logs the registered user into the server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;await SDK.connect(&quot;user123&quot;, &quot;securePassword123&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once connected, the user will be able to start or receive calls. The connection listeners will start monitoring the connection status. &lt;/p&gt;
&lt;h3 id=&quot;step-6-getting-user-jid&quot;&gt;Step 6: Getting User JID&lt;/h3&gt;
&lt;p&gt;Next, add this line:&lt;/p&gt;
&lt;p&gt;const userJid = SDK.getJid(&quot;user123&quot;);&lt;/p&gt;
&lt;p&gt;This will return the Jabber ID (JID) of a user. JID is an identification system used internally by the XMPP system to uniquely identify users. You can therefore use userJid when initiating or accepting calls.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;With these steps, you’ve built the core structure of a one-on-one &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.contus.com/blog/best-react-native-chat-apis-sdk/&quot;&gt;video calling app&lt;/a&gt;. You now have&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Real-time communication between users&lt;/li&gt;
&lt;li&gt;Customizable call UI&lt;/li&gt;
&lt;li&gt;Call control features (answer, decline, hang up)&lt;/li&gt;
&lt;li&gt;SDK-level connection and call event tracking&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;From here, you can implement beyond the basics like outgoing and incoming call UI, Audio controls (mute/unmute), and analytics. &lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Got queries?&lt;/strong&gt; Feel free to add your questions in the comments. I'd be more than happy to join a healthy discussion on the technologies.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Learn more in this video tutorial&lt;/strong&gt;, &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.youtube.com/watch?v=TL7RTm6g0Ek&quot;&gt;How to build a video chat app with React Native?&lt;/a&gt;&lt;/p&gt;
</description>
<category>Getting Started</category>
<guid isPermaLink="true">https://coderlegion.com/2889/create-a-video-chat-app-in-react-native-with-pre-built-api</guid>
<pubDate>Tue, 06 May 2025 06:08:30 +0000</pubDate>
</item>
</channel>
</rss>