M12 — the Android client, end to end #2

Merged
bvandeusen merged 86 commits from dev into main 2026-08-21 08:53:58 -04:00
Showing only changes of commit c28f2bc00e - Show all commits
+117
View File
@@ -0,0 +1,117 @@
# Running the Android client locally
The Android app is the same Vue frontend and Rust core as the desktop, built by
Tauri v2 mobile. This sheet is the local setup; CI's copy of it is the
`ci-tauri-android` image (see `ci-requirements.md`).
**Open this directory in Android Studio:**
```
desktop/src-tauri/gen/android
```
Two things must be true first, and both fail confusingly if they aren't.
## 1. Gradle must run on JDK 17
Android Studio defaults to its own bundled JDK, which is currently 25. That
produces:
> Incompatible Gradle JVM version — The project's Gradle version 8.14.3 is
> incompatible with the Gradle JVM version 25 currently selected to run Gradle
> build.
Fix: `Settings → Build, Execution, Deployment → Build Tools → Gradle → Gradle
JDK`**17**. If none is listed, `Download JDK…` → version 17, vendor Eclipse
Temurin.
Prefer 17 explicitly over Android Studio's "apply a compatible JDK" quick-fix,
which will likely pick 21. 17 is what `ci-tauri-android` pins, and matching it
means a local build and a CI build resolve identically.
**Why 8.14.3 and not Gradle 9:** `cargo tauri android init` generated the wrapper
pin, and Gradle 9 removed `project.exec`, which the generated
`buildSrc/.../BuildTask.kt` still uses. The wrapper, the AGP pin and that file are
all tracked in this repo, so the bump is ours to make whenever it is worth doing —
it is scaffolding we inherited, not a requirement of Tauri. Tauri's own Android
layer targets `compileSdk 36` and registers back handling through
`OnBackPressedDispatcher`, so the library is current; only the template trails.
## 2. The Gradle glue has to be generated once
`settings.gradle` does `apply from: 'tauri.settings.gradle'`, and that file — with
`app/tauri.build.gradle.kts`, `app/tauri.properties`,
`app/src/main/assets/tauri.conf.json` and the compiled `.so` files — is generated
per build and gitignored. A fresh clone does not have them, and settings
evaluation happens before any Gradle task, so the sync fails before anything can
generate them.
From `desktop/src-tauri`:
```sh
cargo tauri android build --debug --target x86_64
```
After that, Android Studio syncs cleanly.
## Prerequisites
Gradle shells out to `cargo tauri android android-studio-script` on every build,
so the Rust toolchain is required on the machine running Android Studio — the CI
image cannot stand in for it.
```sh
rustup toolchain install 1.97.1
rustup target add aarch64-linux-android x86_64-linux-android
cargo install tauri-cli --version 2.11.4 --locked
```
Via the SDK Manager, matching `CI-tauri-android/versions.env`:
| | version | why it matters |
|---|---|---|
| Platform | API 36 | `compileSdk` / `targetSdk` |
| Build-Tools | 35.0.0 | AGP 8.11's default |
| **NDK** | 28.2.13676358 | the Rust core cannot cross-compile without it |
| Platform-Tools | latest | `adb` |
```sh
export ANDROID_HOME=$HOME/Android/Sdk
export NDK_HOME=$ANDROID_HOME/ndk/28.2.13676358
```
Node is needed too — `beforeBuildCommand` runs `npm ci && npm run build`, because
`generate_context!` compiles the frontend into the binary.
## Emulator
Create an AVD with an **x86_64** system image, API 24 or newer (`minSdk = 24`).
CI builds arm64-v8a and x86_64, so either runs, but x86_64 executes natively on a
desktop instead of being translated.
## The dev loop
Android Studio's Run button rebuilds the whole Rust core each time. For
iteration, with an emulator already running:
```sh
cd desktop/src-tauri
cargo tauri android dev
```
That starts Vite, wires `adb reverse` so the emulator reaches
`localhost:5173`, and hot-reloads frontend changes without a rebuild. Android
Studio earns its keep for the debugger, logcat and layout inspection.
## Known gaps, so they are not mistaken for bugs
- **Safe areas / viewport** are not handled yet (task 2706) — content may sit
under a notch or the gesture bar. Deliberately left until a device could
verify it.
- **`android:enableOnBackInvokedCallback`** is absent from the manifest, so
predictive-back animations will not engage on Android 13+. Tauri registers the
dispatcher correctly; only the opt-in flag is missing.
- **`android:usesCleartextTraffic`** is templated in app-wide. A
`networkSecurityConfig` resource is the better shape — see Scribe note 2437,
which hit this on Minstrel.
- Debug builds are large and slow to start. Do not read either as the product.