Skip to content

Local Development

How to run the native iOS and Android apps locally without going through EAS. Use this when you need fast iteration — code change to running app in seconds rather than minutes, with no EAS build queue.

Three local-dev paths exist:

  1. Expo Go — covered in Frontend → Setup. Quickest start; works for everything that doesn't need native modules beyond Expo's standard set.
  2. expo run:android / expo run:ios — native development build on simulator/emulator. Required when you've added a native module that Expo Go doesn't include (Google Sign-In, native maps with Google Maps SDK, etc.).
  3. EAS dev client — a custom dev binary installed once, then JS bundle hot-reloads against it. Best for ongoing native-module work without rebuilding the binary each time.

This page covers paths 2 and 3 on macOS, since Tomoda's primary native dev surface is Android (more common configuration friction). iOS local dev is shorter — mostly Xcode + a single command — and gets a brief section at the end.

Android — toolchain

You need three things working in concert: Java, the Android SDK, and an emulator (or a tethered physical device).

Tool Required version How to install
JDK OpenJDK 17 brew install openjdk@17
Android Studio Latest stable Download
Android SDK Platform-Tools Latest Android Studio → SDK Manager → SDK Tools tab
Android Emulator + a system image Latest Android Studio → Virtual Device Manager
Node 18+ brew install node

Wire JDK 17 into the system

brew install puts the binaries in place but doesn't make them the system default. Symlink it:

sudo ln -sfn /usr/local/opt/openjdk@17/libexec/openjdk.jdk \
  /Library/Java/JavaVirtualMachines/openjdk-17.jdk

Verify:

java -version
# openjdk version "17.0.x"

Set environment variables

Add to ~/.zshrc (or ~/.bashrc):

# Java
export JAVA_HOME="/usr/local/opt/openjdk@17"

# Android SDK — default Mac install location
export ANDROID_HOME="$HOME/Library/Android/sdk"

# CLI tools on PATH
export PATH="$ANDROID_HOME/emulator:$ANDROID_HOME/tools:$ANDROID_HOME/tools/bin:$ANDROID_HOME/platform-tools:$PATH"

Reload your shell: source ~/.zshrc.

M2 Macs running Rosetta Homebrew

If brew --prefix openjdk@17 returns a /usr/local/... path rather than /opt/homebrew/..., your Homebrew is installed under Rosetta (Intel-compatible mode). This is fine — the paths above already match. If you migrated to a native Apple Silicon Homebrew, swap /usr/local for /opt/homebrew everywhere.

Create an emulator

Android Studio → Tools → Device Manager → Create Virtual Device.

Setting Pick
Device Pixel 9 (or any reasonably recent phone profile)
System image API 35 (Android 15)
Graphics Hardware - GLES 2.0

Boot it once before running anything else — first boot can take 60+ seconds.

Android — running the app

From the repo root:

cd frontend
npx expo run:android

What this does:

  1. Checks for a connected Android device or running emulator. If none is found, prints an error.
  2. Compiles the native Android code (Gradle build — takes a few minutes the first time, faster on subsequent runs).
  3. Installs the APK on the device.
  4. Starts Metro and connects.

Pointing the app at your local backend

The Android emulator runs in its own VM — localhost from inside the emulator is the emulator itself, not your Mac. Use the loopback address 10.0.2.2 to reach services running on the host:

EXPO_PUBLIC_API_URL=http://10.0.2.2:8080/api/v1 npx expo run:android

A physical tethered device sees your Mac directly via its LAN IP (192.168.1.x etc.).

Choose a specific target

# List devices visible to ADB
adb devices

# Build for a specific device id
npx expo run:android --device <id>

# Build a release variant locally (signed with the debug keystore)
npx expo run:android --variant release

Reset Metro cache when things get weird

rm -rf $TMPDIR/metro-* $TMPDIR/haste-map-*
npx expo start --clear

Android — Google Maps integration

The map screens use the Google Maps SDK for Android, which is gated on a SHA-1 fingerprint registered in the Google Cloud Console. Without this, map tiles render as a gray grid.

Get the debug keystore SHA-1

keytool -list -v -keystore ~/.android/debug.keystore \
  -alias androiddebugkey \
  -storepass android \
  -keypass android \
  2>/dev/null \
  | grep 'SHA1:'

You'll get something like:

SHA1: 5E:8F:16:06:2E:A3:CD:2C:4A:0D:54:78:76:BA:A6:F3:8C:AB:F6:25

Register it in Google Cloud Console

  1. GCP Console → APIs & Services → Credentials (in project development-485000).
  2. Open the Maps SDK for Android API key.
  3. Application restrictionsAndroid apps → add an entry:
    • Package name: com.tomoda.app
    • SHA-1 fingerprint: paste the value above.
  4. Save. Map tiles should render on next app reload (allow a minute for propagation).

Each developer registers their own debug SHA-1. The production SHA-1 (used for Play-signed builds) is registered separately by whoever runs the release pipeline — see Play Store.

iOS — toolchain

iOS local dev is shorter because Apple's signing complexity is managed by EAS Credentials.

Tool Required How
Xcode Latest stable App Store
Xcode command-line tools Bundled xcode-select --install once after Xcode
CocoaPods Latest sudo gem install cocoapods
iOS Simulator Bundled with Xcode Open Xcode → Settings → Platforms → install a recent iOS version

iOS — running the app

cd frontend
npx expo run:ios

This launches the iOS Simulator (default device — usually iPhone 15 Pro), runs pod install if needed, builds the Xcode project, installs the app, and connects Metro.

Choose a specific simulator

npx expo run:ios --device  # interactive picker

Local backend from the simulator

The iOS Simulator shares the host's network — http://127.0.0.1:8080 works as-is (no loopback gymnastics like Android).

If you're going to be developing against native modules over multiple sessions, build an EAS dev client once and use it instead of repeating expo run:android / expo run:ios:

eas build --profile development --platform android   # or ios

Install the resulting binary onto your device/emulator. Then run npx expo start and connect — your JS bundle hot-reloads against the dev client's native runtime without rebuilding the binary.

See Native Testing for using dev clients in QA workflows.

Common pitfalls

  • "No connected devices" — open Android Studio → Device Manager → Play your emulator manually before running expo run:android. Same for iOS — make sure the Simulator is open.
  • "Task 'installDebug' not found" — you're not in the frontend/ folder. cd frontend first.
  • Network error on login (Android) — emulator can't reach localhost. Use 10.0.2.2 for the API URL.
  • Map tiles render as gray — debug keystore SHA-1 not registered in Cloud Console. See the Google Maps section above.
  • Pods complaining on iOS after dependency changecd frontend/ios && pod install. Or delete ios/Pods and ios/Podfile.lock and re-run npx expo run:ios.
  • JDK mismatch on Android Gradle build — confirm java -version shows 17, not 11 or 21.

What this page does NOT cover

  • The release pipeline — see Native Release for the EAS Build/Submit flow.
  • Store portal management — see Play Store for Google Play Console, App Store for App Store Connect.
  • Testing strategy — see Native Testing for emulator/device QA flows and OAuth caveats.