mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
refactor: migrate core UI and features to KMP, adopt Navigation 3 (#4750)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
parent
b1070321fe
commit
d076361c55
245 changed files with 3106 additions and 1748 deletions
37
docs/agent-playbooks/README.md
Normal file
37
docs/agent-playbooks/README.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
# Agent Playbooks
|
||||
|
||||
These playbooks are execution-focused guidance for common changes in this repository.
|
||||
|
||||
Use `AGENTS.md` as the source of truth for architecture boundaries and required conventions. If guidance conflicts, follow `AGENTS.md` and current code patterns.
|
||||
|
||||
## Version baseline for external docs
|
||||
|
||||
When checking upstream docs/examples, match these repository-pinned versions from `gradle/libs.versions.toml`:
|
||||
|
||||
- Kotlin: `2.3.10`
|
||||
- Koin: `4.2.0-RC1` (`koin-annotations` `2.1.0`, compiler plugin `0.3.0`)
|
||||
- AndroidX Navigation 3: `1.0.1`
|
||||
- Kotlin Coroutines: `1.10.2`
|
||||
- Compose Multiplatform: `1.11.0-alpha03`
|
||||
|
||||
Prefer versioned docs pages that match those versions (for example, Koin `4.2` docs rather than older `4.0/4.1` pages).
|
||||
|
||||
Quick references:
|
||||
|
||||
- Koin annotations (4.2 docs): `https://insert-koin.io/docs/reference/koin-annotations/start`
|
||||
- Koin KMP docs: `https://insert-koin.io/docs/reference/koin-annotations/kmp`
|
||||
- AndroidX Navigation 3 release notes: `https://developer.android.com/jetpack/androidx/releases/navigation3`
|
||||
- Kotlin release notes: `https://kotlinlang.org/docs/releases.html`
|
||||
|
||||
## Playbooks
|
||||
|
||||
- `docs/agent-playbooks/common-practices.md` - architecture and coding patterns to mirror.
|
||||
- `docs/agent-playbooks/di-navigation3-anti-patterns-playbook.md` - DI and Navigation 3 mistakes to avoid.
|
||||
- `docs/agent-playbooks/kmp-source-set-bridging-playbook.md` - when to use `expect`/`actual` vs interfaces + app wiring.
|
||||
- `docs/agent-playbooks/task-playbooks.md` - step-by-step recipes for common implementation tasks.
|
||||
- `docs/agent-playbooks/testing-and-ci-playbook.md` - which Gradle tasks to run based on change type, plus CI parity.
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
52
docs/agent-playbooks/common-practices.md
Normal file
52
docs/agent-playbooks/common-practices.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Common Practices Playbook
|
||||
|
||||
This document captures discoverable patterns that are already used in the repository.
|
||||
|
||||
## 1) Module and layering boundaries
|
||||
|
||||
- Keep domain logic in KMP modules (`commonMain`) and keep Android framework wiring in `app` or `androidMain`.
|
||||
- Use `core:*` for shared logic, `feature:*` for user-facing flows, and `app` for Android entrypoints and integration wiring.
|
||||
- Example: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt` contains shared ViewModel logic, while `app/src/main/kotlin/org/meshtastic/app/messaging/AndroidMessageViewModel.kt` provides the Android/Koin wrapper.
|
||||
|
||||
## 2) Dependency injection conventions (Koin)
|
||||
|
||||
- Use Koin annotations (`@Module`, `@ComponentScan`, `@KoinViewModel`, `@KoinWorker`) and keep DI wiring discoverable from `app`.
|
||||
- Example app scan module: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt`.
|
||||
- Example app startup and module registration: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`.
|
||||
- Ensure feature/core modules are included in the app root module: `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`.
|
||||
- Prefer DI-agnostic shared logic in `commonMain`; inject from Android wrappers.
|
||||
|
||||
## 3) Navigation conventions (Navigation 3)
|
||||
|
||||
- Use Navigation 3 types (`NavKey`, `NavBackStack`, entry providers) instead of legacy controller-first patterns.
|
||||
- Example graph using `EntryProviderScope<NavKey>` and `backStack.add/removeLastOrNull`: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt`.
|
||||
- Example feature flow using `rememberNavBackStack` and `NavDisplay<NavKey>`: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/AppIntroductionScreen.kt`.
|
||||
|
||||
## 4) UI and resources
|
||||
|
||||
- Keep shared dialogs/components in `core:ui` where possible.
|
||||
- Put localizable UI strings in Compose Multiplatform resources: `core/resources/src/commonMain/composeResources/values/strings.xml`.
|
||||
- Use `stringResource(Res.string.key)` from shared resources in feature screens.
|
||||
- Example usage: `feature/node/src/androidMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt`.
|
||||
|
||||
## 5) Platform abstraction in shared UI
|
||||
|
||||
- Use `CompositionLocal` providers in `app` to inject Android/flavor-specific UI behavior into shared modules.
|
||||
- Example provider wiring in `MainActivity`: `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt`.
|
||||
- Example abstraction contract: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt`.
|
||||
|
||||
## 6) I/O and concurrency in shared code
|
||||
|
||||
- In `commonMain`, use Okio streams (`BufferedSource`/`BufferedSink`) and coroutines/Flow.
|
||||
- For ViewModel state exposure, prefer `stateInWhileSubscribed(...)` in shared ViewModels and collect in UI with `collectAsStateWithLifecycle()`.
|
||||
- Example shared extension: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/viewmodel/ViewModelExtensions.kt`.
|
||||
- Example Okio usage in shared domain code:
|
||||
- `core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ImportProfileUseCase.kt`
|
||||
- `core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/usecase/settings/ExportDataUseCase.kt`
|
||||
|
||||
## 7) Namespace and compatibility
|
||||
|
||||
- New code should use `org.meshtastic.*`.
|
||||
- Keep compatibility constraints where required (notably legacy app ID and intent signatures for external integration).
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# DI and Navigation 3 Anti-Patterns Playbook
|
||||
|
||||
This playbook is a fast guardrail for high-risk mistakes in dependency injection and navigation.
|
||||
|
||||
Version note: align guidance with repository-pinned versions in `gradle/libs.versions.toml` (currently Koin `4.2.x` and Navigation 3 `1.0.x`).
|
||||
|
||||
## DI anti-patterns
|
||||
|
||||
- Don't put Android framework dependencies (`Context`, `Activity`, `Application`) into shared `commonMain` business logic.
|
||||
- Do keep shared logic DI-agnostic where practical, then bind it from Android/app layer wiring.
|
||||
- Don't instantiate ViewModels or service dependencies manually in Compose or activities.
|
||||
- Do resolve app-layer wrappers via Koin (`koinViewModel()` / injected bindings).
|
||||
- Don't spread DI graph setup across unrelated modules without registration in app startup.
|
||||
- Do ensure modules are reachable from app bootstrap in `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`.
|
||||
- Don't assume feature/core `@Module` classes are active automatically.
|
||||
- Do ensure they are included by the app root module (`@Module(includes = [...])`) in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`.
|
||||
|
||||
### Current code anchors (DI)
|
||||
|
||||
- App-level module scanning: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt`
|
||||
- App startup + Koin init: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`
|
||||
- Android wrapper ViewModel pattern: `app/src/main/kotlin/org/meshtastic/app/messaging/AndroidMessageViewModel.kt`
|
||||
- Shared ViewModel base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt`
|
||||
|
||||
## Navigation 3 anti-patterns
|
||||
|
||||
- Don't reintroduce controller-coupled navigation APIs for shared flow state.
|
||||
- Do use Navigation 3 types (`NavKey`, `NavBackStack`, `EntryProviderScope`) consistently.
|
||||
- Don't build route identifiers as ad-hoc strings in feature code when typed route keys already exist.
|
||||
- Do keep route definitions in `core:navigation` and use typed route objects.
|
||||
- Don't mutate back navigation with custom stacks disconnected from app backstack.
|
||||
- Do mutate `NavBackStack<NavKey>` with `add(...)` and `removeLastOrNull()`.
|
||||
|
||||
### Current code anchors (Navigation 3)
|
||||
|
||||
- Typed routes: `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt`
|
||||
- App root backstack + `NavDisplay`: `app/src/main/kotlin/org/meshtastic/app/ui/Main.kt`
|
||||
- Graph entry provider pattern: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt`
|
||||
- Feature-level Navigation 3 usage: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/AppIntroductionScreen.kt`
|
||||
|
||||
## Quick pre-PR checks for DI/navigation edits
|
||||
|
||||
- Verify affected graph/module is registered and reachable from app startup.
|
||||
- Verify no new Android framework type leaks into `commonMain`.
|
||||
- Verify routes/backstack use typed keys and Navigation 3 primitives.
|
||||
- Run targeted verification from `docs/agent-playbooks/testing-and-ci-playbook.md`.
|
||||
|
||||
|
||||
|
||||
43
docs/agent-playbooks/kmp-source-set-bridging-playbook.md
Normal file
43
docs/agent-playbooks/kmp-source-set-bridging-playbook.md
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# KMP Source-Set Bridging Playbook
|
||||
|
||||
Use this playbook when introducing platform-specific behavior into shared modules.
|
||||
|
||||
## 1) Decide if `expect`/`actual` is needed
|
||||
|
||||
Use `expect`/`actual` only when a platform API cannot be abstracted cleanly behind an interface passed from app wiring.
|
||||
|
||||
- Prefer interface + DI when behavior is already app-owned.
|
||||
- Prefer `expect`/`actual` for small platform primitives and utilities.
|
||||
|
||||
Examples in current code:
|
||||
- `core/common/src/commonMain/kotlin/org/meshtastic/core/common/util/CommonUri.kt`
|
||||
- `core/common/src/androidMain/kotlin/org/meshtastic/core/common/util/CommonUri.android.kt`
|
||||
- `core/repository/src/commonMain/kotlin/org/meshtastic/core/repository/LocationRepository.kt`
|
||||
|
||||
## 2) Keep source-set boundaries strict
|
||||
|
||||
- `commonMain`: business logic, shared models, coroutine/Flow orchestration.
|
||||
- `androidMain`: Android framework integration (`Context`, system services, Android SDK).
|
||||
- `app`: app bootstrap, DI root inclusion, Activity/service wiring, flavor-specific providers.
|
||||
|
||||
## 3) Resource and UI bridging rules
|
||||
|
||||
- Shared strings/resources must come from `core:resources`.
|
||||
- Platform/flavor UI implementations should be injected via `CompositionLocal` from app.
|
||||
|
||||
Examples:
|
||||
- Contract: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt`
|
||||
- Provider wiring: `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt`
|
||||
|
||||
## 4) DI and module activation checks
|
||||
|
||||
- If a new feature/core module adds Koin annotations, verify it is included by app root module includes.
|
||||
- App root includes are defined in `app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`.
|
||||
|
||||
## 5) Verification checklist
|
||||
|
||||
- No Android-only imports in `commonMain`.
|
||||
- `expect`/`actual` declarations compile across relevant source sets.
|
||||
- Routing/DI still resolves from app startup (`MeshUtilApplication`).
|
||||
- Run verification tasks from `docs/agent-playbooks/testing-and-ci-playbook.md` appropriate to touched modules.
|
||||
|
||||
66
docs/agent-playbooks/task-playbooks.md
Normal file
66
docs/agent-playbooks/task-playbooks.md
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
# Task Playbooks
|
||||
|
||||
Use these as practical recipes. Keep edits minimal and aligned with existing module boundaries.
|
||||
|
||||
## Playbook A: Add or update a user-visible string
|
||||
|
||||
1. Add/update key in `core/resources/src/commonMain/composeResources/values/strings.xml`.
|
||||
2. Import generated resource symbol in UI code (`org.meshtastic.core.resources.<key>`).
|
||||
3. Use `stringResource(Res.string.<key>)` in Compose.
|
||||
4. If the string appears in a shared dialog, prefer `core:ui` dialog components.
|
||||
5. Verify no hardcoded user-facing strings were introduced.
|
||||
|
||||
Reference examples:
|
||||
- `feature/node/src/androidMain/kotlin/org/meshtastic/feature/node/list/NodeListScreen.kt`
|
||||
- `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/component/AlertDialogs.kt`
|
||||
|
||||
## Playbook B: Add shared ViewModel logic in a feature module
|
||||
|
||||
1. Implement or extend base ViewModel logic in `feature/<name>/src/commonMain/...`.
|
||||
2. Keep shared class free of Android framework dependencies.
|
||||
3. Keep Android framework dependencies out of shared logic; if the module already uses Koin annotations in `commonMain`, keep patterns consistent and ensure app root inclusion.
|
||||
4. Add/update Android wrapper in `app/src/main/kotlin/org/meshtastic/app/...` with `@KoinViewModel` when Android instantiation is needed.
|
||||
5. Update navigation entry points in `app/src/main/kotlin/org/meshtastic/app/navigation/...` to resolve wrapper ViewModels with `koinViewModel()`.
|
||||
|
||||
Reference examples:
|
||||
- Shared base: `feature/messaging/src/commonMain/kotlin/org/meshtastic/feature/messaging/MessageViewModel.kt`
|
||||
- Android wrapper: `app/src/main/kotlin/org/meshtastic/app/messaging/AndroidMessageViewModel.kt`
|
||||
- Navigation usage: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt`
|
||||
|
||||
## Playbook C: Add a new dependency or service binding
|
||||
|
||||
1. Check `gradle/libs.versions.toml` for existing library and version alias.
|
||||
2. Add new dependency to version catalog first (if truly new).
|
||||
3. Wire implementation in the owning module (`core:*`, `feature:*`, or `app`) following existing architecture.
|
||||
4. Register bindings/modules in app Koin graph where needed.
|
||||
5. For Android system integration (WorkManager, service bootstrapping), wire via `MeshUtilApplication` and app-layer modules.
|
||||
|
||||
Reference examples:
|
||||
- App startup and Koin bootstrap: `app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`
|
||||
- App module scan: `app/src/main/kotlin/org/meshtastic/app/MainKoinModule.kt`
|
||||
|
||||
## Playbook D: Add or modify navigation flow
|
||||
|
||||
1. Define/extend route keys in `core:navigation`.
|
||||
2. Implement feature entry/content using Navigation 3 types (`NavKey`, `NavBackStack`, `EntryProviderScope`).
|
||||
3. Add graph entries under `app/src/main/kotlin/org/meshtastic/app/navigation`.
|
||||
4. Use backstack mutation (`add`, `removeLastOrNull`) instead of introducing controller-coupled APIs.
|
||||
5. Verify deep-link behavior if route is externally reachable.
|
||||
|
||||
Reference examples:
|
||||
- App graph wiring: `app/src/main/kotlin/org/meshtastic/app/navigation/SettingsNavigation.kt`
|
||||
- Feature intro graph pattern: `feature/intro/src/androidMain/kotlin/org/meshtastic/feature/intro/IntroNavGraph.kt`
|
||||
|
||||
## Playbook E: Add flavor/platform-specific UI implementation
|
||||
|
||||
1. Keep shared contracts in `core:ui` or feature shared code.
|
||||
2. Inject flavor/platform implementation via `CompositionLocal` from `app`.
|
||||
3. Avoid direct dependency from shared modules to Google Maps/osmdroid/other Android SDK-only APIs.
|
||||
4. Keep adapter types narrow and stable (interfaces, DTO-like params).
|
||||
|
||||
Reference examples:
|
||||
- Contract: `core/ui/src/commonMain/kotlin/org/meshtastic/core/ui/util/MapViewProvider.kt`
|
||||
- Provider wiring: `app/src/main/kotlin/org/meshtastic/app/MainActivity.kt`
|
||||
- Consumer side: `feature/map/src/androidMain/kotlin/org/meshtastic/feature/map/MapScreen.kt`
|
||||
|
||||
|
||||
73
docs/agent-playbooks/testing-and-ci-playbook.md
Normal file
73
docs/agent-playbooks/testing-and-ci-playbook.md
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Testing and CI Playbook
|
||||
|
||||
Use this matrix to choose the right verification depth for a change.
|
||||
|
||||
## 1) Baseline local verification order
|
||||
|
||||
Run in this order for routine changes:
|
||||
|
||||
```bash
|
||||
./gradlew clean
|
||||
./gradlew spotlessCheck
|
||||
./gradlew spotlessApply
|
||||
./gradlew detekt
|
||||
./gradlew assembleDebug
|
||||
./gradlew test
|
||||
```
|
||||
|
||||
Notes:
|
||||
- This order aligns with repository guidance in `AGENTS.md` and `.github/copilot-instructions.md`.
|
||||
- CI additionally runs `testDebugUnitTest` in `.github/workflows/reusable-check.yml`.
|
||||
|
||||
## 2) Change-type matrix
|
||||
|
||||
- `docs-only` changes:
|
||||
- Usually no Gradle run required.
|
||||
- If you touched code examples or command docs, at least run `spotlessCheck` if practical.
|
||||
- `UI text/resource` changes:
|
||||
- `spotlessCheck`, `detekt`, `assembleDebug`.
|
||||
- `feature/commonMain logic` changes:
|
||||
- `spotlessCheck`, `detekt`, `test`, `assembleDebug`.
|
||||
- `navigation/DI wiring` changes (app graph, Koin module/wrapper changes):
|
||||
- `spotlessCheck`, `detekt`, `assembleDebug`, `test`, plus `testDebugUnitTest` if available locally.
|
||||
- `worker/service/background` changes:
|
||||
- `spotlessCheck`, `detekt`, `assembleDebug`, `test`, and targeted tests around WorkManager/service behavior.
|
||||
- `BLE/networking/core repository` changes:
|
||||
- `spotlessCheck`, `detekt`, `assembleDebug`, `test`.
|
||||
|
||||
## 3) Flavor and instrumentation checks
|
||||
|
||||
Run these when relevant to map/provider/flavor-specific behavior:
|
||||
|
||||
```bash
|
||||
./gradlew lintFdroidDebug lintGoogleDebug
|
||||
./gradlew testFdroidDebug
|
||||
./gradlew testGoogleDebug
|
||||
./gradlew connectedAndroidTest
|
||||
```
|
||||
|
||||
## 4) CI parity checks
|
||||
|
||||
Current reusable check workflow includes:
|
||||
|
||||
- `spotlessCheck detekt`
|
||||
- `testDebugUnitTest testFdroidDebugUnitTest testGoogleDebugUnitTest`
|
||||
- `koverXmlReport app:koverXmlReportFdroidDebug app:koverXmlReportGoogleDebug`
|
||||
- `assembleDebug`
|
||||
- `lintDebug`
|
||||
- `connectedDebugAndroidTest` (when emulator tests are enabled)
|
||||
|
||||
Reference: `.github/workflows/reusable-check.yml`
|
||||
|
||||
PR workflow note:
|
||||
|
||||
- `.github/workflows/pull-request.yml` ignores docs-only changes (`**.md`, `docs/**`), so doc-only PRs may skip Android CI by design.
|
||||
- Android CI on PRs runs with `run_instrumented_tests: false`; emulator tests are handled in other workflow contexts.
|
||||
|
||||
## 5) Practical guidance for agents
|
||||
|
||||
- Start with the smallest set that validates your touched area.
|
||||
- If modifying cross-module contracts (routes, repository interfaces, DI graph), run the broader baseline.
|
||||
- If unable to run full validation locally, report exactly what ran and what remains.
|
||||
|
||||
|
||||
34
docs/ble-kmp-abstraction-plan.md
Normal file
34
docs/ble-kmp-abstraction-plan.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# Phase 8: `core:ble` KMP Abstraction
|
||||
|
||||
## Objective
|
||||
Migrate `core:ble` from an Android-only library (`meshtastic.android.library`) to a Kotlin Multiplatform library (`meshtastic.kmp.library`). The goal is to provide a unified, platform-agnostic Bluetooth Low Energy (BLE) interface for the rest of the application (e.g., `core:domain`, `core:data`), while explicitly supporting future Desktop and Web targets.
|
||||
|
||||
## Strategy: The "Nordic Hybrid" Abstraction
|
||||
We will use an Interface-Driven (Dependency Injection) approach rather than relying directly on Nordic's KMM library in `commonMain` or using raw `expect`/`actual` for the entire BLE stack.
|
||||
|
||||
Nordic's [KMM-BLE-Library](https://github.com/NordicSemiconductor/Kotlin-BLE-Library) provides excellent, battle-tested Coroutine/Flow APIs for Android and iOS. However, it **does not support Desktop (JVM/Windows/Linux/macOS) or Web (Wasm/JS)**. If we expose Nordic's classes directly in `commonMain`, the project will fail to compile for Desktop/Web targets.
|
||||
|
||||
To resolve this, we will build a custom abstraction layer:
|
||||
|
||||
### 1. The Common Interfaces (`commonMain`)
|
||||
Define pure Kotlin interfaces and data classes representing BLE operations. The rest of the app will only know about these interfaces.
|
||||
* `BleScanner`: For discovering devices.
|
||||
* `BleDevice`: Represents a remote peripheral.
|
||||
* `BleConnectionManager`: Handles connect/disconnect, MTU negotiation, and characteristic read/write/subscribe operations.
|
||||
* *Note: No Nordic dependencies will exist in `commonMain`.*
|
||||
|
||||
### 2. The Android & iOS Implementations (`androidMain` & `iosMain`)
|
||||
These source sets will depend on the Nordic `KMM-BLE-Library`. We will write concrete implementations of our common interfaces (e.g., `NordicBleConnectionManager`) that delegate operations to Nordic's `CentralManager` and `Peripheral` classes.
|
||||
|
||||
### 3. The Future Implementations (`desktopMain` / `webMain`)
|
||||
By keeping `commonMain` free of Nordic dependencies, we reserve the ability to implement our BLE interfaces using other libraries (like [Kable](https://github.com/JuulLabs/kable) or Web Bluetooth APIs) on unsupported platforms without rewriting the core application logic.
|
||||
|
||||
## Execution Plan
|
||||
1. ✅ **Refactor Build Script:** Convert `core/ble/build.gradle.kts` to use the KMP plugin and define `commonMain` and `androidMain` source sets. Move Nordic dependencies to `androidMain`.
|
||||
2. ✅ **Define Abstractions:** Create pure Kotlin interfaces (`BleScanner`, `BleConnection`, etc.) in `commonMain`.
|
||||
3. ✅ **Implement Wrappers:** Move the existing Android-specific Nordic implementation into `androidMain` and adapt it to implement the new `commonMain` interfaces.
|
||||
4. ✅ **Update DI:** Adjust the Hilt/DI modules in `app` or `androidMain` to bind the Android-specific Nordic wrappers to the common interfaces.
|
||||
5. ✅ **Verify:** Ensure the Android app builds and tests pass, confirming the abstraction works correctly.
|
||||
|
||||
## Status: Completed
|
||||
This phase was successfully executed. The Nordic SDK is now fully wrapped by common KMP interfaces (`BleDevice`, `BleScanner`, etc.). The DI modules have been relocated to the `app` module to accommodate Hilt limitations with KMP projects. All tests and integrations have been updated to use the new abstracted interfaces.
|
||||
82
docs/kmp-migration.md
Normal file
82
docs/kmp-migration.md
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
# Kotlin Multiplatform (KMP) Migration Guide
|
||||
|
||||
> [!IMPORTANT]
|
||||
> This document is now primarily a **historical migration guide**.
|
||||
> For the current evidence-backed status snapshot, see [`docs/kmp-progress-review-2026.md`](./kmp-progress-review-2026.md).
|
||||
|
||||
## Overview
|
||||
Meshtastic-Android is actively migrating its core logic layers to Kotlin Multiplatform (KMP). This migration decouples the business logic, domain models, local storage, network protocols, and dependency injection from the Android JVM framework. The ultimate goal is a modular, highly testable `core` that can be shared across multiple platforms (e.g., Android, Desktop, and potentially iOS).
|
||||
|
||||
## Historical Status Snapshot
|
||||
|
||||
By early 2026, the migration had successfully decoupled the foundational data and domain layers, and the primary namespace had been unified to `org.meshtastic`.
|
||||
|
||||
For the current state of completion, blockers, and remaining effort, use [`docs/kmp-progress-review-2026.md`](./kmp-progress-review-2026.md).
|
||||
|
||||
### Accomplished Milestones
|
||||
|
||||
* **Early Foundations (2022-2025):**
|
||||
* ✅ **Storage and repository groundwork:** DataStore adoption, repository-pattern refactors, and service/data decoupling began well before the explicit KMP conversion wave.
|
||||
* ✅ **`core:model` & `core:proto`:** Migrated early as pure data layers.
|
||||
* ✅ **`core:strings` / `core:resources`:** Migrated to Compose Multiplatform for unified string resources (#3617, #3669).
|
||||
* ✅ **Logging:** Replaced Android-bound `Timber` with KMP-ready `Kermit` (#4083).
|
||||
* ✅ **`core:common`:** Decoupled basic utilities and cleanly extracted away from Android constraints (#4026).
|
||||
* **Namespace Modernization:**
|
||||
* The `app` module source code was completely relocated from `com.geeksville.mesh` to `org.meshtastic.app`.
|
||||
* **Legacy Compatibility:** External integrations (like ATAK) rely on legacy Android Intents. `AndroidManifest.xml` preserves the `<action android:name="com.geeksville.mesh.*" />` signatures to ensure unbroken backwards compatibility.
|
||||
* **Module Conversions (`meshtastic.android.library` -> `meshtastic.kmp.library`):**
|
||||
* ✅ **`core:repository`:** Interfaces extracted to `commonMain`.
|
||||
* ✅ **`core:domain`:** Use cases migrated. Android `Handler` and `java.io.File` logic replaced with Coroutines and Okio (#4731, #4685).
|
||||
* ✅ **`core:prefs`:** Android SharedPreferences replaced with Multiplatform DataStore (#4731).
|
||||
* ✅ **`core:network`:** Extracted KMP interfaces for MQTT and local network abstractions.
|
||||
* ✅ **`core:di`:** Coroutine dispatchers mapped to standard Kotlin abstractions instead of Android thread pools.
|
||||
* ✅ **`core:database`:** Migrated to Room Kotlin Multiplatform (#4702).
|
||||
* ✅ **`core:data`:** Concrete repository implementations moved to `commonMain`. Android-specific logic (e.g., parsing `device_hardware.json` from `assets`) was abstracted behind KMP interfaces with implementations provided in `androidMain`.
|
||||
* **Architecture Refinements:**
|
||||
* `core:analytics` was completely dissolved. Abstract tracking interfaces were moved to `core:repository`, and concrete SDK implementations (Firebase, DataDog) were moved to the `app` module.
|
||||
* Test stability greatly improved by eliminating Robolectric for core logic tests in favor of pure MockK stubs.
|
||||
|
||||
* ✅ **`core:ble` / `core:bluetooth`:** Implemented a "Nordic Hybrid" Interface-Driven abstraction. Defined pure KMP interfaces (`BleConnectionManager`, `BleDevice`, etc.) in `commonMain` so that Desktop and Web targets can compile, while using Nordic's `KMM-BLE-Library` specifically inside the `androidMain` source set.
|
||||
* ✅ **`core:service`:** Converted to a KMP module, isolating Android service bindings and lifecycle concerns to `androidMain`.
|
||||
* ℹ️ **`core:api`:** Remains an Android-specific integration module because AIDL is Android-only. Treat it as a platform adapter rather than a shared KMP target.
|
||||
|
||||
### Remaining Work for Broader KMP Maturity
|
||||
The main bottleneck is no longer simply “moving code into KMP modules.” The remaining work is now about validating and hardening that architecture for non-Android targets.
|
||||
|
||||
1. **Android-edge modules still remain platform-specific:**
|
||||
* **`core:barcode` / `core:nfc`:** Android-specific hardware integrations. *Partially addressed:* `core:ui` no longer depends on them directly and abstracts scanning via `CompositionLocalProvider`.
|
||||
* **`core:api`:** Intentionally Android-specific because AIDL is Android-only. Any transport-neutral contracts should continue to be separated from the Android adapter layer.
|
||||
2. **Feature modules are structurally migrated, but cleanup continues:**
|
||||
* *Current State:* all `feature/*` modules now build as KMP libraries, and `androidx.lifecycle.ViewModel` is KMP-compatible.
|
||||
* **`feature:messaging`, `feature:intro`, `feature:map`, `feature:settings`, `feature:node`, `feature:firmware`:** all have major logic/UI in shared modules, with Android-specific adapters isolated where still required.
|
||||
* Remaining work is mostly about boundary cleanup, platform adapter consistency, and ensuring future non-Android targets can compile cleanly.
|
||||
3. **Cross-target validation is still incomplete:**
|
||||
* Most KMP modules currently declare only Android targets in practice.
|
||||
* CI still validates Android builds and tests, but not a broad JVM/iOS/Desktop target matrix.
|
||||
4. **`core:ui` & Navigation are largely complete, but now need target hardening rather than migration work:**
|
||||
* ✅ **Navigation:** Migrated fully to **AndroidX Navigation 3**. The backstack is now a simple state list (`List<NavKey>`), enabling trivial sharing across multiplatform targets without relying on Android's legacy `NavController` or `navigation-compose`.
|
||||
* ✅ **`core:ui`:** Converted to a pure KMP library (`meshtastic.kmp.library.compose`).
|
||||
* Abstracted Clipboard, Intents, and Bitmaps via `PlatformUtils` and `expect`/`actual`.
|
||||
* Replaced Android's `Linkify` with a pure Kotlin Regex and `AnnotatedString` solution.
|
||||
* Ensured all shared UI components rely solely on Compose Multiplatform.
|
||||
* The remaining work here is mostly validation on additional targets and continued isolation of Android-only framework hooks.
|
||||
|
||||
### Dependency Injection
|
||||
The project currently uses **Koin Annotations**.
|
||||
* **Current State:** `core:di` is a KMP module that exposes `javax.inject` annotations (`@Inject`), and the app root still assembles the graph in `AppKoinModule`.
|
||||
* **Important Update:** The original plan was to keep all DI-dependent components centralized in the `app` module, but the current implementation now includes some Koin `@Module`, `@ComponentScan`, and `@KoinViewModel` usage directly in `commonMain` shared modules. See [`docs/kmp-progress-review-2026.md`](./kmp-progress-review-2026.md) for the current architecture assessment.
|
||||
* **Accomplished:** We have successfully migrated from Hilt (Dagger) to **Koin 4.x** using the compiler plugin, completely removing Hilt from the project to enable deeper Multiplatform adoption.
|
||||
|
||||
## Best Practices & Guidelines (2026)
|
||||
When contributing to `core` modules, adhere to the following KMP standards:
|
||||
|
||||
* **No Android Context in `commonMain`:** Never pass `Context`, `Application`, or `Activity` into `commonMain`. Use Dependency Injection to provide platform-specific implementations from `androidMain` or `app`.
|
||||
* **ViewModels:** Use `androidx.lifecycle.ViewModel` and `viewModelScope` within `commonMain` for platform-agnostic state management. The original target pattern was to keep shared ViewModels DI-agnostic and provide app-level Koin wrappers, but the current codebase now contains some Koin annotations directly in shared modules. Prefer the more framework-light pattern for new code unless there is a clear reason to couple a shared ViewModel to Koin.
|
||||
* **Testing:** Use pure `kotlin.test` and `MockK` for unit tests in `commonTest`. Avoid `Robolectric` unless explicitly testing an `androidMain` component. Platform-specific unit tests (e.g. for Workers) should be relocated to the `app` module's `test` source set if they depend on Koin components.
|
||||
* **Resources:** Use Compose Multiplatform Resources (`core:resources`) for all strings and drawables. Never use Android `strings.xml` in `commonMain`.
|
||||
* **Coroutines & Flows:** Use `StateFlow` and `SharedFlow` for all asynchronous state management across the domain layer.
|
||||
* **Persistence:** Use `androidx.datastore` for preferences and Room KMP for complex relational data.
|
||||
* **Dependency Injection:** Prefer keeping `commonMain` classes dependent on agnostic interfaces and minimal DI surface area. The current codebase does include some Koin annotations in shared modules, so treat that as an implementation reality rather than a blanket rule for new code.
|
||||
|
||||
---
|
||||
*Document refreshed on 2026-03-10 as a historical companion to `docs/kmp-progress-review-2026.md`.*
|
||||
685
docs/kmp-progress-review-2026.md
Normal file
685
docs/kmp-progress-review-2026.md
Normal file
|
|
@ -0,0 +1,685 @@
|
|||
# KMP Progress Re-evaluation — March 2026
|
||||
|
||||
> Snapshot date: 2026-03-10
|
||||
>
|
||||
> This document is an evidence-backed re-baseline of Meshtastic-Android's Kotlin Multiplatform migration progress. It supplements and partially corrects the historical narrative in [`docs/kmp-migration.md`](./kmp-migration.md).
|
||||
|
||||
## Scope
|
||||
|
||||
This review covers:
|
||||
|
||||
- all `core:*` and `feature:*` modules in [`settings.gradle.kts`](../settings.gradle.kts)
|
||||
- build conventions in [`build-logic/convention`](../build-logic/convention)
|
||||
- current DI wiring in [`app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`](../app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt)
|
||||
- current application startup in [`app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`](../app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt)
|
||||
- local git history through 2026-03-10
|
||||
- current dependency state in [`gradle/libs.versions.toml`](../gradle/libs.versions.toml)
|
||||
|
||||
---
|
||||
|
||||
## Executive summary
|
||||
|
||||
Meshtastic-Android has made **substantial structural KMP progress** very quickly in early 2026.
|
||||
|
||||
The migration is **farther along than a normal Android app**, but **not as far along as the existing migration guide sometimes implies**.
|
||||
|
||||
### Headline assessment
|
||||
|
||||
| Dimension | Status | Assessment |
|
||||
|---|---:|---|
|
||||
| Core + feature module structural KMP conversion | **22 / 25** | Strong |
|
||||
| Core-only structural KMP conversion | **16 / 19** | Strong |
|
||||
| Feature module structural KMP conversion | **6 / 6** | Excellent |
|
||||
| Explicit non-Android target declarations | **1 / 25** | Very low |
|
||||
| Android-only blocker modules left | **3** | Clear, bounded |
|
||||
| Cross-target CI verification | **0 non-Android jobs** | Missing |
|
||||
|
||||
### Bottom line
|
||||
|
||||
- **If the question is “Have we mostly moved business logic into shared KMP modules?”** → **yes**.
|
||||
- **If the question is “Could we realistically add iOS/Desktop with limited cleanup?”** → **not yet**.
|
||||
- **If the question is “Are we now on the right architecture path?”** → **yes, strongly**.
|
||||
|
||||
### Progress scorecard
|
||||
|
||||
| Area | Score | Notes |
|
||||
|---|---:|---|
|
||||
| Shared business/data logic | **8.5 / 10** | `core:data`, `core:domain`, `core:database`, `core:prefs`, `core:network`, `core:repository` are structurally shared |
|
||||
| Shared feature/UI logic | **8 / 10** | All feature modules are KMP; `core:ui` and Navigation 3 are in place |
|
||||
| Android decoupling | **7 / 10** | `commonMain` is clean of direct Android imports, but edge modules still anchor to Android |
|
||||
| Multi-target readiness | **2.5 / 10** | Nearly all KMP modules still declare only Android targets |
|
||||
| DI portability hygiene | **5 / 10** | Koin works, but `commonMain` now contains Koin modules/annotations despite prior architectural guidance |
|
||||
| CI confidence for future iOS/Desktop | **2 / 10** | CI is Android-only today |
|
||||
|
||||
```mermaid
|
||||
pie showData
|
||||
title Core + Feature module state
|
||||
"KMP modules" : 22
|
||||
"Android-only modules" : 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## What is genuinely complete
|
||||
|
||||
### 1. The architectural center of gravity has moved into shared modules
|
||||
|
||||
This is the biggest success.
|
||||
|
||||
Evidence in current build files shows these are already on `meshtastic.kmp.library`:
|
||||
|
||||
- `core:ble`
|
||||
- `core:common`
|
||||
- `core:data`
|
||||
- `core:database`
|
||||
- `core:datastore`
|
||||
- `core:di`
|
||||
- `core:domain`
|
||||
- `core:model`
|
||||
- `core:navigation`
|
||||
- `core:network`
|
||||
- `core:prefs`
|
||||
- `core:proto`
|
||||
- `core:repository`
|
||||
- `core:resources`
|
||||
- `core:service`
|
||||
- `core:ui`
|
||||
- all feature modules: `intro`, `messaging`, `map`, `node`, `settings`, `firmware`
|
||||
|
||||
That is a major milestone. The repo is no longer “Android app with a few shared helpers”; it is now “Android app with a shared KMP core and KMP feature stack.”
|
||||
|
||||
### 2. Shared UI architecture is materially real, not aspirational
|
||||
|
||||
Current evidence supports the following:
|
||||
|
||||
- `core:ui` is KMP via [`core/ui/build.gradle.kts`](../core/ui/build.gradle.kts)
|
||||
- `core:resources` uses Compose Multiplatform resources via [`core/resources/build.gradle.kts`](../core/resources/build.gradle.kts)
|
||||
- `core:navigation` uses Navigation 3 runtime in `commonMain` via [`core/navigation/build.gradle.kts`](../core/navigation/build.gradle.kts)
|
||||
- feature modules are KMP Compose modules via their `build.gradle.kts` files
|
||||
|
||||
This is unusually advanced for an Android-first app.
|
||||
|
||||
### 3. The Hilt → Koin migration is complete enough to unblock KMP
|
||||
|
||||
Current app startup and root assembly are clearly Koin-based:
|
||||
|
||||
- [`MeshUtilApplication.kt`](../app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt)
|
||||
- [`AppKoinModule.kt`](../app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt)
|
||||
|
||||
This is strategically important because Hilt would have remained one of the strongest barriers to deeper KMP adoption.
|
||||
|
||||
### 4. The BLE architecture is moving in the correct direction
|
||||
|
||||
The repo's BLE direction is good:
|
||||
|
||||
- `core:ble` is KMP
|
||||
- Android Nordic dependencies are isolated to `androidMain` in [`core/ble/build.gradle.kts`](../core/ble/build.gradle.kts)
|
||||
- the repo already adopted an abstraction-first BLE shape instead of leaking vendor APIs through the domain layer
|
||||
|
||||
That makes future alternative platform implementations possible.
|
||||
|
||||
---
|
||||
|
||||
## What is **not** complete yet
|
||||
|
||||
## 1. The repo is structurally KMP, but not yet truly multi-target
|
||||
|
||||
This is the single most important correction.
|
||||
|
||||
Most KMP modules currently use the Android KMP library plugin and define only an Android target.
|
||||
|
||||
The clearest evidence is in build logic:
|
||||
|
||||
- [`KmpLibraryConventionPlugin.kt`](../build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt) applies:
|
||||
- `org.jetbrains.kotlin.multiplatform`
|
||||
- `com.android.kotlin.multiplatform.library`
|
||||
- [`KotlinAndroid.kt`](../build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt) configures Android KMP targets automatically
|
||||
- only [`core/proto/build.gradle.kts`](../core/proto/build.gradle.kts) explicitly adds `jvm()`
|
||||
|
||||
So today the repo has:
|
||||
|
||||
- **broad shared source-set adoption**
|
||||
- **very little explicit second-target validation**
|
||||
|
||||
That means the current state is best described as:
|
||||
|
||||
> **“Android-first KMP-ready”**, not yet **“actively multi-platform validated.”**
|
||||
|
||||
## 2. Three core modules remain plainly Android-only
|
||||
|
||||
These are the biggest structural holdouts:
|
||||
|
||||
- [`core/api/build.gradle.kts`](../core/api/build.gradle.kts) → `meshtastic.android.library`
|
||||
- [`core/barcode/build.gradle.kts`](../core/barcode/build.gradle.kts) → `meshtastic.android.library`
|
||||
- [`core/nfc/build.gradle.kts`](../core/nfc/build.gradle.kts) → `meshtastic.android.library`
|
||||
|
||||
These are not minor details; they sit exactly at the platform edge:
|
||||
|
||||
- AIDL / service API surface
|
||||
- camera + barcode scanning
|
||||
- NFC hardware integration
|
||||
|
||||
This is acceptable in the short term, but it means the “full KMP core” is not done.
|
||||
|
||||
## 3. The historical migration narrative overstated `core:api`
|
||||
|
||||
Earlier migration wording grouped `core:service` and `core:api` together as if both had become KMP modules.
|
||||
|
||||
Current code shows a split reality:
|
||||
|
||||
- `core:service` **is** KMP
|
||||
- `core:api` **is not**; it is still Android-only, which makes sense because AIDL is Android-only
|
||||
|
||||
The accurate statement is:
|
||||
|
||||
> `core:service` is KMP, while `core:api` remains an Android adapter/public integration module.
|
||||
|
||||
## 4. Shared-module DI became a real architecture change during the migration sprint
|
||||
|
||||
Earlier migration guidance aimed to keep DI-dependent components centralized in `app`.
|
||||
|
||||
That is **not how the current codebase ended up**.
|
||||
|
||||
Current codebase evidence:
|
||||
|
||||
- [`core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/di/CoreDomainModule.kt`](../core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/di/CoreDomainModule.kt) contains `@Module` + `@ComponentScan`
|
||||
- [`feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/di/FeatureMapModule.kt`](../feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/di/FeatureMapModule.kt) contains `@Module`
|
||||
- [`feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/di/FeatureSettingsModule.kt`](../feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/di/FeatureSettingsModule.kt) contains `@Module`
|
||||
- [`feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt`](../feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt) contains `@KoinViewModel`
|
||||
|
||||
So the real state is:
|
||||
|
||||
> Koin has been pushed down into shared modules already.
|
||||
|
||||
That is not necessarily wrong, but it is a **material architectural change** from the old migration mandate and should be treated explicitly.
|
||||
|
||||
---
|
||||
|
||||
## Git-history timeline
|
||||
|
||||
Before the explicit KMP conversion wave in 2026, the repo spent roughly **20+ months** accumulating the architectural preconditions for KMP.
|
||||
|
||||
### Long-runway foundations before explicit KMP
|
||||
|
||||
- **2022-06-11 — `54f611290`**: LocalConfig moved to **DataStore**
|
||||
- This was an early signal away from Android-only preference plumbing and toward serializable/shared state management.
|
||||
- **2024-02-06 — `c8f93db00`**: Repository pattern for **NodeDB**
|
||||
- This started separating storage/service concerns from direct consumers.
|
||||
- **2024-08-25 — `0b7718f8d`**: Write to proto **DataStore** using dynamic field updates
|
||||
- Important because it normalized protobuf-backed state handling in a way that later mapped cleanly into shared logic.
|
||||
- **2024-09-13 — `39a18e641`**: Replace service local node DB with **Room NodeDB**
|
||||
- A precursor to the later Room KMP move.
|
||||
- **2024-11-21 — `80f8f2a59`**: Repository-pattern replacement for **AIDL methods**
|
||||
- Important platform-edge cleanup ahead of any `core:api` / `core:service` separation.
|
||||
- **2024-11-30 — `716a3f535`**: **NavGraph decoupled** from ViewModel and entity types
|
||||
- This is classic KMP-enabling work: remove Android-navigation entanglement before trying to share navigation state.
|
||||
- **2025-04-24 — `5cd3a0229`**: `DeviceHardwareRepository` moved toward **local + network data sources**
|
||||
- Strengthened repository boundaries and data-source isolation.
|
||||
- **2025-05-22 — `02bb3f02e`**: Introduce **network module**
|
||||
- Module boundaries became real rather than conceptual.
|
||||
- **2025-08-16 — `acc3e3f63`**: **Mesh service bind decoupled** from `MainActivity`
|
||||
- A high-value Android untangling step before service logic could be shared.
|
||||
- **2025-08-18 to 2025-08-19 — prefs repo migration sweep**
|
||||
- This was a major cleanup of app-level preference access into repository abstractions.
|
||||
- **2025-09-15 to 2025-10-12 — modularization burst**
|
||||
- `build-logic` modularized, nav routes moved to `:core:navigation`, new `:core:model/:core:navigation/:core:network/:core:prefs` modules added, then `:core:ui`, `:core:service`, `:feature:node`, `:feature:intro`, settings, map, and messaging code were progressively extracted.
|
||||
- **2025-11-10 — `28590bfcd`**: `:core:strings` became a **Compose Multiplatform** library
|
||||
- This is one of the clearest pre-KMP waypoints because it introduced shared resource infrastructure ahead of wider KMP conversion.
|
||||
- **2025-11-15 — `0f8e47538`**: BLE scanning/bonding moved to the **Nordic BLE library**
|
||||
- A major modernization that later made the BLE abstraction strategy viable.
|
||||
- **2025-12-17 — `61bc9bfdd`**: `core:common` migrated to **KMP**
|
||||
- **2025-12-28 — `0776e029f`**: **Timber → Kermit**
|
||||
- A direct removal of an Android/JVM-centric logging dependency.
|
||||
|
||||
```mermaid
|
||||
gantt
|
||||
title Meshtastic Android KMP timeline
|
||||
dateFormat YYYY-MM-DD
|
||||
axisFormat %b %d
|
||||
|
||||
section Early runway
|
||||
DataStore foundations begin :milestone, a1, 2022-06-11, 1d
|
||||
NodeDB repository pattern :milestone, a2, 2024-02-06, 1d
|
||||
Proto DataStore dynamic updates :milestone, a3, 2024-08-25, 1d
|
||||
Room-backed NodeDB service move :milestone, a4, 2024-09-13, 1d
|
||||
AIDL methods moved behind repositories :milestone, a5, 2024-11-21, 1d
|
||||
NavGraph decoupled from VM/entities :milestone, a6, 2024-11-30, 1d
|
||||
|
||||
section Modular architecture runway
|
||||
network module introduced :milestone, b1, 2025-05-22, 1d
|
||||
Mesh service bind decoupled :milestone, b2, 2025-08-16, 1d
|
||||
prefs repo migration sweep :active, b3, 2025-08-18, 2025-08-19
|
||||
App Intro -> Navigation 3 :milestone, b4, 2025-09-05, 1d
|
||||
build-logic modularized :milestone, b5, 2025-09-15, 1d
|
||||
nav routes -> core:navigation :milestone, b6, 2025-09-17, 1d
|
||||
new core modules land :milestone, b7, 2025-09-19, 1d
|
||||
core:ui extracted :milestone, b8, 2025-09-25, 1d
|
||||
core:service extracted :milestone, b9, 2025-09-30, 1d
|
||||
feature:node extracted :milestone, b10, 2025-10-01, 1d
|
||||
settings + messaging modularization :active, b11, 2025-10-06, 2025-10-12
|
||||
|
||||
section KMP enablers
|
||||
core:strings -> Compose MP :milestone, c1, 2025-11-10, 1d
|
||||
KMP strings cleanup :milestone, c2, 2025-11-11, 1d
|
||||
Nordic BLE migration :milestone, c3, 2025-11-15, 1d
|
||||
Navigation3 stable dep adopted :milestone, c4, 2025-11-19, 1d
|
||||
DataStore 1.2 adopted :milestone, c5, 2025-11-20, 1d
|
||||
firmware update module lands :milestone, c6, 2025-11-24, 1d
|
||||
core:common -> KMP :milestone, c7, 2025-12-17, 1d
|
||||
Timber -> Kermit :milestone, c8, 2025-12-28, 1d
|
||||
|
||||
section Explicit KMP execution wave
|
||||
core:api created :milestone, d1, 2026-01-29, 1d
|
||||
Hilt -> Koin migration wave :active, d2, 2026-02-20, 2026-02-24
|
||||
core:data / datastore / database KMP :active, d3, 2026-02-21, 2026-03-03
|
||||
repository interfaces to common :milestone, d4, 2026-03-02, 1d
|
||||
prefs + domain KMP :milestone, d5, 2026-03-05, 1d
|
||||
network + di + service KMP :milestone, d6, 2026-03-06, 1d
|
||||
messaging + intro KMP :milestone, d7, 2026-03-06, 1d
|
||||
settings/node/firmware KMP :active, d8, 2026-03-08, 2026-03-10
|
||||
core:ui KMP + Navigation 3 split :milestone, d9, 2026-03-09, 1d
|
||||
```
|
||||
|
||||
### Interpreting the timeline
|
||||
|
||||
The earlier version of this review understated how long the repo had been preparing for KMP.
|
||||
|
||||
The better reading is:
|
||||
|
||||
- **2022-2024:** early storage and repository abstraction groundwork
|
||||
- **2025:** deliberate modularization, decoupling, shared resources, Navigation 3, BLE modernization, and logging abstraction
|
||||
- **late 2025 to early 2026:** explicit KMP conversion work
|
||||
|
||||
So while the visible conversion burst did happen from **2026-02-20 through 2026-03-10**, it was built on a **much longer, roughly 18–24 month architectural runway**.
|
||||
|
||||
That suggests two things:
|
||||
|
||||
1. the migration momentum is real and recent
|
||||
2. the team had already been systematically removing Android lock-in well before the KMP label appeared in commit messages
|
||||
3. the architecture likely still has some “first-pass” decisions that need hardening before declaring the migration mature
|
||||
|
||||
---
|
||||
|
||||
## Main blockers, ranked
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A[Full cross-platform readiness] --> B[Add non-Android targets to selected KMP modules]
|
||||
A --> C[Finish Android-edge module isolation]
|
||||
A --> D[Harden DI portability rules]
|
||||
A --> E[Add non-Android CI + publication verification]
|
||||
|
||||
C --> C1[core:api split remains Android-only]
|
||||
C --> C2[core:barcode camera stack is Android-only]
|
||||
C --> C3[core:nfc uses Android NFC APIs]
|
||||
|
||||
D --> D1[Koin annotations live in commonMain]
|
||||
D --> D2[App-only DI mandate is no longer true]
|
||||
|
||||
E --> E1[No JVM/iOS/desktop smoke builds]
|
||||
E --> E2[Publish flow only covers api/model/proto]
|
||||
```
|
||||
|
||||
### Blocker 1 — No real non-Android target expansion yet
|
||||
|
||||
This is the largest blocker.
|
||||
|
||||
Until a meaningful subset of modules declares at least one additional target such as `jvm()` or `iosArm64()/iosSimulatorArm64()`, the migration remains mostly unproven outside Android.
|
||||
|
||||
**Impact:** high
|
||||
|
||||
**Why it matters:** this is where hidden dependency leaks, unsupported libraries, and source-set assumptions get discovered.
|
||||
|
||||
### Blocker 2 — Android-edge modules are still concentrated in the wrong places for reuse
|
||||
|
||||
The current Android-only modules are reasonable, but they still block a cleaner platform story:
|
||||
|
||||
- `core:api` bundles Android AIDL concerns directly
|
||||
- `core:barcode` bundles camera + scanning + flavor-specific engines in one Android module
|
||||
- `core:nfc` bundles Android NFC APIs directly
|
||||
|
||||
**Impact:** high
|
||||
|
||||
**Why it matters:** these modules define some of the user-facing input and integration surfaces.
|
||||
|
||||
### Blocker 3 — DI portability discipline drifted during the migration sprint
|
||||
|
||||
The repo originally aimed to keep DI packaging centralized in `app`, but now shared modules include Koin annotations and Koin component scans.
|
||||
|
||||
That may still be workable, but it creates two risks:
|
||||
|
||||
- cross-target packaging/tooling complexity grows inside shared modules
|
||||
- the documentation and the implementation no longer agree
|
||||
|
||||
**Impact:** medium-high
|
||||
|
||||
**Why it matters:** DI entropy spreads silently and becomes expensive later.
|
||||
|
||||
### Blocker 4 — Platform-heavy integrations still dominate the outer shell
|
||||
|
||||
These are not failures; they are the expected “last 20%” items:
|
||||
|
||||
- BLE vendor SDKs
|
||||
- DFU/update flows
|
||||
- map engines
|
||||
- camera stack
|
||||
- NFC stack
|
||||
- WorkManager, widgets, notifications, analytics, Play Services integrations
|
||||
|
||||
**Impact:** medium
|
||||
|
||||
**Why it matters:** the deeper your KMP story goes, the more these must be isolated as adapters instead of mixed into shared logic.
|
||||
|
||||
### Blocker 5 — CI does not yet enforce the future architecture
|
||||
|
||||
Current CI in [`.github/workflows/reusable-check.yml`](../.github/workflows/reusable-check.yml) runs Android build, lint, unit tests, and instrumented tests. It does **not** validate a non-Android KMP target.
|
||||
|
||||
**Impact:** medium
|
||||
|
||||
**Why it matters:** architecture not enforced by CI tends to regress.
|
||||
|
||||
---
|
||||
|
||||
## Remaining effort re-estimate
|
||||
|
||||
### Suggested effort framing
|
||||
|
||||
### Phase A — Make the current status truthful and enforceable
|
||||
|
||||
**Effort:** 2–4 days
|
||||
|
||||
- align docs with reality
|
||||
- add a KMP status dashboard/update ritual
|
||||
- define which modules are expected to remain Android-only
|
||||
- define whether shared Koin annotations are accepted or temporary
|
||||
|
||||
### Phase B — Add one real secondary target as a smoke test
|
||||
|
||||
**Effort:** 1–2 weeks
|
||||
|
||||
Best first step:
|
||||
|
||||
- add `jvm()` to a small set of low-risk shared modules first:
|
||||
- `core:common`
|
||||
- `core:model`
|
||||
- `core:repository`
|
||||
- `core:domain`
|
||||
- `core:resources`
|
||||
- possibly `core:navigation`
|
||||
|
||||
This will expose library compatibility gaps quickly without forcing iOS immediately.
|
||||
|
||||
### Phase C — Finish the platform-edge seams
|
||||
|
||||
**Effort:** 1–3 weeks
|
||||
|
||||
Priorities:
|
||||
|
||||
1. split transport-neutral API/service contracts from Android AIDL packaging
|
||||
2. turn barcode into a shared scan contract + platform camera implementations
|
||||
3. keep NFC as a platform adapter, but make the interface intentionally shared
|
||||
|
||||
### Phase D — Bring up iOS/Desktop experimentation
|
||||
|
||||
**Effort:** 2–6 weeks depending on scope
|
||||
|
||||
- iOS is the cleaner next target for BLE relevance
|
||||
- Desktop/JVM is the faster smoke target for compilation discipline
|
||||
- Web remains longest-tail because of BLE, maps, scanning, and service assumptions
|
||||
|
||||
### Revised completion estimate
|
||||
|
||||
| Lens | Completion |
|
||||
|---|---:|
|
||||
| Android-first structural KMP migration | **~88%** |
|
||||
| Shared business-logic migration | **~85–90%** |
|
||||
| Shared feature/UI migration | **~80–85%** |
|
||||
| True multi-target readiness | **~20–25%** |
|
||||
| End-to-end “add iOS/Desktop without surprises” readiness | **~30%** |
|
||||
|
||||
---
|
||||
|
||||
## Best-practice review against the 2026 KMP ecosystem
|
||||
|
||||
### Where the repo aligns well with current guidance
|
||||
|
||||
### Strong alignment
|
||||
|
||||
1. **Use KMP for business logic and state, not for every platform concern**
|
||||
- The repo is doing this well in `core:data`, `core:domain`, `core:repository`, `core:model`, and most features.
|
||||
|
||||
2. **Prefer thin platform adapters over shared platform conditionals**
|
||||
- BLE direction is good.
|
||||
- Map providers being pushed to `app` is good.
|
||||
- `CommonUri` and file-handling abstractions in firmware are good.
|
||||
|
||||
3. **Use Compose Multiplatform resources for shared UI**
|
||||
- The repo already does this in `core:resources`.
|
||||
|
||||
4. **Keep Android framework imports out of `commonMain`**
|
||||
- Current grep checks show no direct Android imports in `core/**/src/commonMain` or `feature/**/src/commonMain`.
|
||||
|
||||
5. **Adopt Room KMP and Flow-based state for shared persistence/state**
|
||||
- Current architecture is aligned here.
|
||||
|
||||
6. **Use Navigation 3 shared backstack state**
|
||||
- This is one of the repo's most forward-looking choices.
|
||||
|
||||
### Where the repo diverges from the latest best-practice direction
|
||||
|
||||
### Divergence 1 — KMP modules are still mostly Android-only in practice
|
||||
|
||||
Modern KMP guidance increasingly assumes that teams will validate at least one non-Android target early, even if product delivery is Android-first.
|
||||
|
||||
Meshtastic has done the source-set work, but not yet the target-validation work.
|
||||
|
||||
### Divergence 2 — Shared modules now depend on Koin annotations more than the docs suggest
|
||||
|
||||
For portability, the cleanest 2026 guidance is still:
|
||||
|
||||
- keep shared logic framework-light
|
||||
- keep DI declarative but minimally invasive
|
||||
- avoid making shared modules too dependent on one DI plugin if you expect broad target expansion
|
||||
|
||||
Meshtastic's current Koin setup is productive, but it is a portability tradeoff.
|
||||
|
||||
### Divergence 3 — CI has not caught up to the architecture
|
||||
|
||||
KMP best practice in 2026 is not just “shared source sets exist”; it is “shared targets are continuously compiled and tested.”
|
||||
|
||||
Meshtastic is not there yet.
|
||||
|
||||
---
|
||||
|
||||
## Dependency review: prerelease and high-risk choices
|
||||
|
||||
Current prerelease entries in [`gradle/libs.versions.toml`](../gradle/libs.versions.toml) deserve explicit policy, not passive inheritance.
|
||||
|
||||
| Dependency | Current | Assessment | Recommendation |
|
||||
|---|---|---|---|
|
||||
| Compose Multiplatform | `1.11.0-alpha03` | Aggressive | Consider downgrading to stable `1.10.2` unless `1.11` features are required now |
|
||||
| Koin | `4.2.0-RC1` | Reasonable short-term | Keep for now if Navigation 3 + compiler plugin behavior is required; switch to stable `4.2.x` once available |
|
||||
| Dokka | `2.2.0-Beta` | Unnecessary risk | Prefer stable `2.1.0` unless a verified `2.2` feature is needed |
|
||||
| Wire | `6.0.0-alpha03` | Moderate risk | Keep isolated to `core:proto`; avoid wider adoption until 6.x stabilizes |
|
||||
| Nordic BLE | `2.0.0-alpha16` | High-value but alpha | Keep behind `core:ble` abstraction only; do not let it leak outward |
|
||||
| Glance | `1.2.0-rc01` | Low KMP relevance | Fine to keep app-only if needed |
|
||||
| AndroidX Compose BOM | alpha channel | App-side risk only | Reassess if instability shows up in previews/tests |
|
||||
| Core location altitude | beta | Low impact | Acceptable if scoped and stable in practice |
|
||||
|
||||
### What the latest release signals suggest
|
||||
|
||||
- **Koin**: current repo version matches the latest GitHub release (`4.2.0-RC1`). This is defensible because it adds Navigation 3 support and compiler-plugin improvements.
|
||||
- **Compose Multiplatform**: repo is ahead of the latest stable release (`1.10.2`). Unless the app depends on an unreleased fix or API, this is probably more bleeding-edge than necessary.
|
||||
- **Dokka**: repo is on beta while latest stable is `2.1.0`. This is a good downgrade candidate.
|
||||
- **Nordic BLE**: repo is already on the latest alpha (`2.0.0-alpha16`). Acceptable only because the abstraction boundary is solid.
|
||||
|
||||
### Dependency policy recommendation
|
||||
|
||||
Use this rule:
|
||||
|
||||
- **stable by default** for infrastructure and docs tooling
|
||||
- **RC only when it directly unlocks needed KMP functionality**
|
||||
- **alpha only behind hard abstraction seams**
|
||||
|
||||
By that rule:
|
||||
|
||||
- keep **Nordic BLE alpha** short-term
|
||||
- probably keep **Koin RC** short-term
|
||||
- strongly consider stabilizing **Compose Multiplatform** and **Dokka**
|
||||
|
||||
---
|
||||
|
||||
## Replacement candidates for Android-blocking dependencies
|
||||
|
||||
### 1. BLE
|
||||
|
||||
### Current state
|
||||
|
||||
- Android implementation depends on Nordic Kotlin BLE
|
||||
- common abstraction shape is already present
|
||||
|
||||
### Recommendation
|
||||
|
||||
Keep current architecture, but evaluate **Kable** as a future non-Android implementation candidate for desktop/web-oriented expansion.
|
||||
|
||||
### Why
|
||||
|
||||
The current repo already did the hard part: it separated the interface from the implementation.
|
||||
|
||||
### 2. DFU / firmware updates
|
||||
|
||||
### Current state
|
||||
|
||||
- firmware feature is KMP, but Nordic DFU remains Android-side
|
||||
|
||||
### Recommendation
|
||||
|
||||
Do **not** force DFU into shared code prematurely.
|
||||
|
||||
Keep a shared firmware orchestration layer and separate platform update engines.
|
||||
|
||||
### Why
|
||||
|
||||
DFU is highly platform- and vendor-specific. Treat it as an adapter boundary, not a KMP purity target.
|
||||
|
||||
### 3. Maps
|
||||
|
||||
### Current state
|
||||
|
||||
- map feature is KMP
|
||||
- actual map engines live in the `app` module by flavor
|
||||
|
||||
### Recommendation
|
||||
|
||||
Current direction is correct. If Android+iOS map unification becomes a real product goal, evaluate a **MapLibre-centered** provider strategy.
|
||||
|
||||
### Why
|
||||
|
||||
Google Maps and OSMdroid are not a future-proof shared-map stack.
|
||||
|
||||
### 4. Barcode scanning
|
||||
|
||||
### Current state
|
||||
|
||||
- `core:barcode` remains Android-only
|
||||
- today it bundles camera, scanning engine, and flavor concerns together
|
||||
|
||||
### Recommendation
|
||||
|
||||
Split this into:
|
||||
|
||||
- shared scan contract + decoding domain helpers
|
||||
- Android camera implementation
|
||||
- future iOS camera implementation
|
||||
- shared or per-platform decoding engine decision
|
||||
|
||||
A pragmatic direction is to push **QR decoding primitives toward ZXing/core-compatible shared logic** while keeping camera acquisition platform-specific.
|
||||
|
||||
### 5. NFC
|
||||
|
||||
### Current state
|
||||
|
||||
- `core:nfc` is Android-only
|
||||
|
||||
### Recommendation
|
||||
|
||||
Do not hunt for a “universal KMP NFC library.” Instead:
|
||||
|
||||
- define a tiny shared capability contract
|
||||
- keep actual hardware integrations as `expect`/`actual` or interface bindings
|
||||
|
||||
### Why
|
||||
|
||||
NFC support varies too much by platform to justify a premature common implementation.
|
||||
|
||||
### 6. Android service API / AIDL
|
||||
|
||||
### Current state
|
||||
|
||||
- `core:api` is Android-only and should remain so at the transport layer
|
||||
|
||||
### Recommendation
|
||||
|
||||
Split any transport-neutral contracts from the Android AIDL packaging if reuse is desired, but keep AIDL itself Android-only.
|
||||
|
||||
### Why
|
||||
|
||||
AIDL is not a KMP concern; it is an Android integration concern.
|
||||
|
||||
---
|
||||
|
||||
## Recommended next moves
|
||||
|
||||
### Next 30 days
|
||||
|
||||
1. add this review to the KMP docs canon
|
||||
2. keep [`docs/kmp-migration.md`](./kmp-migration.md) and this review in sync
|
||||
3. add one JVM smoke target to selected low-risk modules
|
||||
4. add one non-Android CI compile job
|
||||
|
||||
### Next 60 days
|
||||
|
||||
1. split `core:api` narrative into “shared service core” vs “Android adapter API”
|
||||
2. define shared contracts for barcode and NFC boundaries
|
||||
3. decide whether Koin-in-`commonMain` is the long-term architecture or a temporary migration convenience
|
||||
|
||||
### Next 90 days
|
||||
|
||||
1. bring up a small iOS or desktop proof target
|
||||
2. stabilize dependency policy around prerelease libraries
|
||||
3. publish a living module maturity dashboard
|
||||
|
||||
---
|
||||
|
||||
## Recommended canonical wording
|
||||
|
||||
If you want one sentence that is accurate today, use this:
|
||||
|
||||
> Meshtastic-Android has largely completed its **Android-first structural KMP migration** across core logic and feature modules, but it has **not yet completed the second stage of KMP maturity**: broad non-Android target validation, platform-edge abstraction completion, and cross-target CI enforcement.
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
### Repository evidence
|
||||
|
||||
- [`docs/kmp-migration.md`](./kmp-migration.md)
|
||||
- [`docs/koin-migration-plan.md`](./koin-migration-plan.md)
|
||||
- [`docs/ble-kmp-abstraction-plan.md`](./ble-kmp-abstraction-plan.md)
|
||||
- [`gradle/libs.versions.toml`](../gradle/libs.versions.toml)
|
||||
- [`build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt`](../build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt)
|
||||
- [`build-logic/convention/src/main/kotlin/KmpLibraryComposeConventionPlugin.kt`](../build-logic/convention/src/main/kotlin/KmpLibraryComposeConventionPlugin.kt)
|
||||
- [`build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt`](../build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt)
|
||||
- [`.github/workflows/reusable-check.yml`](../.github/workflows/reusable-check.yml)
|
||||
|
||||
### Official ecosystem references reviewed for this snapshot
|
||||
|
||||
- Kotlin Multiplatform docs: <https://kotlinlang.org/docs/multiplatform.html>
|
||||
- Android KMP guidance: <https://developer.android.com/kotlin/multiplatform>
|
||||
- Compose Multiplatform + Jetpack Compose: <https://kotlinlang.org/docs/multiplatform/compose-multiplatform-and-jetpack-compose.html>
|
||||
- Koin Multiplatform docs: <https://insert-koin.io/docs/reference/koin-mp/kmp/>
|
||||
- AndroidX Room release notes: <https://developer.android.com/jetpack/androidx/releases/room>
|
||||
- Ktor client docs: <https://ktor.io/docs/client-create-and-configure.html>
|
||||
|
||||
For raw evidence tables, see [`docs/kmp-progress-review-evidence.md`](./kmp-progress-review-evidence.md).
|
||||
|
||||
247
docs/kmp-progress-review-evidence.md
Normal file
247
docs/kmp-progress-review-evidence.md
Normal file
|
|
@ -0,0 +1,247 @@
|
|||
# KMP Progress Review — Evidence Appendix
|
||||
|
||||
This appendix records the concrete repo evidence behind [`docs/kmp-progress-review-2026.md`](./kmp-progress-review-2026.md).
|
||||
|
||||
## Module inventory
|
||||
|
||||
### Core modules
|
||||
|
||||
| Module | Build plugin state | Current reality | Key evidence |
|
||||
|---|---|---|---|
|
||||
| `core:api` | Android library | **Android-only** | [`core/api/build.gradle.kts`](../core/api/build.gradle.kts) |
|
||||
| `core:barcode` | Android library + compose + flavors | **Android-only** | [`core/barcode/build.gradle.kts`](../core/barcode/build.gradle.kts) |
|
||||
| `core:ble` | KMP library | **KMP, Android target only** | [`core/ble/build.gradle.kts`](../core/ble/build.gradle.kts) |
|
||||
| `core:common` | KMP library | **KMP, Android target only** | [`core/common/build.gradle.kts`](../core/common/build.gradle.kts) |
|
||||
| `core:data` | KMP library | **KMP, Android target only** | [`core/data/build.gradle.kts`](../core/data/build.gradle.kts) |
|
||||
| `core:database` | KMP library | **KMP, Android target only** | [`core/database/build.gradle.kts`](../core/database/build.gradle.kts) |
|
||||
| `core:datastore` | KMP library | **KMP, Android target only** | [`core/datastore/build.gradle.kts`](../core/datastore/build.gradle.kts) |
|
||||
| `core:di` | KMP library | **KMP, Android target only** | [`core/di/build.gradle.kts`](../core/di/build.gradle.kts) |
|
||||
| `core:domain` | KMP library | **KMP, Android target only** | [`core/domain/build.gradle.kts`](../core/domain/build.gradle.kts) |
|
||||
| `core:model` | KMP library | **KMP, Android target only, published** | [`core/model/build.gradle.kts`](../core/model/build.gradle.kts) |
|
||||
| `core:navigation` | KMP library | **KMP, Android target only** | [`core/navigation/build.gradle.kts`](../core/navigation/build.gradle.kts) |
|
||||
| `core:network` | KMP library | **KMP, Android target only** | [`core/network/build.gradle.kts`](../core/network/build.gradle.kts) |
|
||||
| `core:nfc` | Android library + compose | **Android-only** | [`core/nfc/build.gradle.kts`](../core/nfc/build.gradle.kts) |
|
||||
| `core:prefs` | KMP library | **KMP, Android target only** | [`core/prefs/build.gradle.kts`](../core/prefs/build.gradle.kts) |
|
||||
| `core:proto` | KMP library | **KMP with explicit `jvm()`** | [`core/proto/build.gradle.kts`](../core/proto/build.gradle.kts) |
|
||||
| `core:repository` | KMP library | **KMP, Android target only** | [`core/repository/build.gradle.kts`](../core/repository/build.gradle.kts) |
|
||||
| `core:resources` | KMP library + compose | **KMP, Android target only** | [`core/resources/build.gradle.kts`](../core/resources/build.gradle.kts) |
|
||||
| `core:service` | KMP library | **KMP, Android target only** | [`core/service/build.gradle.kts`](../core/service/build.gradle.kts) |
|
||||
| `core:ui` | KMP library + compose | **KMP, Android target only** | [`core/ui/build.gradle.kts`](../core/ui/build.gradle.kts) |
|
||||
|
||||
### Feature modules
|
||||
|
||||
| Module | Build plugin state | Current reality | Key evidence |
|
||||
|---|---|---|---|
|
||||
| `feature:intro` | KMP library + compose | **KMP, Android target only** | [`feature/intro/build.gradle.kts`](../feature/intro/build.gradle.kts) |
|
||||
| `feature:messaging` | KMP library + compose | **KMP, Android target only** | [`feature/messaging/build.gradle.kts`](../feature/messaging/build.gradle.kts) |
|
||||
| `feature:map` | KMP library + compose | **KMP, Android target only** | [`feature/map/build.gradle.kts`](../feature/map/build.gradle.kts) |
|
||||
| `feature:node` | KMP library + compose | **KMP, Android target only** | [`feature/node/build.gradle.kts`](../feature/node/build.gradle.kts) |
|
||||
| `feature:settings` | KMP library + compose | **KMP, Android target only** | [`feature/settings/build.gradle.kts`](../feature/settings/build.gradle.kts) |
|
||||
| `feature:firmware` | KMP library + compose | **KMP, Android target only** | [`feature/firmware/build.gradle.kts`](../feature/firmware/build.gradle.kts) |
|
||||
|
||||
### Inventory totals
|
||||
|
||||
- Core modules: **19**
|
||||
- Feature modules: **6**
|
||||
- KMP modules across core + feature: **22 / 25**
|
||||
- Android-only modules across core + feature: **3 / 25**
|
||||
- Modules with explicit non-Android target declarations: **1 / 25** (`core:proto`)
|
||||
|
||||
---
|
||||
|
||||
## Build-logic evidence
|
||||
|
||||
### KMP convention setup
|
||||
|
||||
- [`KmpLibraryConventionPlugin.kt`](../build-logic/convention/src/main/kotlin/KmpLibraryConventionPlugin.kt) applies:
|
||||
- `org.jetbrains.kotlin.multiplatform`
|
||||
- `com.android.kotlin.multiplatform.library`
|
||||
- [`KmpLibraryComposeConventionPlugin.kt`](../build-logic/convention/src/main/kotlin/KmpLibraryComposeConventionPlugin.kt) adds Compose Multiplatform runtime/resources to `commonMain`
|
||||
- [`KotlinAndroid.kt`](../build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/KotlinAndroid.kt) configures the Android KMP target and general Kotlin compiler options
|
||||
|
||||
### Important implication
|
||||
|
||||
The repo has standardized on the **Android KMP library path** for shared modules, but does **not** yet automatically add a second target like `jvm()` or `ios*()`.
|
||||
|
||||
---
|
||||
|
||||
## Historical documentation gaps this review corrects
|
||||
|
||||
| Topic | Historical narrative gap | Current code reality | Evidence |
|
||||
|---|---|---|---|
|
||||
| `core:api` | earlier migration wording grouped `core:service` and `core:api` together as KMP | `core:service` is KMP, `core:api` is still Android-only | [`docs/kmp-migration.md`](./kmp-migration.md), [`core/api/build.gradle.kts`](../core/api/build.gradle.kts), [`core/service/build.gradle.kts`](../core/service/build.gradle.kts) |
|
||||
| DI centralization | original plan kept DI-dependent components in `app` | several `commonMain` modules contain Koin `@Module`, `@ComponentScan`, and `@KoinViewModel` | [`feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt`](../feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt), [`core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/di/CoreDomainModule.kt`](../core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/di/CoreDomainModule.kt) |
|
||||
| Cross-platform readiness impression | early migration narrative emphasized Desktop/iOS end goals more than active target verification | only `core:proto` explicitly declares a second target today | [`core/proto/build.gradle.kts`](../core/proto/build.gradle.kts), broad scan of module `build.gradle.kts` files |
|
||||
|
||||
---
|
||||
|
||||
## Git history milestones used for the timeline
|
||||
|
||||
These were extracted from local git history on 2026-03-10.
|
||||
|
||||
| Date | Commit | Theme | Milestone | Why it mattered |
|
||||
|---|---|---|---|---|
|
||||
| 2022-06-11 | `54f611290` | storage | create LocalConfig DataStore | Early shift away from raw app-only preference handling |
|
||||
| 2024-02-06 | `c8f93db00` | repositories | implement repository pattern for `NodeDB` | Began decoupling data access from service/UI consumers |
|
||||
| 2024-08-25 | `0b7718f8d` | storage | write to proto DataStore using dynamic field updates | Normalized protobuf-backed state management |
|
||||
| 2024-09-13 | `39a18e641` | database | replace service local node db with Room NodeDB | Precursor to later Room KMP adoption |
|
||||
| 2024-11-21 | `80f8f2a59` | api/service | implement repository pattern replacement for AIDL methods | Reduced direct platform/service coupling at the API edge |
|
||||
| 2024-11-30 | `716a3f535` | navigation | decouple `NavGraph` from ViewModel and NodeEntity | Important cleanup before shared navigation state |
|
||||
| 2025-04-24 | `5cd3a0229` | repositories | `DeviceHardwareRepository` to local + network data sources | Clearer data-source boundaries |
|
||||
| 2025-05-22 | `02bb3f02e` | modularization | introduce network module | Early module extraction toward sharable layers |
|
||||
| 2025-08-16 | `acc3e3f63` | service decoupling | decouple mesh service bind from `MainActivity` | Removed a high-value Android lifecycle coupling |
|
||||
| 2025-08-18 | `a46065865` | prefs/repositories | add prefs repos and DI providers | Started the broader prefs-to-repository sweep |
|
||||
| 2025-08-19 | `c913bb047` | prefs/repositories | migrate remaining prefs usages to repo | Consolidated state access behind repository abstractions |
|
||||
| 2025-09-05 | `4ab588cda` | navigation | Migrate App Intro to Navigation 3 | First major Navigation 3 adoption waypoint |
|
||||
| 2025-09-15 | `22a5521b9` | build logic | modularize `build-logic` | Strengthened convention-based architecture for later KMP rollout |
|
||||
| 2025-09-17 | `7afab1601` | modularization | move nav routes to new `:navigation` project module | Formalized navigation as sharable architecture state |
|
||||
| 2025-09-19 | `0d2c1f151` | modularization | new core modules for `:model`, `:navigation`, `:network`, `:prefs` | One of the clearest runway commits toward KMP |
|
||||
| 2025-09-25 | `c5360086b` | modularization | add `:core:ui` | Created a natural shared UI landing zone |
|
||||
| 2025-09-30 | `db2ef75e0` | modularization | add `:core:service` | Separated service logic from app shell concerns |
|
||||
| 2025-10-01 | `d553cdfee` | modularization | add `:feature:node` | Started feature-level module extraction |
|
||||
| 2025-10-06 | `95ec4877d` | modularization | modularize settings code | Continued decomposition of app screens into sharable feature modules |
|
||||
| 2025-10-12 | `886e9cfed` | modularization | modularize messaging code | Another major feature extraction step |
|
||||
| 2025-11-10 | `28590bfcd` | resources | make `:core:strings` a Compose Multiplatform library | Introduced shared Compose resource infrastructure |
|
||||
| 2025-11-11 | `57ef889ca` | resources | Kmp strings cleanup | Follow-through cleanup to make shared resources practical |
|
||||
| 2025-11-15 | `0f8e47538` | BLE | migrate to Nordic BLE Library for scanning and bonding | Modernized BLE stack before abstracting it for KMP |
|
||||
| 2025-11-19 | `295753d97` | navigation | update `navigation3-runtime` to `1.0.0` | Stabilized the shared-navigation direction |
|
||||
| 2025-11-20 | `a2285a87a` | storage | update androidx datastore to `1.2.0` | Kept a key KMP-friendly persistence layer current |
|
||||
| 2025-11-24 | `4b93065c7` | firmware | add firmware update module | Created a distinct module later migrated to KMP |
|
||||
| 2025-12-17 | `61bc9bfdd` | explicit KMP | `core/common` migrated to KMP | First strong shared-foundation KMP conversion milestone |
|
||||
| 2025-12-28 | `0776e029f` | logging | replace Timber with Kermit | Removed a non-KMP logging dependency |
|
||||
| 2026-01-29 | `15760da07` | modularization/public api | create `core:api` module and publishing | Clarified Android API surface vs shared core artifacts |
|
||||
| 2026-02-20 | `ff3f44318` | DI + explicit KMP | Hilt → Koin and `core:model` KMP pivot | Unblocked broad KMP expansion across modules |
|
||||
| 2026-02-21 | `8a3d82ca7` | explicit KMP | `core:network` + `core:prefs` to KMP | Shared transport and preference abstractions moved into KMP |
|
||||
| 2026-02-21 | `8a3c83ebf` | explicit KMP | `core:database` Room KMP structure | Shared persistence layer became materially multiplatform-ready |
|
||||
| 2026-02-21 | `cd8e32ebf` | explicit KMP | `core:data` to KMP | Concrete repositories moved into shared source sets |
|
||||
| 2026-02-21 | `3157bdd7d` | explicit KMP | `core:datastore` to KMP | Shared preferences/storage infrastructure consolidated |
|
||||
| 2026-02-21 | `727f48b45` | explicit KMP | `core:ui` to KMP | Shared UI layer became real instead of aspirational |
|
||||
| 2026-03-02 | `f3cddf5a1` | explicit KMP | repository interfaces/models to common KMP modules | Finished pushing core contracts into shared code |
|
||||
| 2026-03-03 | `6a858acb4` | explicit KMP | `core:database` to Room Kotlin Multiplatform | Reinforced the Room KMP migration |
|
||||
| 2026-03-05 | `b9b68d277` | explicit KMP | preferences to DataStore, `core:domain` decoupling | Reduced Android/JVM-specific domain assumptions |
|
||||
| 2026-03-06 | `8b13b947a` | explicit KMP | `core:service` to KMP | Shared service orchestration moved out of app-only code |
|
||||
| 2026-03-06 | `62b5f127d` | explicit KMP | `feature:messaging` to KMP | Shared feature migration accelerated |
|
||||
| 2026-03-06 | `4089ba913` | explicit KMP | `feature:intro` to KMP | Same pattern extended to another feature |
|
||||
| 2026-03-08 | `4e3bb4a83` | explicit KMP | `feature:node` and `feature:settings` to KMP | Major user-facing features moved into shared modules |
|
||||
| 2026-03-08 | `50bcefd31` | explicit KMP | `feature:firmware` to KMP | Firmware orchestration became largely shareable |
|
||||
| 2026-03-09 | `875cf1cff` | DI + explicit KMP | Hilt → Koin finalized and KMP common modules expanded | Completed the DI pivot that supports current KMP architecture |
|
||||
| 2026-03-09 | `4320c6bd4` | navigation | Navigation 3 split | Cemented shared backstack/state direction |
|
||||
| 2026-03-09 | `fb0a9a180` | explicit KMP | `core:ui` KMP follow-up | Stabilization after migration |
|
||||
| 2026-03-10 | `5ff6b1ff8` | docs | docs mark `feature:node` UI migration completed | Documentation catch-up after the migration burst |
|
||||
|
||||
---
|
||||
|
||||
## DI evidence
|
||||
|
||||
### App root assembly
|
||||
|
||||
- [`AppKoinModule.kt`](../app/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt) includes shared Koin modules from:
|
||||
- `core:*`
|
||||
- `feature:*`
|
||||
- `app`
|
||||
- [`MeshUtilApplication.kt`](../app/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt) starts Koin directly via `startKoin { ... modules(AppKoinModule().module()) }`
|
||||
|
||||
### Shared-module Koin evidence
|
||||
|
||||
| Location | Evidence |
|
||||
|---|---|
|
||||
| [`core/domain/.../CoreDomainModule.kt`](../core/domain/src/commonMain/kotlin/org/meshtastic/core/domain/di/CoreDomainModule.kt) | `@Module` + `@ComponentScan` in `commonMain` |
|
||||
| [`feature/map/.../FeatureMapModule.kt`](../feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/di/FeatureMapModule.kt) | `@Module` in `commonMain` |
|
||||
| [`feature/settings/.../FeatureSettingsModule.kt`](../feature/settings/src/commonMain/kotlin/org/meshtastic/feature/settings/di/FeatureSettingsModule.kt) | `@Module` in `commonMain` |
|
||||
| [`feature/map/.../SharedMapViewModel.kt`](../feature/map/src/commonMain/kotlin/org/meshtastic/feature/map/SharedMapViewModel.kt) | `@KoinViewModel` in `commonMain` |
|
||||
|
||||
### Conclusion
|
||||
|
||||
The codebase has functionally adopted **shared-module Koin annotations** even though the old guide still describes an `app`-centralized DI policy.
|
||||
|
||||
---
|
||||
|
||||
## CommonMain Android-import check
|
||||
|
||||
A grep scan across:
|
||||
|
||||
- `core/**/src/commonMain/**/*.kt`
|
||||
- `feature/**/src/commonMain/**/*.kt`
|
||||
|
||||
found **no direct `import android.*` lines**.
|
||||
|
||||
This is one of the strongest signals that the migration is architecturally healthy.
|
||||
|
||||
---
|
||||
|
||||
## CI evidence
|
||||
|
||||
Current reusable CI workflow:
|
||||
|
||||
- [`.github/workflows/reusable-check.yml`](../.github/workflows/reusable-check.yml)
|
||||
|
||||
What it verifies today:
|
||||
|
||||
- `spotlessCheck`
|
||||
- `detekt`
|
||||
- Android assemble
|
||||
- Android unit tests
|
||||
- Android instrumented tests
|
||||
- Kover reports
|
||||
|
||||
What it does **not** verify:
|
||||
|
||||
- JVM target compilation for shared modules
|
||||
- iOS target compilation
|
||||
- desktop target compilation
|
||||
- non-Android publication smoke tests
|
||||
|
||||
---
|
||||
|
||||
## Publication evidence
|
||||
|
||||
[`publish-core.yml`](../.github/workflows/publish-core.yml) currently publishes:
|
||||
|
||||
- `:core:api`
|
||||
- `:core:model`
|
||||
- `:core:proto`
|
||||
|
||||
Interpretation:
|
||||
|
||||
- the public integration surface is still centered on Android API + shared model/proto artifacts
|
||||
- the broader KMP core is not yet treated as a published reusable platform SDK set
|
||||
|
||||
---
|
||||
|
||||
## Prerelease dependency watchlist
|
||||
|
||||
From [`gradle/libs.versions.toml`](../gradle/libs.versions.toml):
|
||||
|
||||
| Dependency | Version in repo | Channel |
|
||||
|---|---|---|
|
||||
| Compose Multiplatform | `1.11.0-alpha03` | alpha |
|
||||
| Koin | `4.2.0-RC1` | RC |
|
||||
| Glance | `1.2.0-rc01` | RC |
|
||||
| Dokka | `2.2.0-Beta` | beta |
|
||||
| Wire | `6.0.0-alpha03` | alpha |
|
||||
| Nordic BLE | `2.0.0-alpha16` | alpha |
|
||||
| AndroidX core location altitude | `1.0.0-beta01` | beta |
|
||||
| AndroidX Compose BOM | `2026.02.01` alpha BOM channel | alpha |
|
||||
|
||||
### Latest release signals referenced in the main review
|
||||
|
||||
| Dependency | Observed signal |
|
||||
|---|---|
|
||||
| Koin | Latest GitHub release matches current `4.2.0-RC1` |
|
||||
| Compose Multiplatform | Latest GitHub stable release observed: `1.10.2` |
|
||||
| Dokka | Latest GitHub stable release observed: `2.1.0` |
|
||||
| Nordic BLE | Latest GitHub release matches current `2.0.0-alpha16` |
|
||||
|
||||
---
|
||||
|
||||
## Best-practice evidence anchors
|
||||
|
||||
The following current ecosystem references were reviewed while producing the main report:
|
||||
|
||||
- Kotlin Multiplatform overview: <https://kotlinlang.org/docs/multiplatform.html>
|
||||
- Android KMP guidance: <https://developer.android.com/kotlin/multiplatform>
|
||||
- Compose Multiplatform + Jetpack Compose guidance: <https://kotlinlang.org/docs/multiplatform/compose-multiplatform-and-jetpack-compose.html>
|
||||
- Koin KMP reference: <https://insert-koin.io/docs/reference/koin-mp/kmp/>
|
||||
- AndroidX Room release notes: <https://developer.android.com/jetpack/androidx/releases/room>
|
||||
- Ktor client guidance: <https://ktor.io/docs/client-create-and-configure.html>
|
||||
|
||||
122
docs/koin-migration-plan.md
Normal file
122
docs/koin-migration-plan.md
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
# Koin Migration Implementation Plan (Annotations & K2 Compiler Plugin)
|
||||
|
||||
This document outlines the meticulous, step-by-step strategy for migrating Meshtastic-Android from Hilt (Dagger) to **Koin with Annotations**. This approach leverages the new native **Koin Compiler Plugin (K2)** to automatically generate Koin DSL at compile time, providing a developer experience nearly identical to Hilt/Dagger but with pure, boilerplate-free KMP compatibility. We are targeting Koin 4.2.0-RC1+ and the Koin Compiler Plugin for maximum Compose Multiplatform support and optimal build performance.
|
||||
|
||||
## 1. Goal & Objectives
|
||||
- **Remove Hilt/Dagger completely** from the project.
|
||||
- **Adopt Koin Annotations** for declarative, compile-time verified DI using the native K2 Compiler Plugin.
|
||||
- **Eliminate Android*ViewModel Wrappers** by injecting KMP ViewModels (`@KoinViewModel`) directly.
|
||||
- **Improve Build Times** by replacing Dagger KAPT/KSP with the lightweight, native Koin Compiler Plugin.
|
||||
- **Maintain Incremental Progress** using the Strangler Fig Pattern.
|
||||
|
||||
## 2. Phase 1: Infrastructure Setup
|
||||
**Objective:** Add Koin Annotations and Koin Compiler Plugin to the build system.
|
||||
|
||||
1. **Add Dependencies** in `gradle/libs.versions.toml`:
|
||||
- Ensure versions are at least Koin `4.2.0-RC1` (or stable when available) and Koin Compiler Plugin.
|
||||
- Dependencies: `koin-core`, `koin-android`, `koin-annotations`, `koin-compose-viewmodel`.
|
||||
- Plugins: `io.insert-koin.compiler.plugin`.
|
||||
2. **Configure Root Compiler Plugin** in `build.gradle.kts` (root or build-logic):
|
||||
- Ensure the plugin is available and applied in KMP modules (`alias(libs.plugins.koin.compiler)`).
|
||||
3. **Setup Koin Application** in `MeshUtilApplication.kt`:
|
||||
- Initialize Koin with `startKoin { androidContext(this@MeshUtilApplication); modules(AppModule().module) }`.
|
||||
- *Note:* `.module` is an extension property automatically generated by the compiler plugin for classes annotated with `@Module`.
|
||||
- *Note:* In Koin 4.1+, standard native Context handling is unified, making explicit `androidContext` passing into KMP modules significantly simpler than in Koin 3.x.
|
||||
|
||||
## 3. Phase 2: Core Modules Migration (`core:*`)
|
||||
**Objective:** Replace Hilt modules with Koin Annotated modules.
|
||||
|
||||
1. **Annotate Classes**:
|
||||
- Replace `@Singleton` + `@Inject constructor` with just `@Single`.
|
||||
- Koin automatically binds implementations to their interfaces if it's the only interface implemented.
|
||||
- Standard constructor injection requires no explicit `@Inject` annotations—the compiler auto-detects constructors from the class-level scope annotation (`@Single`, `@Factory`, etc.).
|
||||
2. **Define Koin Modules (`expect` / `actual` Pattern)**:
|
||||
- KMP Best Practice: In `commonMain`, declare an `expect val platformModule: Module`.
|
||||
- In each platform source set (e.g., `androidMain`, `iosMain`), implement this with `actual val platformModule: Module = module { includes(AndroidModule().module) }`.
|
||||
- Use `@Module` and `@ComponentScan("org.meshtastic.core.module")` on these platform-specific classes so the plugin builds the platform dependency graphs correctly.
|
||||
3. **Bridge Hilt/Koin (Incremental Step)**:
|
||||
- If a Hilt class needs a Koin dependency, provide a temporary Hilt `@Provides` that fetches from `GlobalContext.get().get()`.
|
||||
4. **`expect` / `actual` Class Injection**:
|
||||
- When you have an `expect class` that you want to inject, do *not* annotate the `expect` declaration.
|
||||
- Instead, annotate each platform's `actual class` with `@Single` or `@Factory`. The compiler plugin will automatically compile-time link the injected interface to the correct platform implementation.
|
||||
|
||||
## 4. Phase 3: Feature & ViewModel Migration [COMPLETED]
|
||||
**Objective:** Migrate ViewModels and eliminate Android-specific wrappers using latest mapping features.
|
||||
|
||||
1. **Migrate ViewModels**:
|
||||
- Replace `@HiltViewModel` with `@KoinViewModel`.
|
||||
- Move ViewModels to `commonMain` where applicable to share logic across targets.
|
||||
2. **Update Compose Navigation**:
|
||||
- Replace `hiltViewModel()` with `koinViewModel()` in `app/navigation/`.
|
||||
- *Nitty-Gritty:* If using nested Jetpack Navigation graphs, leverage Koin 4.1's `koinNavViewModel()` to replicate Hilt's graph-scoped ViewModels securely.
|
||||
3. **Compose Previews Integration (Experimental)**:
|
||||
- Replace dummy Hilt setups in `@Preview` with Koin's `KoinApplicationPreview` to inject dummy modules specifically for rendering Compose previews.
|
||||
4. **Purge Wrappers**:
|
||||
- Delete `AndroidMetricsViewModel`, `AndroidRadioConfigViewModel`, etc.
|
||||
|
||||
## 5. Phase 4: Advanced Edge Cases (`@AssistedInject` & WorkManager)
|
||||
**Objective:** Address Dagger-specific advanced injection patterns.
|
||||
|
||||
1. **WorkManager & `@HiltWorker`**:
|
||||
- Add `io.insert-koin:koin-androidx-workmanager` to dependencies.
|
||||
- Replace `@HiltWorker` and `@AssistedInject` on Workers with `@KoinWorker`.
|
||||
- Initialize WorkManager factory in `MeshUtilApplication` via `WorkManagerFactory()`.
|
||||
2. **`@AssistedInject` (Non-Worker classes)**:
|
||||
- Meshtastic heavily uses AssistedInject for Radio Interfaces (`NordicBleInterface`, `MockInterface`, etc.).
|
||||
- Replace `@AssistedInject` with Koin's `@Factory` on the class.
|
||||
- Replace `@Assisted` parameters in the constructor with `@InjectedParam`.
|
||||
- In Koin Annotations, when injecting this factory, you pass parameters dynamically: `val radio: RadioInterface = get { parametersOf(address) }`.
|
||||
3. **Dagger Custom `@Qualifier`s**:
|
||||
- Project uses many custom qualifiers (e.g., `@UiDataStore`, `@MapDataStore`) for DataStore instances.
|
||||
- Replace these custom annotations with Koin's `@Named("UiDataStore")`.
|
||||
- Apply `@Named` to both the provided dependency (e.g., inside the `@Module` function) *and* the constructor parameter where it is injected.
|
||||
4. **Compiler Plugin Multiplatform Benefit**:
|
||||
- By using the new `io.insert-koin.compiler.plugin`, we completely bypass the old KSP boilerplate. There is no need for `kspCommonMainMetadata` or complex KSP target wiring in KMP modules.
|
||||
|
||||
## 6. Phase 5: Testing & Final Cleanup
|
||||
**Objective:** Complete Hilt eradication and verify tests.
|
||||
|
||||
1. **Update Tests**:
|
||||
- Replace `@HiltAndroidTest` with Koin testing utilities.
|
||||
- Use `KoinTest` interface and `KoinTestRule` in your Android instrumented tests and Robolectric unit tests to supply mock modules.
|
||||
2. **Remove Hilt Annotations**:
|
||||
- Delete `@HiltAndroidApp`, `@AndroidEntryPoint`, `@InstallIn`, etc.
|
||||
3. **Clean Build Scripts**:
|
||||
- Remove Hilt plugins and dependencies from all `build.gradle.kts` and `libs.versions.toml`.
|
||||
4. **Final Verification**:
|
||||
- Run `./gradlew clean assembleDebug test` to ensure successful compilation and structural integrity.
|
||||
|
||||
## 6. Migration Key mappings (Cheat Sheet)
|
||||
| Hilt/Dagger | Koin Annotations |
|
||||
| :--- | :--- |
|
||||
| `@Singleton class X @Inject constructor(...)` | `@Single class X(...)` |
|
||||
| `@Module` + `@InstallIn` | `@Module` + `@ComponentScan` |
|
||||
| `@Provides` | `@Single` or `@Factory` on a module function |
|
||||
| `@Binds` | Automatic (or `@Single` on implementation) |
|
||||
| `@HiltViewModel` | `@KoinViewModel` |
|
||||
| `hiltViewModel()` | `koinViewModel()` or `koinNavViewModel()` |
|
||||
| `Lazy<T>` | `Lazy<T>` (Native Kotlin) |
|
||||
| Dummy `@Preview` ViewModels | `KoinApplicationPreview { ... }` |
|
||||
|
||||
## 7. Troubleshooting & Lessons Learned (March 2026)
|
||||
### Koin K2 Compiler Plugin Signature Collision
|
||||
During Phase 3, we discovered a bug in the Koin K2 Compiler Plugin (v0.3.0) where multiple `@Single` provider functions in the same module with identical JVM signatures (e.g., several `DataStore` providers taking `(Context, CoroutineScope)`) were incorrectly mapped to the same internal lambda. This caused `ClassCastException` at runtime (e.g., `LocalStats` being cast to `Preferences`).
|
||||
|
||||
**Solution:** Split providers with identical signatures into separate `@Module` classes. This forces the compiler plugin to generate unique mapping classes, preventing the collision.
|
||||
|
||||
### Circular Dependencies in Koin 4.2.0
|
||||
True circular dependencies (e.g., `Service -> InterfaceFactory -> Spec -> Factory -> Service`) can cause `StackOverflowError` during graph resolution even with `Lazy<T>` injection if the `Lazy` is accessed too early (e.g., in a coroutine launched from `init`).
|
||||
|
||||
**Solution:** Break cycles by passing dependencies as function parameters instead of constructor parameters where possible (e.g., passing `service` to `InterfaceSpec.createInterface(...)`).
|
||||
|
||||
### Robolectric Tests & KoinApplicationAlreadyStartedException
|
||||
When running Robolectric tests, `MeshUtilApplication` is recreated for each test. If `startKoin` is called in `onCreate` but not stopped, subsequent tests will fail with `org.koin.core.error.KoinApplicationAlreadyStartedException`.
|
||||
|
||||
**Solution:** Explicitly call `org.koin.core.context.stopKoin()` in the application's `onTerminate` method, which is invoked by Robolectric during teardown.
|
||||
|
||||
---
|
||||
**Status:** **Fully Completed & Stable.**
|
||||
- Hilt completely removed.
|
||||
- Koin Annotations and K2 Compiler Plugin fully integrated.
|
||||
- All DataStore and Circular Dependency issues resolved.
|
||||
- App verified stable on device via Logcat audit.
|
||||
Loading…
Add table
Add a link
Reference in a new issue