Configuration

Android

The desktop application 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 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, 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.

TerminalBash
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

KeyDefaultMeaning
android.minSdkVersion24 (Android 7.0)the oldest Android the application installs on; becomes minSdk of the project
versionthe site's package.jsonmust 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)
identifiersee the app pagebecomes the applicationId of the store, final once published; no hyphen allowed
iconmetadata.faviconthe launcher icons (mipmap-*) are derived from it

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

Commands

CommandEffect
recit app android initgenerates the Android project once, in .recit/app/gen/android/ (ignored by git), from the app: section
recit app android dev -- --hostruns 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 -- --apkthe packages for the four ABIs; -- --target aarch64 --apk limits them to one, -- --aab adds the bundle for the Play Store
recit app build --target androidthe 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.

VariableMeaning
RECIT_ANDROID_KEYSTOREpath of the .jks or .keystore file
RECIT_ANDROID_KEYSTORE_PASSWORDits password
RECIT_ANDROID_KEY_ALIASthe alias of the upload key
RECIT_ANDROID_KEY_PASSWORDthe password of the key, the store's when unset

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

TerminalBash
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.

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:

TerminalBash
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.