A Checklist for `adb reverse` When a Phone Cannot Reach `localhost`
`localhost` on a phone is the phone, not your development computer. Use an ADB reverse mapping and test the exact host port before changing app networking code.
Map the port in the direction your app needs
An Android app running on a physical device interprets localhost as the device itself. Your API server on the laptop is a different network endpoint. With USB debugging, adb reverse forwards a device-side port back to a host-side port:
adb reverse tcp:8080 tcp:8080
adb reverse --listNow a development build can call http://127.0.0.1:8080 on the device, if the server is listening on host port 8080. Confirm that the API responds from the laptop first. A reverse mapping cannot fix a server that is stopped or listening only on an unexpected port.
Verify the selected device and app policy
If multiple emulators or phones are attached, target the intended serial with adb -s SERIAL reverse .... Re-run adb reverse --list after reconnecting because mappings are tied to the current ADB transport and may need to be recreated.
If the request still fails, inspect the app's cleartext traffic policy, Android network security configuration, and the exact URL logged by the client. A development HTTP endpoint may be blocked by platform policy even when the tunnel works. Do not weaken release security settings to make a local tunnel pass.
Use browser or curl tests on the host, then inspect device logs for DNS, connection-refused, TLS, or policy errors. These are different failure classes. Remove stale mappings with adb reverse --remove tcp:8080 when changing ports, and document the development-only URL so it cannot accidentally become a production endpoint.
Prove each layer before changing the app
Treat the path as a sequence: the API process listens on the expected host interface and port; ADB has a reverse mapping for the selected device; the app requests that device-side port; and Android's network policy permits the request. A failure at one layer can resemble a failure at another. Start the server and call it from the laptop with the exact route and method the app needs. Check the listening address as well as the port: a service bound to a container-only interface or an unexpected host interface may not be reachable through the route you expect.
Then list the reverse mappings and use an explicit serial if more than one device is attached. A mapping to the wrong port is easy to miss when the host and device ports differ. For example, the app can use device port 8080 while the development server listens on 3000; the mapping must express that relationship rather than repeat a copied command with identical numbers. Save the mapping command alongside the development run instructions, but execute it after connecting because reconnects can remove the forwarding state.
Check the exact URL generated by the build flavor. Debug, staging, and release configurations often have separate API base URLs. A debug build pointed at a production hostname will not be repaired by reverse forwarding, and a release build should not inherit a local URL. Search the effective configuration or log the sanitized scheme, host, and port at startup. Never log credentials or full tokens while diagnosing connectivity.
Interpret the error before choosing a remedy
Connection refused usually means the destination was reached but nothing accepted the connection at that port. A timeout can indicate a missing route or a server that is not responding. A cleartext-policy message points to Android policy, while a certificate error points to TLS trust or hostname configuration. A DNS error suggests the app is using a hostname rather than the loopback endpoint you intended. These distinctions are more useful than switching randomly between 10.0.2.2, a laptop IP address, and localhost; those addresses serve different emulator and physical-device setups.
Use adb reverse --remove for the specific stale port when changing configuration. If multiple devices are active, check each mapping independently. Keep permissive HTTP settings limited to a debug manifest or debug-only network security configuration, and verify that the release manifest does not include them. A good final check is to disconnect the cable or disable the development tunnel and confirm the production configuration still points to its intended service. That prevents a local convenience from silently becoming a shipping dependency.
Check the host listener and container boundary
When the API runs inside Docker, a host-side curl can succeed while the device tunnel still fails because the container port was never published to the host. Verify the full chain from the container listener to the host port and then to the device port. Confirm the development server binds to an interface reachable by the forwarding mechanism; a process bound only to an isolated container address is not automatically exposed by ADB.
Try a small health route that returns no sensitive data. A stable status response can distinguish transport from authentication or application routing. Once the health check succeeds through the tunnel, issue the actual API request and compare method, path, headers, and body. Do not log bearer tokens or personal payloads. If a proxy or VPN is active, compare behavior with and without it only when policy permits, and record the selected network route.
Keep the mapping lifecycle close to the run command
Make the development command establish the required reverse mapping after confirming that the intended device is attached. If the app needs several local services, document each port and its owner. Remove mappings when the corresponding service stops or the workflow changes. Avoid leaving a generic reverse mapping in a shared script that silently routes a production-like hostname to a developer laptop.
Finally test the same app configuration on a physical device and emulator if both are supported; their host-routing models differ. Assert the environment at runtime using a safe label and make release builds reject localhost endpoints. This catches the subtle case where a networking workaround solves local testing but accidentally remains in a distributable flavor.