RORK LABJP
TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the roundTOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude CodeSIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App StoreMAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessageNATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core MLSEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Articles/Dev Tools
Dev Tools/2026-05-29Intermediate

Diagnosing 'Network request failed' That Only Hits Android Emulator in Rork

Your fetch returns fine in the iOS simulator but throws 'Network request failed' the moment you switch to Android. Here is the diagnosis order I use to separate localhost, cleartext, certificate, and proxy issues, with code that actually compiles.

Rork515React Native209Android43Network3Troubleshooting38

The response that comes back cleanly in the iOS simulator collapses into TypeError: Network request failed the moment you switch to the Android emulator. If you build with Rork for a while — wiring up an internal API, hitting a Node dev server you just spun up — you will meet this error at least once.

I have been shipping mobile apps as a solo developer since 2014, and across a portfolio of wallpaper, healing, and law-of-attraction apps that has accumulated around fifty million downloads, I have run into this exact message more times than I can count. The cause is different each time, but the message is identical. That is why having a fixed diagnosis order saves real hours later on. What follows is the order I actually walk through when only Android falls over.

Restating what the error really means

Network request failed is the coarse-grained error React Native throws when something dies at the network layer. In practice it boils down to one of these four:

  1. The host is unreachable (DNS / IP / port).
  2. The host is reachable, but TLS never completes (cert chain, plain HTTP being blocked).
  3. The request comes back, but Android's cleartext policy rejects it.
  4. A proxy, VPN, corporate firewall, or Charles in the middle eats the connection.

The reason this trips you up more than on iOS is that the Android emulator runs as its own Linux device, separate from your Mac. Forget that for a minute and you can lose half an hour to symptom-level fixes.

First check: is the host even reachable?

Before touching code, open Chrome inside the Android emulator and paste the fetch target URL into the address bar.

  • If it opens, the problem is on the app side (headers, body, timeouts, module load order).
  • If it does not open, the problem is reachability (the next section).

This one step cuts investigation time roughly in half. Whenever I help someone outside my team, I ask them to do this before we look at code.

Cause 1: Hitting localhost directly

For the Android emulator, localhost and 127.0.0.1 point at the emulator itself, not at your Mac. Your Node, Rails, FastAPI, or Supabase dev server is invisible from there. This is the most common pitfall when starting local development in Rork.

To reach the host machine from the Android emulator, use one of these.

// utils/apiBase.ts
import { Platform } from 'react-native';
 
const LOCAL_PORT = 8787;
 
export const getApiBase = () => {
  // Production
  if (!__DEV__) return 'https://api.example.com';
 
  // Development: pick per-platform
  if (Platform.OS === 'android') {
    // Special IP that the Android emulator routes to the host's localhost
    return `http://10.0.2.2:${LOCAL_PORT}`;
  }
  // iOS simulator shares the Mac's network namespace, so this works as-is
  return `http://localhost:${LOCAL_PORT}`;
};

On a physical Android device, 10.0.2.2 does not work either. Either expose the Mac's LAN IP (for example 192.168.1.21) via an env variable and connect over Wi-Fi, or use adb reverse to forward over USB.

# With the Android device plugged in via USB,
# make the host's port look like a local port on the device.
adb reverse tcp:8787 tcp:8787

adb reverse drops when you unplug, so it is safest to keep this for local debugging instead of baking it into a CI flow.

Cause 2: Cleartext HTTP is blocked on Android

Since Android 9, plain HTTP is blocked by default. Even a development URL like http://10.0.2.2:8787 falls into this bucket and gets reported as Network request failed.

In Rork (Expo), the quickest fix during development is to enable cleartext in app.json.

{
  "expo": {
    "android": {
      "usesCleartextTraffic": true
    }
  }
}

I would caution against shipping this flag in production builds. With AdMob, Firebase, and your own API in the same binary, you widen the attack surface a lot. My personal setup splits dev and prod configs (app.dev.json vs app.json) so cleartext stays scoped to development.

If you want fine-grained control, write a network security config and whitelist only the hosts you trust.

<!-- res/xml/network_security_config.xml -->
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
  <domain-config cleartextTrafficPermitted="true">
    <domain includeSubdomains="true">10.0.2.2</domain>
    <domain includeSubdomains="true">192.168.1.21</domain>
  </domain-config>
</network-security-config>

"Just flip usesCleartextTraffic: true and forget to remove it before release" is a very real footgun. Once it leaves the dev branch, double-check the App Bundle.

Cause 3: Self-signed or expired certificates

This one tends to bite when you hit an internal API or a local HTTPS endpoint created with mkcert. The iOS simulator sometimes inherits trust from the Mac keychain, which masks the problem until you migrate http→https.

The Android emulator has its own trust store, so a self-signed certificate is rejected. Short-term, you have two pragmatic options:

  • Fall back to plain HTTP during development with usesCleartextTraffic.
  • Push the CA into Android (use adb push plus network_security_config.xml to trust a user CA).

Long-term, putting a Cloudflare Tunnel or ngrok in front of your dev server so Android can pull a real certificate makes the eventual switch to a production API painless.

Cause 4: A proxy, VPN, or Charles eats the request

Firing fetch() while Charles Proxy is running without the CA installed gives you an instant Network request failed on Android. This is by far the easiest cause to overlook. Every time I screen-share through a session like this, the "oh, I left Charles running" moment shows up.

A clean order to verify:

  1. Check System Settings → Wi-Fi → Proxies.
  2. Quit Charles or Proxyman and retry.
  3. Disconnect the corporate VPN and retry.
  4. If it still fails, open the URL in Chrome (see above).

While I was tuning AdMob frequency caps through Crashlytics + Remote Config, my own Charles instance was dropping the AdMob requests and I convinced myself ads were broken "only in the evening." It cost me half a day before I noticed the middleman. Now ruling out the proxy is the first thing I do.

Two app-side bugs that explain Android-only failures

When reachability is fine, look at the code. Two patterns commonly fail on Android while iOS happily forgives them.

Setting Content-Type yourself on FormData

// ❌ The boundary string is missing — Android rejects, iOS often forgives
await fetch(url, {
  method: 'POST',
  headers: { 'Content-Type': 'multipart/form-data' }, // ← do not set this
  body: formData,
});
 
// ✅ Let React Native compute the boundary
await fetch(url, {
  method: 'POST',
  body: formData,
});

The multipart/form-data boundary is filled in by the runtime. iOS tends to tolerate the missing piece, while Android cuts the connection the moment it is absent.

fetch() without a timeout

If the Android emulator is swapping memory or the Wi-Fi flickers, fetch() can wait forever. Capping the request with an AbortController at 10–15 seconds improves both UX and your ability to debug.

const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 12_000);
try {
  const res = await fetch(url, { signal: ctrl.signal });
  return await res.json();
} catch (e: any) {
  if (e.name === 'AbortError') throw new Error('timeout');
  throw e;
} finally {
  clearTimeout(timer);
}

Where to go from here

Start with the thirty-second check: can the Android emulator's Chrome reach the URL? From there, walk localhost → cleartext → certificate → proxy in order, and you will land on the cause for almost every case.

I am still learning along the way, but I hope this saves you some of the hours I have already spent. Thank you for reading.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

Dev Tools2026-05-19
Fixing 'Row too big to fit into CursorWindow' on Android When AsyncStorage Holds Too Much in Rork
When a React Native app generated with Rork stores large JSON or image metadata in AsyncStorage, Android can throw a Row too big to fit into CursorWindow exception. Here are the practical fixes — MMKV migration, chunked keys, payload trimming, and compression — explained from real wallpaper-app experience.
Dev Tools2026-05-02
Why StatusBar Colors Won't Apply in Rork — and How to Fix It
A practical, case-by-case guide to fixing StatusBar color and style issues in Rork apps across iOS and Android, based on real shipping experience.
Dev Tools2026-04-24
Rork WebView Is Blank or Won't Load: 6 Causes to Check Before You Ship
Dropped a WebView into your Rork app and got a silent blank screen on device? Here's the checklist I run through, in the order that catches the most bugs first.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →