# Android dependencies

Android libraries are declared by family and short name in `dependencies.android`. Each expands to ordinary Maven
coordinates when the manifest is evaluated, so `elide project info`, `elide project export` and every other
consumer of the manifest see plain coordinates.

```pkl
dependencies {
  android {
    androidx { ["activity-compose"] = "1.9.3"; ["core-ktx"] = "1.15.0" }
    compose { bom = "2025.10.00"; modules { "ui"; "foundation"; "material3"; "ui-tooling" } }
    material = "1.12.0"
    playServices { ["location"] = "21.3.0" }
    test { runner = "1.6.2"; modules { "ext-junit" }; espresso { "core" } }
    robolectric = "4.16"
  }
}
```

| Family | Expands to | Scope |
| --- | --- | --- |
| `androidx` | `androidx.<group>:<artifact>:<version>` | app |
| `compose` | `androidx.compose.<group>:<module>`, with the BOM or one version | app |
| `material` | `com.google.android.material:material:<version>` | app |
| `playServices` | `com.google.android.gms:play-services-<name>:<version>` | app |
| `test` | `androidx.test:runner` and the named AndroidX Test and Espresso libraries | instrumented tests |
| `robolectric` | `org.robolectric:robolectric` and the JUnit vintage engine | host tests |

Anything a family does not name goes in `dependencies.maven`; the two merge:

```pkl
dependencies {
  android {
    androidx { ["core-ktx"] = "1.15.0" }
  }
  maven {
    packages { "com.squareup.okhttp3:okhttp:4.12.0" }
    testPackages { "junit:junit:4.13.2" }
    androidTestPackages { "androidx.test.uiautomator:uiautomator:2.3.0" }
  }
}
```

| Bucket | Reaches |
| --- | --- |
| `packages` | The app: compiled against and dexed into the APK. |
| `compileOnly` | Compilation only; never dexed. |
| `testPackages` | Host tests (`src/test`). |
| `androidTestPackages` | Instrumented tests (`src/androidTest`); never the app or host tests. |

## Short names

An AndroidX short name is the group's segments after `androidx.` joined by `-`, then the rest of the artifact's
name. The longest matching group wins:

| Short name | Coordinate |
| --- | --- |
| `core-ktx` | `androidx.core:core-ktx` |
| `activity-compose` | `androidx.activity:activity-compose` |
| `lifecycle-runtime-ktx` | `androidx.lifecycle:lifecycle-runtime-ktx` |
| `wear-compose-material3` | `androidx.wear.compose:compose-material3` |
| `arch-core-runtime` | `androidx.arch.core:core-runtime` |

The groups are those Google's repository lists, less Compose, AndroidX Test and Gradle plugins. A name no family
knows (a group it does not list, an unknown Espresso or Play services library) fails evaluation, naming the
family and the name:

```text
dependencies.android.playServices: no library of the `playServices` family is named "locations"
```

A misspelt artifact within a known AndroidX group passes evaluation and fails resolution.

## Compose

`compose` takes `bom` or `version`, never both. Modules are named the same way, by the group's segments under
`androidx.compose.`: `ui`, `ui-tooling`, `ui-tooling-preview`, `foundation`, `material3`,
`material-icons-extended`, `runtime-livedata`.

```pkl
// Versions from the BOM
compose { bom = "2025.10.00"; modules { "ui"; "material3" } }

// Every module at one release
compose { version = "1.7.8"; modules { "ui"; "foundation" } }
```

With `bom`, the modules are declared without a version and take the one the BOM lists; a module the BOM does not
list fails resolution, naming it. With `version`, every module is declared at that release. Material 3 is released
on its own schedule, so take it from the BOM or declare it in `maven` at its own version.

The Compose compiler is a separate switch: set `kotlin.features.compose = true` in a module with Compose sources
([Kotlin compiler](/docs/toolchain/jvm/kotlinc#compose)).

## AndroidX Test

AndroidX Test's libraries are released together but versioned apart: runner 1.6.2 ships with Espresso 3.6.1 and
`ext:junit` 1.2.1. `test.runner`'s major and minor version select the release, and every other library is taken at
its version in that release:

| `runner` | `core`, `rules` | `ext-junit` | Espresso |
| --- | --- | --- | --- |
| `1.5.x` | 1.5.0 | 1.1.5 | 3.5.1 |
| `1.6.x` | 1.6.1 | 1.2.1 | 3.6.1 |
| `1.7.x` | 1.7.0 | 1.3.0 | 3.7.0 |

| Key | Names |
| --- | --- |
| `modules` | `core`, `core-ktx`, `rules`, `ext-junit`, `ext-junit-ktx`, `ext-truth` |
| `espresso` | `core`, `contrib`, `intents`, `accessibility`, `web`, `idling-resource`, `remote` |

A runner from a release not listed may be declared alone; naming another library under it fails evaluation and says
to declare that library in `dependencies.maven.androidTestPackages`. `test` resolves into the instrumented-test
scope only ([Android tests](/docs/toolchain/android/testing#instrumented-tests)).

## Robolectric

`robolectric` adds `org.robolectric:robolectric` to the host tests, and the JUnit vintage engine that runs its
JUnit 4 runners under `elide test`, at the JUnit Platform release Elide runs tests with
([Android tests](/docs/toolchain/android/testing#host-tests)).

## Google Maven

A project declaring `android` or `dependencies.android` resolves against Google's Maven repository,
`https://dl.google.com/dl/android/maven2`, after the default repositories, without declaring it. It appears in
`dependencies.maven.repositories` as `google`. A manifest that already declares Google's repository keeps its own,
and `enableDefaultRepositories = false` adds nothing, since a mirror then serves it.

## Published AARs

No coordinate needs an `aar` type. Packaging is read from the library's Gradle module metadata when it publishes
one, else from its POM, for a declared coordinate and a transitive one alike. Gradle module metadata also selects
Android variants, follows `available-at` redirects, and applies published version constraints and platforms.

Each resolved `.aar` is unpacked once per project under `.dev/android/libraries/`. Its `classes.jar` goes on the
compile and runtime classpaths, its resources are linked, its manifest merged, its assets and native libraries
packaged, and its `proguard.txt` applied when the app is minified.

## Dagger and Hilt

In an Android project, annotation processors declared in `dependencies.maven.processors` run through kapt before
the Kotlin sources compile, over Kotlin and Java sources together. kapt needs a managed Kotlin toolchain (or a Kotlin
distribution that ships it):

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

import "elide:Android.pkl" as Android
import "elide:Kotlin.pkl" as Kotlin

name = "di-app"

kotlin {
  toolchain = new Kotlin.ManagedKotlinToolchain { version = "2.2.21" }
}

android {
  namespace = "com.example.app"
  compileSdk = 36
  hilt = true
}

dependencies {
  maven {
    packages { "com.google.dagger:hilt-android:2.57.2" }
    processors { "com.google.dagger:hilt-compiler:2.57.2" }
  }
}

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

- Declare `dagger-compiler` for plain Dagger or `hilt-compiler` for Hilt in `processors`, at the runtime's version.
- `android.hilt = true` rewrites `@HiltAndroidApp` and `@AndroidEntryPoint` classes to extend their generated
  `Hilt_*` base, as Hilt's Gradle plugin does. Plain Dagger needs no flag.
- `@InstallIn` bindings in workspace libraries reach the application's component; libraries holding bindings run
  the Hilt processor too.
- Generated sources are under `.dev/jvm/generated-sources/<source set>/kapt`.

KSP-only processors do not run in Android project builds.
