React Native Fetch Failing on Android But Working iOS
When a fetch call in React Native works perfectly on iOS but fails on Android, the cause is almost always a platform-specific network policy difference — Android is significantly stricter by default about what kinds of network requests it allows, especially in newer OS and target SDK versions.
The Problem
Your app calls an API with fetch, and it works fine when testing on iOS Simulator or a physical iPhone, but on Android it fails immediately with something like:
TypeError: Network request failed
The error is vague and doesn’t point to a specific cause, and the same request works fine when tested directly with curl or Postman from your development machine, which rules out the API itself as the problem.
Why It Happens
Android enforces network security rules that iOS doesn’t apply in the same way, and the most common causes are:
- The API is served over plain HTTP instead of HTTPS, and Android blocks cleartext (unencrypted) traffic by default starting with API level 28 (Android 9), while iOS’s App Transport Security allows more exceptions during development
- You’re using
localhostto point to a local development server, but on an Android emulator,localhostrefers to the emulator itself, not your development machine — this is a completely different problem from CORS, and produces the exact same generic “Network request failed” error - A self-signed or otherwise untrusted SSL certificate is being used for local HTTPS development, which Android rejects more strictly than iOS by default
- A missing or misconfigured
usesCleartextTrafficsetting in the Android manifest, which is required if you genuinely need to allow HTTP traffic for a specific domain during development or for a legacy API
The Fix
First, confirm whether the API is being reached over plain HTTP. If so, either switch to HTTPS (the correct long-term fix), or, for local development specifically, explicitly allow cleartext traffic in android/app/src/main/AndroidManifest.xml:
<application
android:usesCleartextTraffic="true"
...>
For a more targeted approach that doesn’t disable HTTPS enforcement app-wide, use a network security config file instead, allowing cleartext only for specific domains:
<!-- android/app/src/main/res/xml/network_security_config.xml -->
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">10.0.2.2</domain>
</domain-config>
</network-security-config>
and reference it from the manifest:
<application
android:networkSecurityConfig="@xml/network_security_config"
...>
If the real issue is localhost pointing to the wrong place, replace it with the Android emulator’s special alias for your host machine, 10.0.2.2, instead of localhost or 127.0.0.1:
const API_URL = Platform.OS === 'android' ? 'http://10.0.2.2:3000' : 'http://localhost:3000';
If you’re testing on a physical Android device rather than an emulator, neither localhost nor 10.0.2.2 will work — instead, use your development machine’s actual local network IP address, and make sure both devices are on the same network.
Still Not Working?
If you’ve confirmed HTTPS is being used correctly and the host address is right, check whether your API’s SSL certificate is self-signed or issued by a certificate authority Android doesn’t trust for development purposes. Android’s stricter certificate validation can silently fail requests that iOS accepts more permissively, especially with certificates generated by local development tools — in that case, using a properly trusted certificate, or a tool like ngrok to tunnel through a trusted HTTPS endpoint during development, resolves the mismatch without weakening your production security settings.
It’s also worth double-checking your app’s targetSdkVersion in android/build.gradle, since Android’s network security defaults have gotten progressively stricter with each API level. An app targeting a newer SDK version is subject to tighter cleartext and certificate validation rules by default than one still targeting an older version, which can explain why a network call that worked fine on an older Android setup starts failing after a routine SDK upgrade, even without any change to your networking code itself.
Finally, if the request involves file uploads or a multipart form body, confirm the Content-Type header is being set correctly and isn’t accidentally overridden by React Native’s fetch polyfill in a way that differs subtly from how iOS handles the same request. Multipart requests are a common source of platform-specific bugs that look like a general “network failed” error but are actually a malformed request body being rejected server-side on one platform and silently tolerated on the other.