An on-device test gallery for GPUI Kit v0.7.3 on Android: a phone-sized version of Kit's story gallery. Every user-facing component has a demo screen that exercises its states and events (each interactive screen shows the current value and an event log), plus lab tools for touch, scrolling, diagnostics, rendering stress and themes.
- Component coverage and per-component Android status: COMPONENT_MATRIX.md (the app renders its catalog from this file).
- What our forks of gpui-mobile, GPUI Kit and GPUI carry: MOBILE_PATCHES.md.
- Assessment and possible work for the GPUI / GPUI Kit teams: CONTRIBUTING_IDEAS.md.
One GPUI snapshot is used throughout; cargo tree resolves exactly one
version of every gpui-pre-* crate.
| Dependency | Version / revision |
|---|---|
GPUI Kit (gpui-kit, gpui-component, gpui-base, gpui-fps) |
0.7.3 from Bombatomica64/gpui-kit mobile-lab (tag v0.7.3 + 10 commits) |
GPUI (gpui-pre) |
0.3.9 from Bombatomica64/gpui-pre mobile-lab (crates.io 0.3.9 + 3 touch fixes and TextInputConfiguration::purpose), via [patch.crates-io] |
GPUI's other crates (gpui-pre-wgpu, …) |
0.3.9 from crates.io (the snapshot GPUI Kit 0.7.3 pins) |
gpui-mobile (gpui-pre-mobile) |
Bombatomica64/gpui-mobile main (upstream main + the fork-only branches in MOBILE_PATCHES.md) |
| Renderer | wgpu 29.0.4 (Vulkan preferred, GLES fallback) |
| Rust | 1.98.1 stable, edition 2024 |
| Android | NDK 27.2.12479018, compileSdk 35, targetSdk 34, minSdk 26, AGP 9.1.0, Gradle 9.4.1, JDK 17 |
Prerequisites (Linux or macOS):
# Rust + Android targets + cargo-ndk
curl -sSf https://sh.rustup.rs | sh
rustup target add aarch64-linux-android x86_64-linux-android
cargo install cargo-ndk
# JDK 17 and the Android SDK/NDK (command-line tools)
sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0" "ndk;27.2.12479018"
export ANDROID_HOME=$HOME/Android/Sdk # build.sh defaults to this pathBuild the APK:
./build.sh android --release # arm64-v8a (phones)
./build.sh android --release --abi all # arm64-v8a + x86_64 (phones and emulators)
./build.sh android --debug # unoptimized native code
./build.sh android --release --install # also adb install + launchThe script compiles the Rust library with cargo ndk into
android/app/src/main/jniLibs/, then runs Gradle (assembleRelease or
assembleDebug) and copies the result to
dist/gpui-mobile-lab-<version>-<profile>.apk. Once the native libraries are
in jniLibs/, cd android && ./gradlew assembleRelease works on its own.
The release APK is signed with the local Android debug key so it can be sideloaded without a keystore. It is a test app, not for store distribution.
adb install -r dist/gpui-mobile-lab-0.1.2-release.apk
adb shell am start -n dev.gpui.mobile.lab/.LabActivityOr download the APK from the GitHub release and open it on the phone (allow installs from unknown sources).
Logs and panics go to logcat under the tag GPUI_MOBILE_LAB (a panic logs its
message, location and a backtrace before the process aborts):
adb logcat -s GPUI_MOBILE_LAB AndroidRuntime DEBUGTesting aid: open a screen directly from adb (the title is the "Demo screen" column of the matrix):
adb shell am start -n dev.gpui.mobile.lab/.LabActivity --es screen "'Text Input'"The catalog lists every component grouped by category, with version info, Android API level, viewport size, a search field and working/partial/broken counters (tap a counter to filter). Tap a component to open its screen; the header's ‹ Catalog button and Android back return to the catalog. Back closes the keyboard, then any open overlay, then the screen; at the catalog it sends the app to the background like a launcher Activity.
Lab tools: Touch Lab (tap, double-tap, long-press, drag, pinch counters), Scroll Stress (100 rows, 10,000 virtual rows, nested axes, long text), Diagnostics (viewport, scale, visual viewport, keyboard, focus, scroll, foreground state, gpui-fps HUD, render cadence), Stress Test (Start/Stop/Reset over 120 buttons, 400 text nodes, inputs, a 5,000-row list and a per-frame chart) and Themes (System/Light/Dark and bundled Kit themes, switched at runtime).
android/app/src/main/java/dev/gpui/mobile/lab/LabActivity.java is a plain
Activity with a SurfaceView, driving gpui-mobile's host entry point
(gpui_mobile::android::host, see src/host.rs). GPUI runs on one
process-lived render thread, so the app keeps its state across surface
destruction (Home/resume) and Activity recreation: the Activity keeps its
HostId in its saved state, so a recreated instance gets its own window
back, and a fresh launch opens a new one. The Activity forwards
touch (MotionEvent), keys (back arrives in GPUI as escape), lifecycle,
configuration changes and an IME InputConnection proxy adapted from gpui-mobile's
GpuiInputActivity. adjustResize shrinks GPUI's viewport above the
keyboard; the app is locked to portrait.
No physical phone or KVM-capable emulator was available while building this.
All statuses in the matrix come from running the release APK (x86_64 ABI) on
Redroid Android 13
(API 33) in Docker on the host kernel, configured as a 1080×2340, 420 dpi
phone (411×819 pt at 2.625×), with the stock AOSP keyboard (LatinIME).
Interactions were driven with adb shell input (taps, swipes, raw
motionevent DOWN/MOVE/UP for long-press and drags, key events and IME
typing), and verified from screenshots and the in-app event logs in logcat.
Several screens were later checked on a physical phone (360×736 pt); the Bubble, ShimmerText and Toggle notes in the matrix come from that.
Limits of that setup:
- Rendering is CPU-based (SwiftShader Vulkan). GPUI's own draw time was ~0.5–2.4 ms per frame, but continuously animated frames arrived only every ~1.2 s, and timers ran several times slower than requested. No performance number from this setup says anything about a phone GPU — use Diagnostics and Stress Test on a real device.
- The arm64-v8a library in the APK was built and packaged but not executed.
- Multi-touch (pinch) and real finger physics could not be exercised with adb injection.
Each is detailed in the matrix; fixes carried by our forks are in MOBILE_PATCHES.md.
Broken on a touch-only phone
OtpInputandTimeField(and the time row of a date+timeDatePicker) never raise the soft keyboard: they have no text-input handler and do not callrequest_virtual_keyboard(which the gpui-mobile fork now implements). Editing needs a hardware keyboard.
Partially working
- Bold and italic text render as regular weight (Android's Roboto is a variable font; the cosmic-text stack does not apply weight/italic variations). Headings are larger but not bold.
- TextView long-press selection shows handles and a Copy/Select All menu, but the initial selection is not reliably the pressed word.
- Popovers and submenus are not moved back into the viewport: a popover near the right edge and submenus are clipped. Breadcrumb, full Pagination, static Table, DescriptionList, Settings and long Accordion titles do not adapt to a 411 pt width.
- Carousel: one fling can skip several slides; its prev/next buttons render outside the card.
NumberInputand masked number inputs open the full text keyboard; gpui-mobile always requests the default keyboard type.- A disabled
Radioinside aRadioGroupis still selectable; a.rows(5)Textarea renders one row tall (both not Android-specific; reported as longbridge/gpui-kit#3324 and #3325, fixes proposed in #3331 and #3330). - In dark mode, a TextView inside a
FilledchatBubblerenders white on white (TextView ignores the bubble's text color), and the defaultShimmerTexthighlight is invisible on regular text. Both are not Android-specific (#3326 and #3327, fixes proposed in #3329 and #3328); the demos work around them. - Kit tooltips are disabled on mobile by design; a raw GPUI
.tooltip()did not show on long-press either. - Closing a sheet while a fling is still running hands the remaining momentum to the page behind; a horizontal fling that reaches the end of a TabBar can continue as vertical page scrolling.
- After a tap, the touched menu row keeps its hover highlight.
Fixed in our forks (checked on a OnePlus CPH2581, Android 16)
- Finger drags on
on_dragelements: Resizable dividers and Dock splitters and tabs work (Resizable, Dock). DataTable column resize/reorder still do not. - A focused input scrolls above the keyboard; back hides the keyboard without closing the dialog or screen, and tapping any input brings the keyboard back (dialog).
- A horizontal swipe no longer scrolls a vertical-only page.
- Night mode follows the system at launch and when it changes; emoji render on the host-driven entry point.
Back with the keyboard hidden still arrives as escape, so an input that
opts into clean_on_escape() clears its text instead of navigating back.
Other
window.request_animation_frame()panics when called outside rendering (for example in a click handler); call it fromrender.
build.sh build script (cargo ndk + Gradle)
Cargo.toml the lab crate (cdylib); forks pinned by rev, [patch.crates-io] for gpui-pre
src/lib.rs GPUI launch, window
src/host.rs JNI entry points for LabActivity
src/app.rs catalog, navigation, back
src/matrix.rs parses COMPONENT_MATRIX.md into the catalog
src/screens/ one module per demo screen
android/ Gradle project (LabActivity, manifest, assets)
themes/ Kit theme JSONs bundled into the app
AGPL-3.0 (see LICENSE). Dependencies keep their own licenses: gpui-mobile
is GPL-3.0-or-later / AGPL-3.0-or-later / Apache-2.0; GPUI, gpui-base,
gpui-component and gpui-fps are Apache-2.0. NotoColorEmoji.ttf is under the SIL Open Font License.