How to access localhost from an Android emulator (and what 10.0.2.2 means)
How to access localhost from an Android emulator (and what 10.0.2.2 means)
When you're building Android apps that talk to a backend server on your computer (like a local API), one of the most common issues developers face is this:
Why doesn’t my app connect to localhost?
Here’s what’s really happening, and how to fix it.
When your app runs inside an Android emulator, it runs in a virtual machine — essentially, a separate computer within your computer.
So when you try to access localhost or 127.0.0.1 inside the emulator, it refers to the emulator itself, not your real computer. Since your development server is running on your actual machine, not inside the emulator, the connection fails.
Android emulators include a special IP address that redirects to your host machine’s localhost. That address is:
10.0.2.2
Using this IP from within the emulator will connect directly to the localhost of your development machine. So if your server is running on localhost:3000, change your app to connect to http://10.0.2.2:3000 instead.
Some newer emulator images, especially ones with Google Play Store support (like Pixel 7 or Pixel 8 system images), use a different virtual network configuration. In these environments, the emulator uses a different gateway to access the host.
In those cases, try using:
10.0.3.2
If 10.0.2.2 doesn’t work, this is likely why.
Here are some best practices to avoid hardcoding and make your emulator–host connection more flexible:
1. Make the server IP configurable
Instead of hardcoding the IP address, let it be defined by a config variable. For example:
const String serverIp = "10.0.2.2"; // or "10.0.3.2" as needed
const int serverPort = 3000;
String get baseUrl => "http://$serverIp:$serverPort";
This way, you can change it easily when switching environments.
2. Detect emulator at runtim
In Flutter, you can use the device_info_plus package to detect whether your app is running on an emulator:
import 'package:device_info_plus/device_info_plus.dart';
Future<bool> isEmulator() async {
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
return androidInfo.isPhysicalDevice == false;
}
When the app is running on an emulator, you can automatically use 10.0.2.2 or 10.0.3.2 as needed. On a real device, you might use a production or staging server instead.
3. Add fallback logic
If your app fails to connect to 10.0.2.2, try again using 10.0.3.2. This can make your development experience smoother, especially if you switch between emulator types.
You can also use your machine’s LAN IP address (like 192.168.1.50) to connect to your backend server. This works well for both emulators and physical Android devices connected to the same Wi-Fi network. Just make sure your firewall allows incoming connections to your server.
localhost points to the emulator, not your computer.
Use 10.0.2.2 to access your host machine from most Android emulators.
If that doesn't work, try 10.0.3.2 — especially on Google Play–enabled emulator images.
Make your base URL configurable and use runtime detection to adjust for emulator vs. real device.
Implement fallback logic and consider using your LAN IP when testing across multiple devices.