# Android toolchain

> Build, run, test and ship Android apps and libraries with Elide, without Gradle: create a Compose app, run it on an emulator, and find the commands.

Elide builds, runs, tests and ships Android applications and libraries from `elide.pkl`, with no Gradle and no
Android Gradle Plugin. The Android SDK's own tools (`aapt2`, `d8`, R8, `zipalign`, `apksigner`, `bundletool`) run as
build tasks, so `elide build --inspect` lists every step and a second build runs only what changed.

## Create and run an app

```bash
mkdir hello-android
cd hello-android
elide init --template android --plain --skip-defaults -- package=com.example.hello
elide run
```

On a machine with nothing but Elide installed, `elide run`:

1. Looks for an Android SDK in `ANDROID_HOME`, `ANDROID_SDK_ROOT` and the location Android Studio installs it in,
   and uses Elide's managed SDK under `~/.elide/toolchains/android` when none is found ([Android SDK](https://elide.help/docs/toolchain/android/sdk)).
2. Asks once to accept the Android SDK licenses, then installs the platform, build-tools, platform-tools, emulator
   and system image the project needs.
3. Builds the `app` APK and signs it with the Android debug keystore, `~/.android/debug.keystore`, created on first
   use.
4. Installs it on the one attached device, or creates and boots the project's emulator when none is attached.
5. Launches the app and prints its log until you press Ctrl+C or the app exits.

The license question is asked only on a terminal. In CI, accept the licenses explicitly and install first:

```bash
elide install --accept-android-licenses
elide build
```

The emulator needs hardware acceleration. On Linux that is `/dev/kvm` and membership of the `kvm` group; without
it the emulator fails before booting and says so ([Headless hosts, CI and WSL](https://elide.help/docs/toolchain/android/run#headless-hosts-ci-and-wsl)).

## The template

```text
elide.pkl
src/main/kotlin/com/example/hello/MainActivity.kt
src/androidTest/kotlin/com/example/hello/MainActivityTest.kt
```

```pkl
amends "elide:project.pkl"

import "elide:Android.pkl" as Android

name = "android-app"
version = "1.0.0"

android {
  namespace = "com.example.hello"
  compileSdk = 36
  minSdk = 30

  // Generates `AndroidManifest.xml`; a `src/main/AndroidManifest.xml`, if added, is merged over it.
  application {
    label = "@string/app_name"
    theme = "@android:style/Theme.Material.Light.NoActionBar"
    allowBackup = false
    activities { ["MainActivity"] { launcher = true } }
  }
  resources {
    strings {
      ["app_name"] = "Elide Android App"
      ["greeting"] = "Hello Elide!"
    }
  }
}

kotlin {
  features {
    compose = true
  }
}

dependencies {
  android {
    androidx { ["activity-compose"] = "1.9.3" }
    compose { version = "1.7.8"; modules { "foundation"; "ui-tooling" } }
    test { runner = "1.6.2"; modules { "ext-junit" } }
  }
}

artifacts {
  ["app"] = new Android.AndroidApk {}
}
```

- `android` declares the namespace (the package `R` lives in, and the application id), the API levels, the
  application's manifest entries and its strings. No `AndroidManifest.xml` or `strings.xml` is needed
  ([Manifest and resources](https://elide.help/docs/toolchain/android/manifest)).
- `kotlin.features.compose` turns on the Compose compiler, and `dependencies.android` declares the Compose,
  AndroidX and AndroidX Test libraries by family ([Android dependencies](https://elide.help/docs/toolchain/android/dependencies)).
- `app` leaves `release` unset, so it is a debuggable build signed with the debug keystore; `elide build --release`
  builds it as a release instead ([Signing and release](https://elide.help/docs/toolchain/android/release)).
- `src/androidTest` holds an instrumented test, which `elide test --device` runs on a device
  ([Android tests](https://elide.help/docs/toolchain/android/testing)).

`elide init --template android --parameters` lists the template's parameters (`project_name`, `app_name`,
`package`, `greeting`).

## Commands

Android projects use the commands every project has. Device behaviour is behind flags, and each command runs a build
target that `elide build` can also run by name ([Android build targets](https://elide.help/docs/toolchain/android/targets)).

| Command | Does |
| --- | --- |
| `elide run [app]` | Build, install, launch and follow the app's log. |
| `elide run --emulator` | The same, on the project's emulator. |
| `elide run --debugger[=port]` | Launch suspended and forward a JDWP port. |
| `elide run --watch` | Rebuild, reinstall and relaunch on every source change. |
| `elide test` | Host tests, under Robolectric. |
| `elide test --device` | Instrumented tests on a device or emulator. |
| `elide build [artifact]` | Build APKs, bundles and libraries. |
| `elide install` | Install dependencies and the SDK components the project needs. |
| `elide project info` | The SDK, its components, devices and emulators. |

## Artifacts

| Artifact | Publishes |
| --- | --- |
| `Android.AndroidApk` | An application package: `.dev/artifacts/<name>.apk`. |
| `Android.AndroidAab` | An app bundle for a store: `.dev/artifacts/<name>.aab`. |
| `Android.AndroidAar` | A library archive: `.dev/artifacts/<name>.aar`. |
| `Android.AndroidFeature` | A dynamic feature module inside a bundle. |

An application consumes the libraries of its own workspace and published AARs alike: classes, resources,
manifests, assets and native libraries merge in one pass ([Manifest and resources](https://elide.help/docs/toolchain/android/manifest#libraries)).

## Limits

- Android support ships with every standard Elide distribution. A custom build made without it
  (`--no-android`) refuses to build, run or test an Android project, and says so.

- There are no build types or product flavors. Declare one artifact per variant, with its own `applicationId`,
  `release` and resource directories.
- No `BuildConfig` class is generated, and Android Lint does not run.
- Host tests run against one artifact of a project, chosen by `android.hostTests`.
- `src/androidTest` instruments one application: the first APK that is neither a test APK nor minified.
- A bundle with dynamic feature modules cannot be minified.
- A Java resource that several jars carry ships from the first; only `META-INF/services` files are merged.
- `elide lsp` serves Java sources only; Kotlin sources get no language server from it.
