Docs menu

Platforms

One codebase builds a Windows EXE, a Linux binary and an Android APK. The differences are in packaging and a few platform services, which anvil_ui::platform wraps so your code needs no cfg.

Windows and Linux

cargo build -p my_app --release
# Windows: target/release/my-app.exe    Linux: target/release/my-app
  • Hide the console window on Windows release builds by putting this at the top of main.rs: #![cfg_attr(all(not(debug_assertions), target_os = "windows"), windows_subsystem = "windows")].
  • Give the window an icon with WindowBuilder::with_window_icon. anvil_ui::logo::mark_frame(128) gives the ANVL mark if you have none.
  • Linux needs Vulkan drivers (Mesa on Intel and AMD; NVIDIA's own driver).

Android

Set up once

  1. Install Android Studio, then its SDK and NDK (SDK Manager, SDK Tools tab, "NDK (Side by side)").
  2. Add the Rust target and the packager:
    rustup target add aarch64-linux-android
    cargo install cargo-apk
  3. Point the tools at the SDK and NDK. On Windows they are usually under %LOCALAPPDATA%\Android\Sdk:
    export ANDROID_HOME="$LOCALAPPDATA/Android/Sdk"
    export ANDROID_NDK_ROOT="$ANDROID_HOME/ndk/<version>"

Build and run

cargo apk build -p my_app --lib --release    # target/release/apk/my-app.apk
cargo apk run -p my_app --lib --release      # install on the connected phone and start it

Turn on USB debugging on the phone (Settings, About phone, tap Build number seven times, then Developer options). adb devices should list it.

The manifest

cargo-apk writes the Android manifest from [package.metadata.android] in your Cargo.toml:

[package.metadata.android]
package = "com.example.myapp"        # the app's unique id on the device and the store
apk_name = "my-app"
build_targets = ["aarch64-linux-android"]
strip = "strip"

[[package.metadata.android.uses_permission]]
name = "android.permission.INTERNET"  # only if the app uses the network

[package.metadata.android.sdk]
min_sdk_version = 26
target_sdk_version = 35

[package.metadata.android.application]
label = "My App"

# ANVL handles every configuration change itself. Without this, rotating the phone recreates the
# activity, and winit cannot start a second event loop in the same process.
[package.metadata.android.application.activity]
config_changes = "orientation|screenSize|smallestScreenSize|screenLayout|density|keyboard|keyboardHidden|navigation|uiMode|fontScale|locale|layoutDirection|colorMode|touchscreen|mcc|mnc"

The entry point

Android starts your library's android_main, not main. The starter's is a good template: it points your data folder at the app's private storage and starts the same run as desktop. A relative path means nothing on Android, so always build file paths from android_app.internal_data_path().

Signing a release

Debug builds are signed with a debug key, which is fine for your own phone. For a release, make a keystore once and point cargo-apk at it:

keytool -genkey -v -keystore release.keystore -alias myapp -keyalg RSA -keysize 2048 -validity 10000
[package.metadata.android.signing.release]
path = "release.keystore"
keystore_password = "…"   # keep this out of version control

Keep the keystore and its password safe. An app signed with a different key cannot update the installed one.

Platform services

NeedUse
Clipboardplatform::Clipboard::new(), then get_text() and set_text(s). After each frame, pass ui.take_clipboard() to set_text; before one, answer input.wants_paste() with input.set_paste(clipboard.get_text()). Desktop uses the system clipboard, Android its ClipboardManager.
Soft keyboardplatform::soft_input::set_shown(ui.text_input_active()) after each frame: the keyboard shows while a text field has focus. Does nothing on desktop.
System bars and notchplatform::soft_input::window_insets() returns the top, bottom, left and right insets and the keyboard height (Android); pass them to ui.set_insets and lay out in ui.safe_area().
Back buttoninput.take_back() is true once after Android's back button. Close the top sheet or go back a screen.
Window inputevents::WindowInput turns winit events into ANVL's Input, including Android's quirks (Enter arriving as text) and touch.
Pointer shapeevents::cursor_icon(ui.cursor()) gives winit's cursor for what the UI asked for.

macOS and iOS

The renderer has a Metal backend, but macOS and iOS are not built or tested yet.