# Android

The [desktop application](/documentation/configuration/app) of a Récit site also builds for Android:
the same `app:` section, the same shell, an `.apk` or an `.aab` at the end. Nothing changes in the
site: the pages are the `dist/` of `recit-site build`, served offline by the shell.

## Prerequisites

Everything of the [desktop prerequisites](/documentation/configuration/app#prerequisites-desktop)
except WebKitGTK (Android has its own WebView), plus:

- the Android targets of Rust:
  `rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android`;
- a JDK 17 (`openjdk-17-jdk-headless` on Debian and Ubuntu, a Temurin archive elsewhere),
  `JAVA_HOME` set;
- the Android SDK through `sdkmanager` (the command-line tools, downloaded from
  [developer.android.com](https://developer.android.com/studio#command-line-tools-only), unpacked in
  `$ANDROID_HOME/cmdline-tools/latest`): `platform-tools`, `platforms;android-37`,
  `build-tools;36.0.0` (what the project generated by Tauri 2.12 compiles against; Gradle downloads
  a missing one itself once the licences are accepted) and `ndk;27.2.12479018`, licences accepted
  (`sdkmanager --licenses`); `ANDROID_HOME` and `NDK_HOME` set;
- for `recit app android dev` only: an emulator, or a device with USB debugging reached by `adb`.

Nothing needs a screen: a Linux machine without a display builds the packages.

```bash
export JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
export ANDROID_HOME="$HOME/Android/Sdk"
export NDK_HOME="$ANDROID_HOME/ndk/27.2.12479018"
export PATH="$ANDROID_HOME/platform-tools:$PATH"
```

When one of them is missing, `recit app android …` stops and lists what to install.

## Section

| Key                     | Default                   | Meaning                                                                                                                                 |
| ----------------------- | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `android.minSdkVersion` | `24` (Android 7.0)        | the oldest Android the application installs on; becomes `minSdk` of the project                                                         |
| `version`               | the site's `package.json` | must be `0.0.1` or more: Tauri refuses `0.0.0` on Android; `versionCode` is derived from it (major × 1 000 000 + minor × 1 000 + patch) |
| `identifier`            | see the app page          | becomes the `applicationId` of the store, final once published; no hyphen allowed                                                       |
| `icon`                  | `metadata.favicon`        | the launcher icons (`mipmap-*`) are derived from it                                                                                     |

Android 12 and later derive the splash screen from the icon: there is nothing to draw.

## Commands

| Command                            | Effect                                                                                                                          |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `recit app android init`           | generates the Android project once, in `.recit/app/gen/android/` (ignored by git), from the `app:` section                      |
| `recit app android dev -- --host`  | runs the application in the emulator or on the device against `recit-site dev`, reached over the local network, with hot reload |
| `recit app android build -- --apk` | the packages for the four ABIs; `-- --target aarch64 --apk` limits them to one, `-- --aab` adds the bundle for the Play Store   |
| `recit app build --target android` | the site build followed by `recit app android build`                                                                            |

The arguments after `--` go to `tauri android …` as they are. Two of them matter on a machine
without a terminal: `--ci` skips every prompt (GitHub Actions sets `CI=true`, which does the same),
and `--skip-targets-install` keeps Tauri from calling `rustup` when the targets are already there.

The packages land in `.recit/app/gen/android/app/build/outputs/`: `apk/universal/release/` for the
`.apk`, `bundle/universalRelease/` for the `.aab`. Installing on a device is
`adb install <file>.apk`.

## Signing

A release build installs only when it is signed, and the Play Store accepts only a signed `.aab`.
The keystore never enters the repository: it is named by the environment, and `@recit/app` writes
`gen/android/keystore.properties` from it and wires the release build type of the generated Gradle
project to that file (an `android {}` block appended once to `app/build.gradle.kts`, marked
`recit:signing`). Without a keystore the build is unsigned, with a warning.

| Variable                          | Meaning                                         |
| --------------------------------- | ----------------------------------------------- |
| `RECIT_ANDROID_KEYSTORE`          | path of the `.jks` or `.keystore` file          |
| `RECIT_ANDROID_KEYSTORE_PASSWORD` | its password                                    |
| `RECIT_ANDROID_KEY_ALIAS`         | the alias of the upload key                     |
| `RECIT_ANDROID_KEY_PASSWORD`      | the password of the key, the store's when unset |

A keystore for tests, never for a store, comes from the JDK:

```bash
keytool -genkeypair -keystore upload.jks -alias upload -keyalg RSA -keysize 2048 \
  -validity 10000 -storepass change-me -keypass change-me -dname "CN=Lorem Ipsum, O=Example, C=FR"
```

The key of a published application is final, like the identifier: keep the keystore and its
passwords out of git, and lose neither.

:::note{title="Work in progress"}

The wiring of the signing is a function of `@recit/app`, `prepareAndroidSigning(shellDir, env)`,
which `recit app android build` does not call yet. Until it does, run it once after `init` and
before a release build, from the site directory:

```bash
RECIT_SHELL_DIR="$PWD/.recit/app" node --input-type=module -e "
  import { prepareAndroidSigning } from '@recit/app';
  console.log(await prepareAndroidSigning(process.env.RECIT_SHELL_DIR, process.env));
"
```

The `App Android` workflow of the repository does exactly that, with the keystore in a base64
secret.

Two more things the commands do not do yet, both one line in `@recit/app`: the generated Gradle
project calls the Tauri CLI back as `pnpm tauri …` from the shell, so the `node_modules/.bin` of the
package that holds `@tauri-apps/cli` must be on the `PATH` (the workflow adds it); and a `version`
of `0.0.0` must be overridden, `-- --config '{"version":"0.1.0"}'`, until the site sets
`app.version`.

:::

## Development on a device

The site's development server listens on every interface and accepts any host name, so an emulator
or a device on the same network reaches it. `recit app android dev -- --host` makes Tauri replace
`devUrl` with the machine's address on the local network (`-- --host 192.168.1.20` names it instead
of prompting), starts `recit-site dev` unless one already answers on the site's port, and installs a
debug build on the first device `adb devices` lists. The Rust side reloads on change; the site has
its own hot reload through the same origin.

## Limits

- The deck remote control (`--remote` of `recit dev`) has no server in the application.
- One application per site; a site with `basePath` or `cdnUrl` is refused by the `app:` diagnostics.
- An `.aab` from the Play Store and an `.apk` built here carry different signatures: install the
  `.apk` for tests, upload the `.aab` for the store.
