Start here
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
- 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. - 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. Groupdev.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 usesincludeBuildon a checkout. The coordinates are fixed now so app code never changes. - Interface modules are plain Kotlin/JVM. They target JVM 17, depend on nothing but the Kotlin standard
library, and use
suspendwhere Swift usesasync. 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. - Sign-in on Android is Sign in with Google through Credential Manager.
GoogleSignInreturns aGoogleCredential(the ID token and the request nonce);SessionExchangesends 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 indocs/api/openapi.yaml. The session store is Android Keystore-backed in production, in memory in tests. - Strings.
res/values/strings.xmlholds en andres/values-b+zh+Hans/strings.xmlholds zh-Hans (the BCP 47 qualifier matches thezh-Hanslocale id used everywhere else). The Android CI fails when a key is missing in either, as the iOS CI does for the string catalog. - CI. Kotlin and Android run on GitHub's Linux runners, which are cheap, unlike macOS. This repo's CI gets
a
kotlinjob (./gradlew buildinpackages/kotlin), andpnpm verifyruns it locally too, so a JDK 17+ is a requirement of this repo. Each app's Android CI is a GitHub workflow that runsandroid/scripts/ci.sh(lint, unit tests, strings check) and reports as theandroid-cistatus. iOS stays local (ADR-0001, 10). - 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-playadapter, the twin of@botgumi/asc. - Design tokens get a third output.
generateKotlinwritesDesignTokens.ktintobotgumi-design-tokensfrom the same source as the CSS and Swift outputs. - Merge policy. The required statuses include each chosen platform's CI status:
ios-ciwhen iOS is on,android-ciwhen Android is on, plusbotgumi/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'scheckCataloghas to readstrings.xmlas well as.xcstringsexports.- 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.