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
- Install Android Studio, then its SDK and NDK (SDK Manager, SDK Tools tab, "NDK (Side by side)").
- Add the Rust target and the packager:
rustup target add aarch64-linux-android cargo install cargo-apk - 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
| Need | Use |
|---|---|
| Clipboard | platform::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 keyboard | platform::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 notch | platform::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 button | input.take_back() is true once after Android's back button. Close the top sheet or go back a screen. |
| Window input | events::WindowInput turns winit events into ANVL's Input, including Android's quirks (Enter arriving as text) and touch. |
| Pointer shape | events::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.