Ivan FB c51f313cd2
docs(examples): add Android (Kotlin/JNI) example over the native C ABI
An Android library module wrapping the timer library's native ABI behind an
idiomatic Kotlin `TimerNode` class via a JNI shim. `build-libs.sh` cross-compiles
two native libraries per ABI (arm64-v8a + x86_64) into src/main/jniLibs/:
libmy_timer.so (the Nim library) and libmy_timer_jni.so (the JNI bridge, which
NEEDs the former). The shim turns each Kotlin `external fun` into a blocking call
and reads the typed EchoResponse struct out of the result callback.

Gradle packages everything under jniLibs/ automatically; an instrumented test
covers create/version/echo on a device/emulator. Native build validated for
both ABIs (correct aarch64/x86_64 ELF, JNI symbols exported, libmy_timer.so
linked).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 18:37:20 +02:00
..

Android example — Kotlin over the native C ABI (JNI)

An Android library module that wraps the timer library's native (zero-serialization) C ABI behind an idiomatic Kotlin class, TimerNode, via a small JNI shim. Struct returns come back as typed Kotlin values.

TimerNode("my-app").use { node ->
    println(node.version())              // "nim-timer v0.1.0"
    val r = node.echo("hello", delayMs = 5)
    println("${r.echoed} / ${r.timerName}")
}

How it fits together

 Kotlin TimerNode  ──external fun──▶  libmy_timer_jni.so  ──C ABI──▶  libmy_timer.so
 (src/main/kotlin)                    (jni/my_timer_jni.c)            (Nim, generated)
  • libmy_timer.so — the Nim library, exporting the native C ABI.
  • libmy_timer_jni.so — a JNI shim that turns each native Kotlin method into a blocking call into that ABI, reading the typed EchoResponse struct out of the result callback. It NEEDS libmy_timer.so at load time.
Path Description
build-libs.sh Cross-compiles both .sos per ABI into src/main/jniLibs/<abi>/.
jni/my_timer_jni.c The JNI bridge.
src/main/kotlin/.../TimerNode.kt The Kotlin wrapper + EchoResult.
build.gradle.kts, settings.gradle.kts Library module build files.
src/androidTest/... Instrumented test (runs on a device/emulator).

Build

cd examples/timer/android
export ANDROID_NDK_ROOT=/path/to/android-ndk   # or rely on the default
./build-libs.sh                                  # arm64-v8a + x86_64 by default

This stages libmy_timer.so + libmy_timer_jni.so under src/main/jniLibs/. The Android Gradle plugin packages everything under jniLibs/ into the AAR/APK automatically — no extra Gradle config needed.

Add ABIs by extending the table at the bottom of build-libs.sh and the abiFilters in build.gradle.kts.

Use it

  • As a module: drop android/ into your project, include(":mytimer") in your settings.gradle, and implementation(project(":mytimer")).
  • By hand: copy src/main/jniLibs/<abi>/*.so into your app's jniLibs/ and TimerNode.kt into your sources.

Then org.logos.mytimer.TimerNode is ready to use.

Run the test

./gradlew connectedAndroidTest        # needs a connected device or emulator

Notes

  • This is the native, same-process path — the app loads the library directly. The CBOR ABI is for inter-process communication only (see ../ipc).
  • Each call blocks on a condvar in the JNI shim until the library's FFI-thread callback fires, so the Kotlin API is simple and synchronous.
  • Regenerate ../c_bindings/my_timer.h with nimble genbindings_c (from the repo root) if the library's API changes.