Files
web-terminal/android/README.md
Yaojia Wang 4fe1981997 chore(android): install Android SDK + wire AGP 9.2.1 (toolchain proven)
Android SDK installed on this machine (cmdline-tools via brew;
platform-tools + platforms;android-35 + build-tools;35.0.0), local.properties
points Gradle at it (gitignored). Wired AGP 9.2.1 into the version catalog +
google() repos; proved the full chain by building a throwaway android-library
module against SDK 35 (AAR produced), then removed the probe.

Groundwork for AW2+: AGP plugins in the catalog, google() in settings, and the
working recipe in README (incl. the AGP-9-has-built-in-Kotlin gotcha — Android
modules apply ONLY the android plugin, never kotlin.android). Pure JVM modules
still green (218 tests). Framework module stubs stay gated until implemented.
2026-07-08 17:37:54 +02:00

6.8 KiB

WebTerm — Android client

A native Android client for the WebTerm browser-terminal server, targeting functional parity with the shipped iOS client. See the full design in docs/ANDROID_CLIENT_PLAN.md (stack §2, module architecture §3, server contract §4, task waves §5).

This directory is a Gradle multi-module project. The module set mirrors the iOS SPM package set and inherits its rule: dependencies only flow down; nothing points upward (ARCHITECTURE §1).

⚠️ No-SDK constraint (why only 5 modules build here)

The current build environment has no Android SDK. Everything that can be pure Kotlin/JVM (kotlin("jvm")) is built and unit-tested now; anything that needs the Android framework (com.android.* plugins) is scaffolded but disabled.

  • Enabled now (pure Kotlin/JVM, ./gradlew test-able): :wire-protocol, :session-core, :api-client, :client-tls, :test-support.
  • Scaffolded but COMMENTED OUT in settings.gradle.kts (dirs + a build.gradle.kts stub exist, marked // TODO(android-sdk)): :app, :terminal-view, :host-registry, :client-tls-android.

To bring the Android modules online later: install an SDK, add local.propertiessdk.dir, add the Android Gradle Plugin + google() to pluginManagement, then uncomment the include(...) lines and the plugin blocks in each stub.

Module map (mirror of the iOS SPM packages — plan §3)

iOS SPM package Android module Kind Status
WireProtocol :wire-protocol pure Kotlin/JVM built
SessionCore (reducers) :session-core pure Kotlin/JVM built
APIClient :api-client pure Kotlin/JVM built
ClientTLS (pure half) :client-tls pure Kotlin/JVM built
TestSupport :test-support pure Kotlin/JVM (fakes) built
ClientTLS (fwk half) :client-tls-android Android (AndroidKeyStore/Tink) ⏸ SDK-gated
HostRegistry :host-registry Android (DataStore) ⏸ SDK-gated
SwiftTerm host view :terminal-view Android (Termux wrap) ⏸ SDK-gated
App/WebTerm :app Android app (Compose/Hilt/FCM) ⏸ SDK-gated

Not yet scaffolded: :transport-okhttp (OkHttp TermTransport/HttpTransport impls, JVM) is owned by task A7 and will be added then. The iOS URLSession*Transports consolidate into it (plan §3 framing note).

Dependency graph (arrows = "depends on")

                          :app  (SDK-gated)
        ┌───────────────┬───┴────┬──────────────┬───────────────┐
        ▼               ▼        ▼              ▼               ▼
 :terminal-view   :session-core :api-client  :host-registry  :client-tls-android
   (SDK-gated)         │          │           (SDK-gated)         │
        │              │          │                               ▼
        │              │          │                          :client-tls (pure)
        └──────┬───────┴──────────┴──────────────┬────────────────┘
               ▼                                  ▼
         :wire-protocol   ◀────────────  :transport-okhttp (A7, not yet)
               ▲
               └──────── :test-support  → test source sets only

:wire-protocol is the frozen shared contract (Android analogue of src/types.ts + WireProtocol) — ClientMessage/ServerMessage, MessageCodec, Validation, WireConstants, HostEndpoint (the single Origin/wsURL derivation), and the TermTransport / HttpTransport / PingableTermTransport boundary interfaces. New wire types are added only here (a coordination point).

Toolchain

  • Gradle 9.6.1 (via the committed wrapper — always use ./gradlew).
  • Kotlin 2.3.21 (matches the Kotlin embedded in Gradle 9.6.1).
  • JVM toolchain 17 (jvmToolchain(17) in every module).
  • Versions are pinned in the version catalog gradle/libs.versions.toml: kotlinx-serialization-json, kotlinx-coroutines-core/-test, JUnit5 (Jupiter), Turbine, MockK.

Pure modules apply kotlin("jvm") + kotlin("plugin.serialization"), wire the libs.bundles.unit-test bundle into testImplementation, and run tests on the JUnit Platform (tasks.test { useJUnitPlatform() }).

Build & test

# Use the committed wrapper for everything.
./gradlew help          # sanity: the build configures
./gradlew projects      # lists the 5 pure modules
./gradlew build         # compile all pure modules
./gradlew test          # run JVM unit tests (JUnit5 + coroutines-test + Turbine + MockK)

Testing target: ≥80% Kover coverage on the pure modules (:wire-protocol, :session-core, :api-client, :client-tls pure half). TDD, immutable data, small focused files — same discipline as the rest of the repo.

Android SDK setup (proven working)

The pure JVM modules need only a JDK + Gradle. The Android-framework modules (:app, :terminal-view, :host-registry, :client-tls-android — plan AW2+) need the Android SDK. This machine is set up and the toolchain is proven (an AGP library module compiled against SDK 35 and produced an AAR):

  • SDK location: /usr/local/share/android-commandlinetools (installed via brew install --cask android-commandlinetools).
  • Installed packages: platform-tools, platforms;android-35, build-tools;35.0.0.
  • android/local.properties (gitignored) points Gradle at it: sdk.dir=/usr/local/share/android-commandlinetools.
  • Shell env (for sdkmanager/adb): export ANDROID_HOME=/usr/local/share/android-commandlinetools.

Wiring an Android module (the working recipe)

  • Repos: google() is in both pluginManagement and dependencyResolutionManagement in settings.gradle.kts (needed to resolve AGP + androidx).
  • Plugin: AGP 9.2.1 (libs.plugins.android.library / .android.application), compatible with Gradle 9.6.1.
  • Gotcha: AGP 9 has built-in Kotlin — apply ONLY the android plugin. Adding org.jetbrains.kotlin.android errors with "no longer required since AGP 9.0".
  • Module block: android { namespace = "…"; compileSdk = 35; defaultConfig { minSdk = 29 } }.

To add more SDK pieces later (e.g. an emulator image for instrumented tests): sdkmanager "system-images;android-35;google_apis;arm64-v8a" "emulator".