---
title: Install the Android SDK
description: Add the Extentos Android SDK to your Gradle project from Maven Central. Coordinates, minSdk 31, Kotlin/AGP/Gradle floors, and the assistant runtime.
type: guide
platform: android
vendor: meta
related:
  - /docs/sdk/android/manifest
  - /docs/sdk/android/initialization
  - /docs/getting-started/android
  - /docs/sdk/android/runtime-internals
---

The Extentos Android SDK ships five artifacts on **Maven Central**. Most apps need the first two:

- `com.extentos:glasses` — the core runtime (`ExtentosGlasses`, capability clients, transports)
- `com.extentos:glasses-ui` — the prebuilt connection page (`ExtentosConnectionPage`). Optional on a voice-only app, which renders no connection page.
- `com.extentos:glasses-meta` — **optional**; the Meta Ray-Ban transport (camera, display, the DAT device session). Only apps that use camera or display need it.
- `com.extentos:glasses-local` — **optional**; on-device text models for the [local tier](/docs/concepts/local-models).
- `com.extentos:glasses-local-voice` — **optional**; on-device speech for the local tier.

The Extentos artifacts resolve from the default `mavenCentral()` repository. `com.extentos:glasses` and `glasses-ui` have **no vendor dependency**, so a voice-only app needs nothing beyond a normal Gradle dependency — no extra repository, no token.

The Meta DAT dependencies (`com.meta.wearable:mwdat-*`) come in through **`com.extentos:glasses-meta`** only. They live on **Meta's GitHub Packages**, which needs one extra repo declaration plus a GitHub token (step 1). Skip that step entirely if your app doesn't use camera or display.

## 1. Declare the repositories

**Voice-only apps can skip this whole step.** Extentos resolves from Maven Central, which is already in the default Android template.

If you add `com.extentos:glasses-meta` for camera or display, its **Meta DAT** artifacts resolve from **Meta's GitHub Packages**, which requires a GitHub personal access token with the `read:packages` scope. Add both to your `settings.gradle.kts`:

```kotlin title="settings.gradle.kts"
// pluginManagement MUST be the first block in a settings script — Gradle
// aborts configuration otherwise. Without it, `id("com.android.application")`
// can't resolve: AGP lives on google(), and Gradle's default plugin repo is
// gradlePluginPortal() only.
pluginManagement {
    repositories { google(); mavenCentral(); gradlePluginPortal() }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
        // Transitive Meta DAT artifacts (com.meta.wearable:mwdat-*) live here:
        maven {
            url = uri("https://maven.pkg.github.com/facebook/meta-wearables-dat-android")
            credentials {
                username = "token"
                password = (providers.gradleProperty("github_token").orNull
                    ?: System.getenv("GITHUB_TOKEN")) ?: ""
            }
        }
    }
}
```

…followed by your module declarations:

```kotlin title="settings.gradle.kts (continued)"
rootProject.name = "myapp"
include(":app")
```

`FAIL_ON_PROJECT_REPOS` means a `repositories { }` block in `app/build.gradle.kts` becomes a hard build failure — declare every repository here instead.

Create the token at **github.com → Settings → Developer settings → Personal access tokens** with the `read:packages` scope, then export it as `GITHUB_TOKEN`. That is the one location every surface agrees on — the block above reads it, the scaffold's variant reads it, and `extentos-mcp setup` checks it. (`~/.gradle/gradle.properties` as `github_token=...` also satisfies the block above, but the scaffold's variant and the diagnostic don't look there, so you can end up with a working build and a diagnostic that reports it missing — or the reverse.) To check your setup, run **`npx -p @extentos/mcp-server extentos-mcp setup`** from the project root — a read-only diagnostic that validates the token against GitHub Packages and prints exact remediation; it reads `GITHUB_TOKEN` or the project's `local.properties` and does **not** store the token for you. (`generateConnectionModule` emits a variant of this repo block that reads `local.properties`/env instead of the Gradle property — same URL, same key names.) Without the token the build fails at first sync with `Could not resolve com.meta.wearable:mwdat-core`.

## 2. Add the dependency

```kotlin title="app/build.gradle.kts"
dependencies {
    implementation("com.extentos:glasses:2.1.2")
    implementation("com.extentos:glasses-ui:2.1.2")

    // ONLY if your app uses the camera or the display. This is what brings the
    // Meta DAT artifacts (and therefore the repository + token in step 1).
    // Voice-only apps omit it entirely.
    implementation("com.extentos:glasses-meta:2.1.2")
}
```

You don't declare the Meta DAT SDK (`com.meta.wearable`) yourself — it comes in transitively through `com.extentos:glasses-meta`, and *only* through that module. It resolves from the GitHub Packages repo added in step 1, so the `read:packages` token must be in place for a camera/display build to succeed. A voice-only app never pulls those artifacts and needs no token.

`glasses-meta` registers itself at app start, so adding the dependency is the whole integration — `TransportChoice.Auto` resolves to real glasses with no extra code.

## 3. Set the build floors

The library is built against these versions. Match or exceed them.

| Requirement | Value | Why |
|---|---|---|
| `minSdk` | **31** (Android 12) | Meta DAT mandates it. The Bluetooth runtime-permission model the SDK uses is Android 12+. |
| `compileSdk` | **35** | Transitive deps require it. |
| Kotlin | **2.1.20** | The AAR is compiled with 2.1.20; older Kotlin emits `Class file was compiled with a newer version` metadata warnings. |
| Android Gradle Plugin | **8.6.0+** | Compose deps in `glasses-ui` declare an AGP 8.6 floor. |
| Gradle | **8.7+** | Required by AGP 8.6. |
| Java/Kotlin target | **17** | The library targets JVM 17. |

The library does **not** declare a `targetSdk` — your app's `targetSdk` is inherited as-is. `targetSdk 34` or `35` both work for most apps. (`getPermissions` reports `34` as the value it derived its permission advice against; that's the floor it reasoned from, not a requirement on you.)

You also need two build features that a fresh AGP 8 project leaves **off** by default:

```kotlin title="app/build.gradle.kts"
plugins {
    id("com.android.application") version "8.6.0"
    id("org.jetbrains.kotlin.android") version "2.1.20"
    // Required if you use ExtentosConnectionPage or any Compose UI. From Kotlin
    // 2.0 the Compose compiler is a separate plugin — same version as Kotlin.
    id("org.jetbrains.kotlin.plugin.compose") version "2.1.20"
}

android {
    // AGP 8 removed the manifest-package fallback: without a namespace the build
    // fails with "Namespace not specified."
    namespace = "com.example.myapp"
    compileSdk = 35

    defaultConfig {
        minSdk = 31
        // targetSdk = 35  // your choice; the library doesn't impose one
    }

    buildFeatures {
        // AGP 8 defaults this to false. The SDK's own initialization snippet
        // reads BuildConfig.DEBUG, and the MCP scaffold emits buildConfigField
        // entries (EXTENTOS_SESSION_URL), so this must be on.
        buildConfig = true
        compose = true   // only if you use Compose / ExtentosConnectionPage
    }

    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions { jvmTarget = "17" }
}
```

<Callout type="warn">
  **`buildConfig = true` is not optional.** Every canonical initialization
  snippet branches on `BuildConfig.DEBUG` to pick the environment, and the
  browser-simulator loop reads `BuildConfig.EXTENTOS_SESSION_URL`. On AGP 8 with
  the feature off, `BuildConfig` does not exist and those lines don't resolve.
</Callout>

### The project key (production gateway attribution)

If your app uses the assistant, add your account-bound project key as a `buildConfigField` so production gateway usage attributes to your Extentos account:

```kotlin title="app/build.gradle.kts"
android {
    defaultConfig {
        buildConfigField("String", "EXTENTOS_PROJECT_KEY", "\"pk_your_project_key\"")
    }
}
```

The SDK reads it reflectively and sends it as the `x-extentos-project-key` header. (Meta's App ID and Client Token go through `resValue` / `R.string` instead, not `buildConfigField` — those rotate, and `BuildConfig` string constants are compile-time-inlined so a stale one can survive a rotation. The project key is regenerated by re-running the scaffold, which rewrites this line, so the same hazard doesn't apply.) `generateConnectionModule` mints the key and emits this exact line for you (it appears in the response's `buildConfigFields[]`); if you are wiring the project by hand, get the key from your dashboard project. In the **simulator** it is unused — attribution there comes from the session token. On a **sideloaded development build on a real phone** it is load-bearing: such a build can't attest (Play Integrity doesn't recognise it), so the key is how the gateway identifies the caller. Without it, and without a simulator-session binding, `assistant.start(...)` fails with `AssistantError.NoApiKey`. It never causes a *build* error, so the only symptom is at runtime — see [gateway identity](/docs/concepts/ai-gateway#identity-how-the-gateway-knows-whos-calling). Don't commit it to a public repo; re-run `generateConnectionModule` to rotate.

Some dependencies are yours, not the SDK's. `glasses-ui` brings the Compose runtime *it* needs, not the ones your UI code uses:

```kotlin title="app/build.gradle.kts"
// The SDK's suspend surface needs a dispatcher from your side
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
// Only if you follow the docs' Compose idiom (collectAsStateWithLifecycle)
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.6")
// Your own Compose stack, if you have UI of your own
implementation(platform("androidx.compose:compose-bom:2024.09.03"))   // any recent BOM works — declare only one
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.9.2")
```

**If you started from the Android Studio template**, its root `build.gradle.kts` already declares these plugins with `apply false` and a version. Declaring a version in both places fails with *"plugin request must not include a version"* — keep one. Either drop the `version` from the block above, or delete the root declarations. A `libs.versions.toml` catalog works too; the SDK doesn't care which you pick.

## 4. Sync and verify

Run a Gradle sync (or `./gradlew :app:dependencies`) and confirm `com.extentos:glasses` resolves. Next, wire it up:

- [Manifest setup](/docs/sdk/android/manifest) — add `CAMERA` / `RECORD_AUDIO` / `INTERNET`
- [Initialization](/docs/sdk/android/initialization) — `ExtentosGlasses.create(...)`

## The assistant runtime

The end-to-end voice **assistant** (`glasses.assistant.*` — wake/sleep state machine, barge-in, `session.say(...)`) ships in the stable **`1.4.0`** release on Maven Central — the standard dependency above includes it. No snapshot, no extra repository, no special setup. See [the assistant page](/docs/concepts/assistant) for the API surface and [the voice-assistant guide](/docs/guides/voice-assistant) for an end-to-end build.

## Source

- **Artifact:** [`com.extentos:glasses` on Maven Central](https://central.sonatype.com/artifact/com.extentos/glasses)
