tally/android/README.md

3.3 KiB

Tally for Android

Two screens, reached from a hamburger drawer: the standard calculator (design 9.2), an NCalc-style pad with a live answer as you type, and the converter (design 9.6), which reopens where it was left and shows a value in every unit at once. Settings has the theme: follow the device, light or dark. The engine behind it is the finished one, so 2^100 + 1, x = 5 then x * 2 and cagr(10000, 25000, 5) * 100 all work. The programmer and financial screens (design 9) are not built.

Building

Nothing is installed system-wide; the toolchain comes from mise. Setup, emulator and deployment, including wireless adb, are in SETUP.md - this file is about what the app is.

mise install && mise run android-sdk             # toolchain, then the SDK packages
zig build android                                # the native library, one .so per ABI
cd android && gradle assembleDebug

zig build android writes zig-out/android/{arm64-v8a,x86_64,armeabi-v7a}/libtally.so, which is the jniLibs layout Gradle expects, so Gradle only packages it. Gradle never invokes Zig, and there is no CMake or ndk-build in this project: the library is built without the NDK entirely (design 6.2), and the JNI glue is hand-written Zig (engine/src/jni.zig) rather than generated from jni.h.

Because Gradle never invokes Zig, zig build android has to be re-run by hand after any engine or JNI change, or the APK gets a stale library.

The Gradle wrapper is not committed; gradle wrapper writes one, or Android Studio offers to on first open.

The first thing to run, and it needs no device

zig build jvm-test

engine/src/jni.zig reaches the JNI function table by index, because there is no jni.h here to name the entries. Four of those indices have to be right. JNI's table is fixed by the specification rather than by the platform, so a desktop JVM checks them just as well as a phone: this step builds the library for the host, loads it from Java (which runs JNI_OnLoad, which round-trips a string through two of the four), and drives every entry point. Moving one index by a slot makes it fail.

Then, with a device or emulator attached:

cd android && gradle connectedAndroidTest

TallyEngineTest covers what only the real app can: that a session remembers x and Ans, that 2^100 + 1 arrives with its last digit intact, that 12 in to ft is exactly 1, that an error carries the engine's own wording rather than a second copy written in Kotlin, and that a closed session refuses to evaluate instead of crashing. It passes on an Android 15 x86_64 image with 16KB pages. Note that it uninstalls the app when it finishes.

Nothing has been verified on a physical device yet, and arm64-v8a is the interesting one: it is the ABI phones actually use, and the only one where the page-size bug in design 6.3 showed up.

What is deliberately not here

  • No arithmetic. Kotlin parses no expressions, formats no numbers and words no errors. All of it is the shared engine, so a wrong answer is wrong in the CLI and TUI too, which is the point of having one engine.
  • No error table. Messages come from engine.phrase through the JSON.
  • No unit list. TallySession.unitCatalog() returns the engine's own tables, aliases included, for whenever the convert screen is built.