Android: what makes an app feel native

How a Zinc app is exported, built and installed is in docs/reports/zinc-next-android.md (zinc export --target android, Gradle, the Nexus 7 runs). This page is what the app does to behave like an Android app, and how to try it on the desktop. The audit and plan behind it: docs/reports/mobile-native-feel.md.

Try it on the desktop

ZINC_PLATFORM=android zinc run . runs the program as on Android: platform() and Platform.OS answer android, Platform.select takes the android branch, x.android.ts replaces x.ts, and the behaviour below is Android’s. ZINC_PLATFORM_VERSION=29 plays an Android 10 (the scroll glow instead of the stretch). The OS’s signals are sent from a ZINC_INPUT script (docs/ui.md, “Platform and system signals”):

10 safearea 24 0 48 0
10 theme dark
10 fontscale 1.3
20 keyboard 280
40 back
50 backstart left
52 backprogress 0.5
54 back

What the app does

AreaOn Android
Identityplatform() android; Platform.Version the API level (ro.build.version.sdk)
Safe area, themefrom SDL (SDL_GetWindowSafeArea, SDL_GetSystemTheme) at start and when they change
Font size, animations offConfiguration.fontScale and the animator duration scale 0, sent by the activity (ZincActivity) at start, on a configuration change and on resume
Keyboardits height from the IME insets (API 30+) or the visible frame (before), so the focused field is revealed above it; the field’s traits choose the keyboard (ZincEdit.onCreateInputConnection: input type, return key action, autofill hint)
Text input connectionZincEdit, in place of SDL’s hidden editor: an InputConnection over a mirror of the focused field (text, selection, composing region), so suggestions, autocorrect and composition see the real text
Backthe back key (SDL, trapped) and Android 13+‘s back callback go to the program’s back handlers (BackHandler, ui.addBackHandler, the navigation stack); none takes it: the task goes to the background (Android 12+‘s behaviour for a root activity). Android 14’s predictive back gesture sends its progress, and the navigation stack shrinks the screen with it
ScrollingOverScroller’s spline fling, the stretch overscroll (12+) or the glow (before), no rubber band, the scrollbars’ 300 ms + 250 ms fade
Navigationzinc:ui/navigation: Material’s shared X axis, the back arrow and a left-aligned title; modal screens as full-screen dialogs
Touch selectiona long press selects a word: teardrop handles, a white edit menu, a magnifier while a handle moves, the long-press haptic
Status barStatusBar barStyle (light or dark icons), hidden, backgroundColor (before Android 15, which draws apps edge to edge)
Hapticszinc:haptics and Vibration: View.performHapticFeedback (CONFIRM, REJECT, SEGMENT_TICK where the API has them); vibrate needs the VIBRATE permission, without it a haptic tick

The Java side is src/app/android/java (copied into the exported project), the JNI side src/app/android_glue.cpp.

Not proven on a device

Written and compiled on the Mac (javac against android-36, clang with NDK r29) without a device in the loop (2026-10-16): the keyboard height, the input connection with Gboard, swipe typing and a Japanese IME, predictive back, the status bar, haptics and the font scale on a phone are lines of the cross-platform pass in backlog/later.md.

Edit this page on GitHub ↗