A complete guide to Android development using the Devbox Android plugin. This guide covers setup, device management, development workflow, testing, and troubleshooting.
The Android plugin enables reproducible Android development without touching global system state. It provides:
- Project-local Android environment: All Android user data (AVDs, emulator configs, adb keys) is stored in
.devbox/virtenv/, never in~/.android - Reproducible SDK management: Android SDK composed via Nix flake, ensuring consistent tooling across machines
- Device management: JSON-based device definitions with CLI commands for creating, updating, and managing AVDs
- Emulator control: Scripts for starting, stopping, and resetting emulators
The plugin does not provide build or deploy commands. Every project has different build tooling (Gradle, Bazel, custom scripts), so you define those in your own devbox.json. See Adding Build Scripts for patterns.
Pure shells with devbox run --pure guarantee isolated, reproducible execution without side effects.
- Devbox installed
Devbox handles downloading JDK, Gradle, and all other tools — you don't need to install them separately.
Create or modify your devbox.json to include the Android plugin:
{
"include": ["github:segment-integrations/devbox-plugins?dir=plugins/android"],
"packages": {
"jdk17": "latest",
"gradle": "latest"
}
}The include line adds the plugin. The packages section adds your build tooling. APK paths are auto-detected at runtime when you deploy.
Initialize the Devbox environment:
devbox shellThis command:
- Downloads and installs the Android SDK via Nix
- Creates device definitions in your devbox.d directory
- Sets up runtime scripts
- Configures environment variables for Android development
Verify the installation:
devbox run android.sh infoThis displays resolved SDK information including SDK root, build tools version, and available devices.
Device definitions are JSON files that describe AVD configurations. Each plugin install creates a devices/ directory inside your devbox.d/ folder with default device files.
The plugin includes two default devices:
min.json- Minimum supported Android version (API 21, namedpixel_api21)max.json- Latest Android version (API 36, namedmedium_phone_api36)
These files live in your devbox.d/ directory, which is the devbox plugin configuration folder. The plugin creates a subdirectory there with a devices/ folder containing them (e.g., devbox.d/<plugin-dir>/devices/min.json). The filenames (min, max) are short nicknames you use in commands. The name field inside each JSON file is the full AVD name that appears in device listings.
View all available device definitions:
devbox run android.sh devices listOutput shows device names, API levels, device profiles, and system image tags.
Create a device with specific configuration:
devbox run android.sh devices create pixel_api28 \
--api 28 \
--device pixel \
--tag google_apis \
--abi x86_64Parameters:
--api(required): Android API level--device(required): AVD device profile (e.g.,pixel,pixel_xl,Nexus 5)--tag: System image tag (google_apis,google_apis_playstore,play_store,aosp_atd,google_atd)--abi: Preferred ABI architecture (x86_64,arm64-v8a,x86)
Show configuration for a specific device:
devbox run android.sh devices show pixel_api28Modify an existing device definition:
# Update API level
devbox run android.sh devices update pixel_api28 --api 29
# Rename device
devbox run android.sh devices update pixel_api28 --name pixel_api29
# Change system image tag
devbox run android.sh devices update pixel_api28 --tag google_apis_playstoreRemove a device definition:
devbox run android.sh devices delete pixel_api28By default, the Android SDK flake evaluates all devices. To optimize evaluation time (especially in CI), set the ANDROID_DEVICES environment variable in your devbox.json:
{
"env": {
"ANDROID_DEVICES": "min,max"
}
}Leave ANDROID_DEVICES unset or empty to evaluate all devices.
After creating, updating, or deleting devices, regenerate the lock file:
devbox run android.sh devices evalThe lock file (in your devices directory) optimizes CI builds by limiting which SDK versions are downloaded. Commit this file to version control.
Ensure local AVDs match device definitions:
devbox run android.sh devices syncThis creates or updates AVDs to match your JSON device definitions. Run this after modifying device files or pulling changes.
Start an Android emulator for testing:
# Start default device
devbox run start:emu
# Start specific device by nickname
devbox run start:emu pixel_api28
# Start with clean state (wipe data)
devbox run android.sh emulator start --pure pixel_api28Without --pure, the emulator reuses existing state if already running (faster for development). With --pure, it always starts fresh with wiped data (reproducible for testing).
Set the default device in devbox.json:
{
"env": {
"ANDROID_DEFAULT_DEVICE": "max"
}
}Stop all running emulators:
devbox run stop:emuReset AVD state (clears all data and app installations):
devbox run android.sh emulator resetThis is useful after Nix package updates or when you need a clean slate.
The plugin provides emulator and device management. Build and deploy commands are project-specific, so you define them in your devbox.json. Here's a typical setup:
{
"include": ["github:segment-integrations/devbox-plugins?dir=plugins/android"],
"packages": {
"jdk17": "latest",
"gradle": "latest"
},
"shell": {
"scripts": {
"build:android": [
"gradle assembleDebug"
],
"build:release": [
"gradle assembleRelease"
],
"start:app": [
"android.sh run ${1:-}"
]
}
}
}The ${1:-} syntax passes an optional argument through to the command — it means "use the first argument if provided, otherwise use nothing." This lets you run devbox run start:app (default device) or devbox run start:app min (specific device).
With these scripts defined, you can:
# Build the APK
devbox run build:android
# Build, install, and launch on the emulator
devbox run start:app
# Run on a specific device
devbox run start:app minHow APK auto-detection works: The android.sh run command waits for the emulator to boot, then auto-detects the APK using this precedence chain:
ANDROID_APP_APKenv var — if set, resolves the path/glob relative to project root- Recursive search of the project directory for
.apkfiles, skipping.gradle/,build/intermediates/,node_modules/, and.devbox/ - Recursive search of the current working directory (if different from project root)
The app's package name and launch activity are extracted from the APK automatically.
In most projects, step 2 finds the right APK with no configuration. If auto-detection picks the wrong APK (e.g., multiple build variants), set ANDROID_APP_APK explicitly:
{
"env": {
"ANDROID_APP_APK": "app/build/outputs/apk/debug/app-debug.apk"
}
}See the Android example project for a complete working setup. The example project uses a local plugin path for development. If you use it as a template, change the include to the GitHub URL shown above.
Typical development session:
# 1. Enter devbox shell
devbox shell
# 2. Start emulator
devbox run start:emu max
# 3. Build and run app (using your custom scripts)
devbox run build
devbox run start:app max
# 4. Make code changes, rebuild, and redeploy
devbox run build
devbox run start:app max
# 5. Stop emulator when done
devbox run stop:emuThe Android example project includes E2E test infrastructure using process-compose. You can use it as a template for your own project.
Copy the example test suite:
cp -r examples/android/tests/ your-project/tests/Add a test script to your devbox.json:
{
"shell": {
"scripts": {
"test:e2e": [
"process-compose -f tests/test-suite.yaml --no-server --tui=${TEST_TUI:-false}"
]
}
}
}For reproducible CI/CD testing, use pure mode:
TEST_PURE=1 devbox run test:e2eThis ensures:
- Fresh emulator with clean state
- No cached data from previous runs
- Emulator and app are stopped after tests
Run tests with TUI for real-time monitoring:
TEST_TUI=true devbox run test:e2eThe TUI shows process status, logs, and resource usage during test execution.
Customize test behavior with environment variables:
# Set emulator boot timeout (seconds)
BOOT_TIMEOUT=120 devbox run test:e2e
# Run headless (no emulator GUI)
EMU_HEADLESS=1 devbox run test:e2e
# Enable debug logging
ANDROID_DEBUG=1 devbox run test:e2eConfigure the plugin by setting environment variables in devbox.json:
{
"env": {
"ANDROID_DEFAULT_DEVICE": "max",
"ANDROID_BUILD_TOOLS_VERSION": "36.1.0",
"ANDROID_COMPILE_SDK": "36",
"ANDROID_TARGET_SDK": "36"
}
}Key variables:
ANDROID_DEFAULT_DEVICE- Default device when none specifiedANDROID_APP_APK- Path or glob pattern for APK (empty = auto-detect)ANDROID_BUILD_TOOLS_VERSION- Build tools versionANDROID_COMPILE_SDK- Compile SDK versionANDROID_TARGET_SDK- Target SDK version
In React Native projects or iOS-only contexts, skip Android SDK evaluation:
{
"env": {
"ANDROID_SKIP_SETUP": "1"
}
}This speeds up shell initialization when you only need iOS tooling.
Use an existing local Android SDK instead of Nix-managed SDK:
{
"env": {
"ANDROID_LOCAL_SDK": "1"
}
}Set ANDROID_SDK_ROOT to your local SDK path.
Limit which devices are evaluated to reduce initialization time:
{
"env": {
"ANDROID_DEVICES": "min,max"
}
}Customize emulator behavior:
{
"env": {
"EMU_HEADLESS": "1",
"EMU_PORT": "5554",
"ANDROID_EMULATOR_PURE": "1",
"ANDROID_DISABLE_SNAPSHOTS": "1"
}
}Options:
EMU_HEADLESS- Run emulator without GUI windowEMU_PORT- Preferred emulator port (default: 5554)ANDROID_EMULATOR_PURE- Always start fresh with clean stateANDROID_DISABLE_SNAPSHOTS- Disable snapshot boots, force cold boot
Display all configuration settings:
devbox run android.sh config showRun devbox shell after changing devbox.json to apply the new values. To reset to defaults, remove the overrides from your devbox.json.
Symptom: Emulator fails to start or times out during boot.
Solutions:
-
Check if hardware acceleration is available:
devbox run emulator -accel-check
-
Try starting with snapshot disabled:
ANDROID_DISABLE_SNAPSHOTS=1 devbox run start:emu
-
Reset emulator state:
devbox run android.sh emulator reset
-
Increase boot timeout:
BOOT_TIMEOUT=180 devbox run start:emu
Symptom: Error installing APK on emulator.
Solutions:
-
Verify the APK exists (check your build output directory):
find . -name '*.apk' -not -path '*/.gradle/*' -not -path '*/intermediates/*'
-
Check if app is already installed:
adb shell pm list packages | grep your.package.name -
Uninstall existing version:
adb uninstall com.example.myapp
-
Check emulator is fully booted:
adb shell getprop sys.boot_completed
Symptom: Commands fail with "ANDROID_SDK_ROOT not set" or SDK tools not found.
Solutions:
-
Re-enter devbox shell to reload the environment:
exit devbox shell -
Verify SDK installation:
devbox run android.sh info
Symptom: Warning about lock file checksum not matching device definitions.
Solution:
Regenerate the lock file:
devbox run android.sh devices evalCommit the updated lock file.
Symptom: Multiple emulators running on the same port.
Solutions:
-
Stop all emulators:
devbox run stop:emu
-
Specify different ports for each emulator:
EMU_PORT=5554 devbox run start:emu device1 EMU_PORT=5556 devbox run start:emu device2
Symptom: Gradle build fails with "SDK Build Tools version X not found".
Solutions:
-
Check available build tools:
devbox run android.sh info
-
Update build tools version in
devbox.json:{ "env": { "ANDROID_BUILD_TOOLS_VERSION": "36.1.0" } } -
Sync Gradle configuration with SDK version: Edit
app/build.gradle:android { compileSdk 36 buildToolsVersion "36.1.0" }
For detailed troubleshooting information, enable debug logging:
# Platform-specific debug
ANDROID_DEBUG=1 devbox shell
# Global debug
DEBUG=1 devbox shellDebug logs show:
- Environment variable resolution
- SDK path discovery
- Device configuration loading
- Emulator startup commands
- ADB communication
Test your app across multiple Android versions:
-
Create device definitions for each version:
devbox run android.sh devices create api21 --api 21 --device pixel --tag google_apis devbox run android.sh devices create api28 --api 28 --device pixel --tag google_apis devbox run android.sh devices create api36 --api 36 --device pixel --tag google_apis
-
Regenerate lock file:
devbox run android.sh devices eval -
Test on each device:
devbox run start:emu api21 # run your tests... devbox run stop:emu devbox run start:emu api36 # run your tests... devbox run stop:emu
Use the plugin in GitHub Actions:
name: Android CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Devbox
uses: jetify-com/devbox-install-action@v0.11.0
- name: Run E2E Tests
run: |
EMU_HEADLESS=1 TEST_PURE=1 devbox run test:e2eConfigure for headless operation:
{
"env": {
"EMU_HEADLESS": "1",
"ANDROID_EMULATOR_PURE": "1"
}
}Use specific system image tags:
# Google APIs (recommended for most apps)
devbox run android.sh devices create dev --api 34 --device pixel --tag google_apis
# Google Play Store (for testing in-app purchases)
devbox run android.sh devices create prod --api 34 --device pixel --tag google_apis_playstore
# AOSP (minimal Android, no Google services)
devbox run android.sh devices create minimal --api 34 --device pixel --tag default- Complete API Reference: See Android Reference for exhaustive documentation of all commands, environment variables, and configuration options
- Plugin Testing: See plugins/tests/android/ for plugin unit tests
- CI/CD Workflows: See .github/workflows/ for CI integration examples
- Plugin Development: See Conventions for plugin development patterns
- Device Management Guide: Deep dive into device definitions and management
- Testing Guide: Comprehensive testing strategies and best practices
- Troubleshooting Guide: Extended troubleshooting scenarios
- Quick Start: Get up and running quickly
- Android Example: Complete Android app with build scripts, deploy commands, and E2E test suites
- React Native Example: Cross-platform app using both Android and iOS plugins
- GitHub Issues: Report bugs or request features
- Devbox Documentation: jetify.com/devbox/docs
- Discord: Join the Jetify community for help and discussions