Skip to content

Demo: library catalog with jextract JNI callbacks, official Swift Android SDK#48

Merged
colemancda merged 4 commits into
masterfrom
feature/demo-library-catalog
Jul 25, 2026
Merged

Demo: library catalog with jextract JNI callbacks, official Swift Android SDK#48
colemancda merged 4 commits into
masterfrom
feature/demo-library-catalog

Conversation

@colemancda

Copy link
Copy Markdown
Member

Summary

Rebuilds the Demo app around a library catalog: a Compose list of the wrapped Android SDK modules, each opening a screen that runs a live demo implemented in Swift, with output streamed back to Kotlin through swift-java jextract (JNI mode) callbacks and runnable blocks.

Build system: Skip → official Swift Android SDK

  • Cross-compilation now runs swift build --swift-sdk aarch64-unknown-linux-android28 (Swift 6.3.3 release toolchain + artifactbundle) directly from Gradle — an jextract Exec task plus a stageJniLibs Copy task replace skip android build and build-swift.sh.
  • --disable-sandbox is required for the jextract plugin's enableJavaCallbacks nested Gradle build, which the Skip driver could not pass through.
  • The generated JNI Java bindings and the SwiftKitCore runtime compile into the APK as sources (sourceSets + filter.exclude for the two jdk.jfr-dependent annotations, which don't exist on Android).

jextract JNI bridge (CatalogBridge)

  • New SwiftPM target with mode: jni and enableJavaCallbacks; public API is catalogEntryCount/Name/Summary and runCatalogDemo(index:onLine:onCompleted:) — Swift invokes the Kotlin lambdas through generated thunks.
  • Static-only: nativeLibraryName: SwiftAndroidApp points the generated bindings at libSwiftAndroidApp.so, so there is a single copy of the module (no separate .so, no duplicated module statics).
  • Demos obtain the application Context via AndroidContext.application (ActivityThread bootstrap), wrapped exactly once as a JNI global ref — JavaObjectHolder deletes the incoming local ref on promotion, so re-wrapping a cached pointer would crash.
  • The legacy SwiftObject/external-method callback shims (Runnable, ViewOnClickListener, list/recycler adapters, Fragment) are removed, superseded by the jextract bridge (net −500 LOC).

Catalog demos

Module Demo
AndroidOS Build manufacturer/model/device, release + SDK level, SystemClock uptimes
AndroidContent Package name, target/min SDK, data dir via Context/ApplicationInfo
AndroidLocation LocationManager providers and enabled state
AndroidMedia AudioManager stream volumes and ringer mode
AndroidNet ConnectivityManager active network, WifiManager state
AndroidNFC NfcAdapter availability

Adds ACCESS_NETWORK_STATE, ACCESS_WIFI_STATE, and NFC permissions required by the demos.

Test plan

  • ./gradlew :app:assembleDebug builds end-to-end (Swift cross-compile → jextract generation → APK)
  • All six catalog demos verified on an arm64-v8a emulator (API 36): output lines and completion arrive via Swift→Kotlin callbacks, no crashes
  • llvm-nm confirms the Java_* thunk symbols are exported from libSwiftAndroidApp.so

- Replace `skip android build` with direct `swift build --swift-sdk`
  (official Swift 6.3.3 Android SDK); --disable-sandbox enables the
  swift-java jextract plugin's nested Java callbacks build
- Add CatalogBridge target using jextract JNI mode with
  enableJavaCallbacks for Swift<->Java closures/Runnable blocks
- Stage SwiftKitCore + generated Java bindings into the app build,
  vendoring Android-compatible copies of jdk.jfr-based annotations
- Smoke test in Application: Kotlin->Swift downcall and Swift->Kotlin
  closure callback, verified on emulator
Follow the trucksmart-ios integration pattern: an `jextract` Exec task
runs `swift build` with the swift.org toolchain, a `stageJniLibs` Copy
task stages product/runtime/NDK libraries, and the generated JNI Java +
SwiftKitCore sources join the source set via srcDir + filter.exclude.
Replaces build-swift.sh, the source-staging Sync task, and the vendored
jdk.jfr-free annotation copies.
- CatalogBridge exposes catalogEntryCount/Name/Summary and
  runCatalogDemo(index, onLine, onCompleted) — demo output and
  completion are delivered from Swift to Kotlin through jextract
  JNI closures/runnable blocks
- Curated demos exercising the SDK wrappers: AndroidOS (Build,
  SystemClock), AndroidContent (package/app info), AndroidLocation
  (providers), AndroidMedia (volumes, ringer), AndroidNet
  (connectivity, Wi-Fi), AndroidNFC (adapter)
- Demos acquire the application Context via AndroidContext
  (ActivityThread bootstrap), wrapped once as a JNI global ref
- Compose catalog UI: list of libraries with the Swift-driven
  clock header, per-entry demo screens
- Remove the legacy SwiftObject/external-method callback shims
  (Runnable, ViewOnClickListener, adapters, Fragment) superseded
  by the jextract bridge
- Add ACCESS_NETWORK_STATE, ACCESS_WIFI_STATE and NFC permissions
  required by the demos

All six demos verified on the emulator (arm64-v8a, API 36).
Drop the separate CatalogBridge dynamic product: it duplicated the
module (static-linked into the app library and as its own .so),
splitting module statics between two copies. The jextract config's
nativeLibraryName now points the generated Java bindings at
SwiftAndroidApp, whose exported Java_* thunk symbols resolve the
native methods.
@colemancda
colemancda merged commit 3a8ebbf into master Jul 25, 2026
16 checks passed
@colemancda
colemancda deleted the feature/demo-library-catalog branch July 25, 2026 18:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant