Kotlin (JVM and Android)¶
bindings/kotlin is a Gradle build with two library modules over the UniFFI crate in bindings/uniffi, plus a Compose sample:
| Module | What | Native library |
|---|---|---|
:jvm | Kotlin/JVM library; JNA loads the library from classpath resources (<os>-<arch>/) | host, via scripts/build-jvm-native.sh |
:android | Android AAR (minSdk 24), jniLibs for arm64-v8a, armeabi-v7a, x86_64, x86 | scripts/build-android.sh (cargo-ndk) |
:sample-compose | Minimal Jetpack Compose note-search app | from :android |
The generated bindings (jvm/src/main/kotlin, android/src/main/kotlin) are committed; the two flavors differ only in the Android-safe cleaner. Regenerate with scripts/generate.sh. The API has no Android or JVM types, so the sources are a ready starting point for a Kotlin Multiplatform expect/actual wrapper.
Requirements¶
- JDK 17, Gradle (the wrapper pins 8.14.6; AGP 8.13.2, Kotlin 2.2.21).
- Rust stable 1.89 or later and
rustup; the scripts add the Rust targets (user-level). - Android: SDK platform 35, build-tools 35, NDK r27.3 (
27.3.13750724; r26 or later should work),cargo install cargo-ndk --locked. Put the SDK path inbindings/kotlin/local.properties(sdk.dir=...) orANDROID_HOME; the NDK is found under$ANDROID_HOME/ndkorANDROID_NDK_HOME.
Build and test¶
cd bindings/kotlin
scripts/bootstrap-gradle-wrapper.sh # once: fetch the checksum-pinned gradle-wrapper.jar
scripts/build-jvm-native.sh # host library into jvm/build/natives
./gradlew :jvm:test # JUnit on the JVM
scripts/build-android.sh # four ABIs into android/src/main/jniLibs
./gradlew :android:assembleRelease :sample-compose:assembleDebug
./gradlew :android:connectedAndroidTest # needs a device or emulator
Use¶
val store = HmsStore.open(
File(filesDir, "hms").absolutePath,
StoreOptions(dimensions = 4096u, encryptionKey = keyFromKeystore))
store.ingest(DocumentInput(id = "note-1", text = "Title\nBody ...", titleWeight = 3u))
val hits = store.search("title", SearchOptions(k = 10u, bm25K1 = 1.2, bm25B = 0.75))
store.snapshot(backupDir.absolutePath)
val restored = HmsStore.restore(backupDir.absolutePath, otherDir.absolutePath, sameOptions)
Disk-touching calls are suspend functions that run on a worker thread, with …Blocking variants. A store is thread-safe. A directory opens once per process (HmsException.StoreLocked); shutdown() releases it at once, close() (AutoCloseable) drops the handle. Errors are the sealed HmsException (InvalidInput, StoreLocked, Encryption, IncompatibleStore, NotFound, AlreadyExists, Corrupt, Io, Closed, Internal; each carries details). Metadata and filters are JSON object strings (metadataJson, filterJson).
Proguard/R8: the AAR ships consumer-rules.pro keeping JNA and the binding package.
Verified¶
./gradlew :jvm:teston macOS arm64 (JDK 17): 7 JUnit cases pass (typed errors, encryption with a plaintext scan of the store, snapshot/restore with tamper detection, suspend forms under concurrency, BM25 options,titleWeight, filters)../gradlew :android:connectedDebugAndroidTeston an arm64-v8a emulator (Android 15, API 35, running on the Apple silicon host): 2 instrumented cases pass.:android:assembleReleaseproduces the AAR;:sample-compose:assembleDebugbuilds the sample, which installed on that emulator, launched, and created its store.- All four ABIs build with cargo-ndk (NDK r27.3, API 24).
Not verified: execution of armeabi-v7a, x86 and x86_64 libraries (built and packaged, not run), a physical device, and Android versions other than 15. The macOS host library is built with strip = debuginfo because strip = true produced a dylib that macOS 27's dyld refused to load; Linux hosts and the Android libraries use fully stripped builds.
Sizes and timings: benchmarks/results/mobile_v1.json.