---
title: "Android"
source: "docs/decisions/ADR-0002-android.md"
---

> Part of [botgumi docs](https://www.botgumi.com/docs/llms.txt). Every page: [llms.txt](https://www.botgumi.com/llms.txt).

# ADR-0002: Android as a second native client

Status: accepted · 2026-10-18

## Context

ADR-0001 fixed the client side to native iOS. Apps built from botgumi also need to ship on Android,
natively, with the same provider interfaces, the same agent team and the same gates. The Swift package
already shows the shape: interfaces, no-ops and contract suites per concern, vendor code only in adapters.
Android needs the same shape in Kotlin, a track of its own, a store path, and CI.

## Decisions

1. **Two native client platforms.** Android joins iOS in the opinionated core (this amends ADR-0001,
   decision 5). Android apps are native: Kotlin, Jetpack Compose, Gradle with the Kotlin DSL and a version
   catalog. No cross-platform UI layer. An app lists its client platforms in `botgumi.config.ts`
   (`platforms`, default `["ios", "android"]`). Each platform is its own track (`ios/`, `android/`) with its
   own worker, CI status and store checklist; the generator creates only the chosen tracks.
2. **One Gradle build under `packages/kotlin/`.** Gradle needs no manifest at the repository root, so the
   Kotlin build lives next to the Swift sources (the Swift manifest stays at the root for SwiftPM's sake).
   Modules mirror the Swift modules one to one: `botgumi-auth`, `botgumi-api-client`,
   `botgumi-design-tokens`, `botgumi-analytics` (Analytics and Flags), `botgumi-analytics-amplitude`,
   `botgumi-crash`, `botgumi-crash-sentry`, `botgumi-purchases`, `botgumi-purchases-revenuecat`,
   `botgumi-contracts`. Group `dev.botgumi`, artifact = module name, version = the git tag, the same
   version as the Swift package. Apps depend on the Maven artifacts; the publish step lands with the first
   tagged release, so until then an app uses `includeBuild` on a checkout. The coordinates are fixed now so
   app code never changes.
3. **Interface modules are plain Kotlin/JVM.** They target JVM 17, depend on nothing but the Kotlin standard
   library, and use `suspend` where Swift uses `async`. So they build and test with any JDK 17+ and no Android
   SDK, here and on Linux CI. Adapter modules are stubs on the same footing today; each becomes an Android
   library (`com.android.library`, minSdk 26) when its vendor SDK lands, since those SDKs need Android.
   Only an adapter module may import a vendor SDK; the vendor-import lint covers Kotlin imports too.
4. **Sign-in on Android is Sign in with Google** through Credential Manager. `GoogleSignIn` returns a
   `GoogleCredential` (the ID token and the request nonce); `SessionExchange` sends it to the backend, which
   verifies it and returns the app session, as it does for an Apple credential. The backend's session-exchange
   endpoint accepts both providers; an app that turns Android on records that in `docs/api/openapi.yaml`.
   The session store is Android Keystore-backed in production, in memory in tests.
5. **Strings.** `res/values/strings.xml` holds en and `res/values-b+zh+Hans/strings.xml` holds zh-Hans
   (the BCP 47 qualifier matches the `zh-Hans` locale id used everywhere else). The Android CI fails when a
   key is missing in either, as the iOS CI does for the string catalog.
6. **CI.** Kotlin and Android run on GitHub's Linux runners, which are cheap, unlike macOS. This repo's CI gets
   a `kotlin` job (`./gradlew build` in `packages/kotlin`), and `pnpm verify` runs it locally too, so a JDK 17+
   is a requirement of this repo. Each app's Android CI is a GitHub workflow that runs `android/scripts/ci.sh`
   (lint, unit tests, strings check) and reports as the `android-ci` status. iOS stays local (ADR-0001, 10).
7. **Store.** Google Play, through the Play Developer API (the Gradle Play Publisher plugin). Builds go to the
   internal testing track first; release signing is Play App Signing, with the upload key in the secrets
   inventory. Human gate 7 becomes **store submission** and covers both stores. Play gets its own asset and
   compliance checklists (`docs/checklists/google-play-*.md`). The dashboard reads Play data through a
   `@botgumi/google-play` adapter, the twin of `@botgumi/asc`.
8. **Design tokens get a third output.** `generateKotlin` writes `DesignTokens.kt` into
   `botgumi-design-tokens` from the same source as the CSS and Swift outputs.
9. **Merge policy.** The required statuses include each chosen platform's CI status: `ios-ci` when iOS is
   on, `android-ci` when Android is on, plus `botgumi/review`.

## Consequences

- Every provider with a client side is now implemented twice (Swift and Kotlin). The contract suites are
  the same in both, so an adapter is done when it passes the suite on its platform.
- The Android SDK is needed only to build adapters with vendor SDKs and the apps themselves, not this repo.
- Kotlin packages get GitHub CI, which Swift does not have; the Swift build stays a local check.
- The backend worker owns the Google credential path of the session exchange; it is a contract change, so it
  goes through a change request with both reviewers.
- `@botgumi/i18n`'s `checkCatalog` has to read `strings.xml` as well as `.xcstrings` exports.
- The number of tracks grows to seven. The orchestrator opens one issue per track as before, so an
  Android-only or iOS-only app has one fewer.
