Meshtastic-Android/docs/archive/ble-kmp-strategy.md
James Rich 84bb6d24e4
docs: summarize KMP migration progress and architectural decisions (#4770)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
2026-03-13 02:23:25 +00:00

111 lines
6.4 KiB
Markdown

# `core:ble` KMP Strategy Analysis
> Date: 2026-03-10
>
> Context: Nordic responded to [our inquiry](https://github.com/NordicSemiconductor/Kotlin-BLE-Library/issues/183#issuecomment-4030710057) confirming KMP is on their roadmap but not yet available, and recommended KABLE for projects needing KMP now.
## Current State — Already Well-Architected
Our `core:ble` is **already one of the best-structured modules in the repo** for KMP:
| Layer | What exists | KMP-ready? |
|---|---|---|
| `commonMain` interfaces | `BleConnection`, `BleScanner`, `BleDevice`, `BleConnectionFactory`, `BluetoothRepository`, `BleConnectionState`, `BleService`, `BleRetry`, `MeshtasticBleConstants` | ✅ Pure Kotlin — zero platform imports |
| `androidMain` implementations | `AndroidBleConnection`, `AndroidBleScanner`, `AndroidBleDevice`, `AndroidBleConnectionFactory`, `AndroidBluetoothRepository`, `AndroidBleService` | ✅ Properly isolated |
| DI | `CoreBleModule` (commonMain), `CoreBleAndroidModule` (androidMain) | ✅ Clean split |
**The abstraction boundary is already drawn exactly where it needs to be.** No Nordic types leak into `commonMain`.
## The JVM Target Question
Adding `jvm()` to `core:ble` is **easy right now** — the `commonMain` has zero platform dependencies. The only blocker would be providing `jvmMain` implementations of the BLE interfaces, but for JVM (headless/desktop) we have two options:
### Option A: No-op / Stub JVM Implementation (Minimal, Unblocks CI Now)
Add `jvm()` and provide no-op or stub implementations in `jvmMain` (or don't — `commonMain` is just interfaces, it'll compile fine with no `jvmMain` source at all). Consumers on JVM would get `BleScanner`/`BleConnection` etc. from DI; a headless JVM app would simply not wire BLE into the graph.
**Effort: ~10 minutes. Unblocks JVM smoke compile immediately.**
### Option B: KABLE-backed JVM Implementation (Real Desktop BLE)
Replace or supplement the Nordic `androidMain` implementation with KABLE in `commonMain` or platform-specific source sets.
## Library Comparison
### Nordic Kotlin-BLE-Library (current: `2.0.0-alpha16`)
| Aspect | Status |
|---|---|
| Module structure | `core` and `client-core` are **pure JVM** (no Android dependencies). `client-android`, `environment-android` etc. are Android-only. |
| KMP status | **Not KMP yet.** `core` & `client-core` are JVM-only modules (not KMP multiplatform). No `iosMain`, no `commonMain` with `expect`/`actual`. |
| Roadmap | Nordic says: _"The library is intended to eventually be multiplatform on its own"_ but _"I don't have much KMP experience yet, we just started experimenting."_ |
| Our coupling | 5 Nordic imports across 6 `androidMain` files. All wrapped behind our `commonMain` interfaces. |
| Mocking | ✅ Has `client-android-mock`, `core-mock` modules — we use these in tests |
| Stability | Alpha (`2.0.0-alpha16`) — API still changing (recent breaking change in alpha16: `services()` emission) |
### KABLE (JuulLabs, current: `0.42.0`)
| Aspect | Status |
|---|---|
| KMP targets | ✅ Android, iOS, macOS, JVM, JavaScript, Wasm |
| API style | Coroutines/Flow-first. `Scanner`, `Peripheral`, `connect()`, `observe()`, `read()`, `write()` |
| JVM support | ✅ Uses Bluetooth on macOS/Linux/Windows via native bindings |
| Mocking | ❌ No mock module (Nordic's advantage) |
| Maturity | More mature than Nordic's KMP story, actively maintained |
| License | Apache 2.0 |
| Our coupling cost | Would need to rewrite 6 `androidMain` files (~400 lines total) |
## Recommended Strategy
### Phase 1: Add `jvm()` Target Now (No Library Change) ✅ COMPLETED
Since `commonMain` is already pure Kotlin interfaces, `jvm()` has been added to `core:ble/build.gradle.kts`. No JVM BLE implementation is needed — the interfaces compile fine and a headless JVM app simply wouldn't inject BLE bindings.
This unblocked `core:ble` in the JVM smoke compile. CI now validates `core:ble:compileKotlinJvm` on every PR.
### Phase 2: Evaluate Whether to Migrate to KABLE (Strategic Decision)
There are three paths, and the right one depends on project goals:
#### Path A: Stay on Nordic, Wait for Their KMP Support
- **Pro:** Zero migration work, we're already well-abstracted
- **Pro:** Nordic's mock modules are valuable for testing
- **Con:** Nordic says KMP is "intended" but has no timeline and "just started experimenting"
- **Con:** Nordic library is still alpha (API instability risk)
- **Risk:** Could be waiting 1+ years
#### Path B: Migrate to KABLE for `commonMain`, Keep Nordic as Optional Android Backend
- **Pro:** Real KMP BLE across all targets immediately
- **Pro:** KABLE is production-ready and actively maintained
- **Con:** ~400 lines of adapter code to rewrite
- **Con:** No built-in mock support (would need our own test doubles)
- **Con:** Two BLE library dependencies during transition
#### Path C: Dual-Backend Architecture (Best of Both Worlds)
Keep `commonMain` interfaces as-is. Add a `kableMain` or use KABLE in `commonMain` only for platforms that need it (JVM/iOS), keep Nordic on Android.
This is **overkill for now** but the architecture already supports it — our `BleConnection`/`BleScanner` interfaces would have multiple implementations selected via DI.
### Recommendation
**Phase 1 completed** (`jvm()` added, CI validates it).
For Phase 2: **Path A (stay on Nordic, wait)** is the pragmatic choice for now because:
1. Our abstraction layer is already clean — switching BLE backends later is a bounded, mechanical task
2. Nordic is actively developing (alpha16 released March 4, 2026 — 6 days ago)
3. We don't currently need real BLE on JVM/iOS
4. The mock modules are genuinely useful for testing
If Nordic hasn't shipped KMP by the time we're ready for iOS, revisit KABLE. The migration cost is predictable: ~6 files, ~400 lines, all in `androidMain``commonMain`.
## Potential Contribution to Nordic
Nordic is open to help. High-impact contributions we could make:
1. **File an issue or PR** showing how `core` and `client-core` could become `kotlin("multiplatform")` modules with `commonMain` + `jvmMain` source sets (they're pure JVM already — it's a build config change)
2. **Propose the `expect`/`actual` pattern** for `CentralManager` / `Peripheral` interfaces, showing how our wrapper demonstrates the abstraction boundary
3. **Share our `commonMain` interface design** as a reference for what a KMP-ready API surface looks like
This would accelerate their timeline and reduce our eventual migration friction.