Understanding UnknownHostException in Android (and Why the API 'Worked Locally')
TIL that an Android UnknownHostException on a hostname that resolved fine everywhere else usually isn't a DNS problem at all — it's the app pointed at a hostname that only ever existed on my dev machine.
The Problem #
The app worked perfectly against the API in development and threw java.net.UnknownHostException on the exact same code path once built and run outside my machine. No amount of restarting the Metro bundler or clearing the cache changed anything, because the bug wasn't in the JavaScript at all.
Context #
Local development used http://localhost:5000 as the API base URL, which resolves fine on the machine running both the app and the API — for a browser or an iOS simulator sharing that machine's network stack.
What I Tried #
Checked the API server itself was up and reachable, confirmed it in a browser on the same machine, and assumed the issue was somewhere in the request code.
What Went Wrong #
I was debugging the wrong layer. The request code was fine — the hostname it was pointed at simply didn't mean what I assumed it meant on an Android device or emulator.
The Solution #
localhost on an Android emulator refers to the emulator's own virtual device, not the host machine running it — the host is reachable at 10.0.2.2 instead. On a physical device, neither works; it needs the host machine's actual LAN IP, or (for anything beyond local testing) a real hostname pointing at deployed infrastructure.
const API_URL = __DEV__
? Platform.select({
android: "http://10.0.2.2:5000", // Android emulator → host machine
ios: "http://localhost:5000", // iOS simulator shares the host's network
default: "http://192.168.1.42:5000", // physical device on the same LAN
})
: "https://api.production-domain.com";Why It Works #
UnknownHostException means exactly what it says: the device genuinely could not resolve that hostname to anything, because localhost on an Android emulator is a real, different, un-configured host — not an alias for "the computer I'm developing on."
Lessons Learned #
An error message that looks like a DNS/networking failure can actually be a configuration assumption baked into a URL constant. The fix wasn't network debugging — it was noticing "localhost" means something different depending on which device is asking.
What I Would Do Differently #
I'd set up platform-aware API base URLs from the very first API call in the project, instead of hardcoding localhost and discovering the platform difference the first time someone tested on Android.
Related Concepts #
Android emulator networking (10.0.2.2), Platform.select(), dev vs. production API base URLs.
Related content
Debugging Production API Failures by Reading the Client's Error, Not the Server's Logs
TIL that when a mobile app reports 'network error' against a production API, the fastest path to the actual cause is the client-side error object, not tailing server logs that show nothing wrong.
Debugging an Android-Only FormData Upload Failure
TIL an upload that worked reliably on iOS and failed intermittently on Android came down to a missing file extension on the filename I was generating — Android's multipart parsing cared, iOS's didn't.
Android-Specific React Native Problems That Never Show Up on iOS
TIL that a React Native feature working perfectly in the iOS simulator is not evidence it'll work on Android — cleartext traffic, back-button handling, and permission dialogs all diverge silently.