Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Wear OS

Perry can compile TypeScript apps for Wear OS watches and the Wear OS emulator.

Wear OS is Android on a watch, so Perry reuses the exact same backend as the Android target: your perry/ui tree lowers through perry-ui-android (JNI → TextView / LinearLayout / Button / …), and the compiled aarch64-linux-android .so is identical to a phone build. perry run wearos then packages it with a watch form-factor overlay — the android.hardware.type.watch feature, the standalone meta-data, and the androidx.wear support library — and installs it over adb like any other APK.

This page is about full Wear OS apps. For glanceable Wear OS Tiles (the swipe-left surfaces, built from Widget({...}) declarations), see Wear OS Tiles.

Requirements

Wear OS uses the same toolchain as the Android target (the .so is cross-compiled and the APK is built with Gradle), plus a Wear OS system image. You need all of:

ToolWhyNotes
JDK 17Runs Gradle / the Android Gradle PluginThe template uses AGP 8.8.2, which targets JDK 17. Newer JDKs (21/26) can fail the Gradle build — point JAVA_HOME at a 17 if your default is newer.
Gradle 8.xperry run wearos bootstraps a Gradle wrapper from the system gradleAGP 8.8.2 requires Gradle 8.10.2+ and is not compatible with Gradle 9.x.
Android SDKadb, aapt, signing, the android-35 platformplatform-tools, build-tools;35.0.0, platforms;android-35. Set ANDROID_HOME.
Android NDK (r27+)Cross-compiles the runtime/.so for aarch64-linux-androidSet ANDROID_NDK_HOME. r27+ is fine — Perry points cc-rs at the NDK llvm-ar.
Rust Android targetThe .so is aarch64-linux-androidrustup target add aarch64-linux-android. Wear is arm64-only — NaN-boxing needs 64-bit pointers.
Wear OS system image + emulatorA watch to install ontoUse an arm64-v8a image (Perry packages an arm64-v8a libperry_app.so).

One-time setup (macOS example)

# 1. JDK 17 + Gradle 8.x (Homebrew's `gradle` may be 9.x — pin 8.x if so)
brew install --cask temurin@17
brew install gradle@8        # or any Gradle 8.10.2+

# 2. Point the env at the SDK / NDK / JDK 17 (add to your shell profile)
export ANDROID_HOME="$HOME/Library/Android/sdk"
export ANDROID_NDK_HOME="$ANDROID_HOME/ndk/<installed-version>"   # e.g. 28.2.13676358
export JAVA_HOME="$(/usr/libexec/java_home -v 17)"
export PATH="$ANDROID_HOME/platform-tools:$ANDROID_HOME/emulator:$PATH"

# 3. SDK packages + a Wear OS arm64 image
sdkmanager --licenses
sdkmanager "platform-tools" "emulator" \
           "platforms;android-35" "build-tools;35.0.0" \
           "ndk;28.2.13676358" \
           "system-images;android-34;android-wear;arm64-v8a"

# 4. The Rust cross target
rustup target add aarch64-linux-android

# 5. Create + boot a Wear OS emulator (round watch)
avdmanager create avd -n perry_wear \
  -k "system-images;android-34;android-wear;arm64-v8a" -d wearos_large_round
emulator -avd perry_wear

On Apple-silicon Macs the arm64-v8a image runs natively; on Intel hosts it runs under the emulator’s arm translation. The first perry run wearos build also downloads the AGP and androidx.wear dependencies from Google’s Maven repo, so the initial build needs network access.

Building

perry compile app.ts -o app --target wearos

This cross-compiles to aarch64-linux-android and emits libperry_app.so — the same artifact as --target android. Packaging into a watch APK happens at run time (below).

Running with perry run

perry run wearos          # Auto-detect a connected watch / booted Wear emulator

perry run wearos:

  1. Cross-compiles the .so (identical to the Android path).
  2. Copies the Android Gradle template and applies the Wear overlay:
    • adds <uses-feature android:name="android.hardware.type.watch" android:required="true" />
    • adds <meta-data android:name="com.google.android.wearable.standalone" android:value="true" />
    • adds implementation("androidx.wear:wear:1.3.0")
    • raises minSdk to 30 (Wear OS 3, the Google Play floor for watch APKs)
  3. Runs ./gradlew assembleDebug, debug-signs, then adb install + launches and streams logcat.

wear and wear-os are accepted as aliases for wearos.

UI Toolkit

Identical to Android — the same perry/ui widgets map to the same Android View classes (TextTextView, VStack → vertical LinearLayout, ButtonButton, ScrollViewScrollView, and so on). No Wear-specific widget API is required: an Android UI tree renders directly on a watch.

The androidx.wear dependency in the overlay brings in BoxInsetLayout and swipe-to-dismiss so round screens and the back gesture behave like a native Wear app.

App Lifecycle

A Wear OS app uses the same App({...}) entry point as every other Perry UI target:

import { App, Text, VStack, Button, State } from "perry/ui"

const count = State(0)

App({
    title: "My Watch App",
    width: 200,
    height: 200,
    body: VStack(8, [
        Text("Hello, Wear OS!"),
        Text(`Taps: ${count.value}`),
        Button("Tap me", () => count.set(count.value + 1)),
    ]),
})

Under the hood this is the Android lifecycle: PerryActivity loads libperry_app.so, calls into your compiled main() over JNI, and the perry/ui tree is realized as Android Views on the watch.

State Management

Reactive state works exactly as on Android and the other platforms:

import { App, Text, VStack, Button, State } from "perry/ui"

const count = State(0)

App({
    title: "Counter",
    width: 400,
    height: 300,
    body: VStack(16, [
        Text(`Count: ${count.value}`),
        Button("Increment", () => count.set(count.value + 1)),
    ]),
})

Platform Detection

Because the runtime target triple is aarch64-linux-android, a Wear OS app reports the Android platform number — __platform__ === 2:

declare const __platform__: number

// Platform constants:
// 0 = macOS
// 1 = iOS
// 2 = Android
// 3 = Windows
// 4 = Linux
// 5 = Web (browser, --target web / --target wasm)
// 6 = tvOS
// 7 = watchOS
// 8 = visionOS

if (__platform__ === 0) {
    console.log("Running on macOS")
} else if (__platform__ === 1) {
    console.log("Running on iOS")
} else if (__platform__ === 3) {
    console.log("Running on Windows")
}

There is intentionally no separate Wear OS platform constant: at runtime a Wear app is an Android app. Branch on screen size at runtime if you need watch-specific layout.

Configuration

Wear OS reuses the [android] section of perry.toml (bundle id, etc.):

[android]
bundle_id = "com.example.mywatch"

Limitations

Wear OS inherits Android’s widget set but the watch form factor imposes the usual constraints:

  • Small round screens — design for ~1.2–1.4“ displays; prefer ScrollView and short labels. Use the androidx.wear BoxInsetLayout insets for round bezels.
  • Touch-only — no hover or right-click.
  • Single window — modal flows map to Dialog views, same as Android.
  • Battery / memory — keep apps lightweight; Wear devices have far less RAM than phones.
  • Publishing — Wear apps ship through Google Play as Android APK/AABs (the perry publish flow treats Wear like Android).

Next Steps