Simulators are fast; physical devices are truthful. USB and wireless debugging connect your SDK 57 app to real hardware, and internal distribution ships installable builds to QA without App Store review - the loop every mobile team needs before production.
# Android USB - verify the device is visibleadb devices# Expect: <serial> device# Run a development build directly on the plugged-in phonenpx expo install expo-dev-clientnpx expo run:android --device# iOS USB - pick the connected iPhone in Xcode or:npx expo run:ios --device# Start Metro for an installed dev client (same Wi‑Fi or USB)npx expo start --dev-client --lan# Android wireless (Android 11+) after initial USB pairingadb pair <ip>:<pairing-port> # one-time, from Developer optionsadb connect <ip>:5555adb devices# Internal QA build (installable .ipa / .apk)eas build --profile preview --platform all
When to reach for this:
Validating camera, biometrics, push notifications, or haptics that simulators fake or skip.
Measuring scroll performance and memory on real SoCs.
QA needs an installable build without public store release.
Debugging LAN issues - USB (--localhost / adb reverse) bypasses flaky Wi‑Fi.
Dogfooding a release candidate on teammates' phones via TestFlight or Play internal testing.
# After eas.json exists:eas logineas build:configure # if not already done# Daily dev on your phoneeas build --profile development --platform android# Install from the QR/link, then:npx expo start --dev-client# Hand to QA - internal distribution (no store review)eas build --profile preview --platform ioseas build --profile preview --platform android# iOS → TestFlight internal group; Android → direct APK or Play internal track
// App.tsx - show connection mode so QA can report environment accuratelyimport { useEffect, useState } from "react";import { StyleSheet, Text, View } from "react-native";import * as Device from "expo-device";import Constants from "expo-constants";export default function App() { const [info, setInfo] = useState("Loading…"); useEffect(() => { const model = Device.modelName ?? "unknown"; const os = `${Device.osName} ${Device.osVersion}`; const build = Constants.expoConfig?.version ?? "0.0.0"; const channel = Constants.expoConfig?.extra?.eas?.channel ?? "local"; setInfo(`${model} · ${os} · v${build} · channel:${channel}`); }, []); return ( <View style={styles.container}> <Text style={styles.title}>QA device probe</Text> <Text style={styles.body}>{info}</Text> <Text style={styles.hint}>Shake device → dev menu → Reload after Metro reconnects.</Text> </View> );}const styles = StyleSheet.create({ container: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, title: { fontSize: 20, fontWeight: "700", marginBottom: 8 }, body: { fontSize: 14, textAlign: "center", color: "#334155" }, hint: { marginTop: 16, fontSize: 12, color: "#64748b" },});
What this demonstrates:
adb devices as the gate before Android USB deploys.
npx expo run:android --device / npx expo run:ios --device for local native compiles onto hardware.
eas.json profiles separating development (dev client + debug) from preview (internal QA).
expo-device metadata on screen so bug reports include model and OS.
USB debugging exposes the device to adb (Android) or Xcode's devicectl/ios-deploy (iOS). Expo CLI and npx expo run:* forward Metro's bundle over the connection.
npx expo start --dev-client serves JavaScript to an installed development build - the native shell stays on the phone; JS hot reloads over LAN, tunnel, or USB port forwarding.
Wireless debugging keeps the same Metro URL after you pair once - Android uses adb pair + adb connect; iOS uses Xcode Window > Devices and Simulators wireless pairing (iOS 17+ workflow in Xcode 15+).
Internal distribution (distribution: "internal" in eas.json) produces ad-hoc iOS IPAs (registered devices) or Android APKs / Play internal track uploads - not public store listings.
Preview channel builds pair well with EAS Update so QA can receive OTA JS bundles after the native shell is installed.
Connect via USB → Xcode Devices → Connect via network checkbox
Same Mac network; device shows as wireless in Xcode
After wireless adb connect, run npx expo start --dev-client --lan and ensure the phone and laptop share a subnet (guest Wi‑Fi often blocks peer traffic).
adb devices shows unauthorized - The RSA prompt was dismissed. Fix: Revoke USB debugging authorizations on the phone, replug USB, accept the prompt.
iOS build fails with provisioning errors - Device UDID not in the profile. Fix: Run eas device:create and rebuild, or register the device in Apple Developer portal.
Metro unreachable on LAN - Guest networks block device-to-laptop traffic. Fix: Use adb reverse + --localhost on Android, or --tunnel on both platforms.
Expo Go on device mismatches SDK 57 - App Store Expo Go may lag your project SDK. Fix: Use a development build; Expo Go cannot load arbitrary native modules anyway.
You should see your serial number with the state device. If it says unauthorized, accept the debugging prompt on the phone.
How do I run my Expo app on a physical iPhone?
Install Xcode, enable Developer Mode on the phone, trust the Mac, then run npx expo run:ios --device or select the device in Xcode. For cloud builds, use eas build --profile development --platform ios and install from the link.
What is the difference between expo start and expo start --dev-client?
--dev-client targets your custom development build instead of Expo Go. Required once expo-dev-client and custom native modules are in the project.
How does wireless debugging work on Android?
On Developer options > Wireless debugging, pair with adb pair <ip>:<port> using the six-digit code, then adb connect <ip>:5555. The device should appear in adb devices without USB.
How do I enable wireless debugging on iOS?
Connect via USB, open Xcode Window > Devices and Simulators, select your iPhone, and check Connect via network. After pairing, Xcode and npx expo run:ios --device can target the wireless device.
What is internal distribution in EAS?
"distribution": "internal" in eas.json produces builds for registered testers - ad-hoc iOS IPAs, Android APKs, or Play internal track - without public store release.
When should I use the preview profile vs development?
development - engineers with dev menus and fast iteration.preview - QA builds closer to production, installable via TestFlight internal or APK link.
Metro cannot connect on Wi‑Fi - what now?
Try npx expo start --dev-client --tunnel, or on Android run adb reverse tcp:8081 tcp:8081 and start with --localhost.
Do I need expo-dev-client for physical device testing?
Only if you use native modules beyond Expo Go's bundle. For SDK-only spikes, scan the QR with Expo Go. Most production apps outgrow Expo Go quickly.
How do I register iOS devices for internal builds?
eas device:create
Share the registration URL with testers before running eas build --profile preview --platform ios.
Can I install an Android preview build without Google Play?
Yes - set "buildType": "apk" under the Android profile. EAS provides a direct download link you can share in Slack or email.
Why is my physical device slower than the simulator?
Debug builds include dev tooling, Metro network overhead, and real thermal throttling. Use release or preview builds for representative performance.
What hardware features must be tested on a real device?
Camera, push notifications, biometrics, haptics, precise GPS, barometer, and background tasks. Simulators omit or fake these.
How do I share a build with non-developer QA?
Run eas build --profile preview, distribute the install link or TestFlight invite, and document the Metro channel if you also push EAS Update bundles.