8.9 KiB
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.
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.
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
Timberwith KMP-readyKermit(#4083). - ✅
core:common: Decoupled basic utilities and cleanly extracted away from Android constraints (#4026).
-
Namespace Modernization:
- The
appmodule source code was completely relocated fromcom.geeksville.meshtoorg.meshtastic.app. - Legacy Compatibility: External integrations (like ATAK) rely on legacy Android Intents.
AndroidManifest.xmlpreserves the<action android:name="com.geeksville.mesh.*" />signatures to ensure unbroken backwards compatibility.
- The
-
Module Conversions (
meshtastic.android.library->meshtastic.kmp.library):- ✅
core:repository: Interfaces extracted tocommonMain. - ✅
core:domain: Use cases migrated. AndroidHandlerandjava.io.Filelogic 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 tocommonMain. Android-specific logic (e.g., parsingdevice_hardware.jsonfromassets) was abstracted behind KMP interfaces with implementations provided inandroidMain.
- ✅
-
Architecture Refinements:
core:analyticswas completely dissolved. Abstract tracking interfaces were moved tocore:repository, and concrete SDK implementations (Firebase, DataDog) were moved to theappmodule.- 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.) incommonMainso that Desktop and Web targets can compile, while using Nordic'sKMM-BLE-Libraryspecifically inside theandroidMainsource set.- ✅
core:service: Converted to a KMP module, isolating Android service bindings and lifecycle concerns toandroidMain. - ℹ️
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.
- Android-edge modules still remain platform-specific:
core:barcode/core:nfc: Android-specific hardware integrations. Partially addressed:core:uino longer depends on them directly and abstracts scanning viaCompositionLocalProvider.core:api: Intentionally Android-specific because AIDL is Android-only. Any transport-neutral contracts should continue to be separated from the Android adapter layer.
- Feature modules are structurally migrated, but cleanup continues:
- Current State: all
feature/*modules now build as KMP libraries, andandroidx.lifecycle.ViewModelis 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.
- Current State: all
- 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.
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 legacyNavControllerornavigation-compose. - ✅
core:ui: Converted to a pure KMP library (meshtastic.kmp.library.compose).- Abstracted Clipboard, Intents, and Bitmaps via
PlatformUtilsandexpect/actual. - Replaced Android's
Linkifywith a pure Kotlin Regex andAnnotatedStringsolution. - Ensured all shared UI components rely solely on Compose Multiplatform.
- Abstracted Clipboard, Intents, and Bitmaps via
- The remaining work here is mostly validation on additional targets and continued isolation of Android-only framework hooks.
- ✅ Navigation: Migrated fully to AndroidX Navigation 3. The backstack is now a simple state list (
Dependency Injection
The project currently uses Koin Annotations.
- Current State:
core:diis a KMP module that exposesjavax.injectannotations (@Inject), and the app root still assembles the graph inAppKoinModule. - Important Update: The original plan was to keep all DI-dependent components centralized in the
appmodule, but the current implementation now includes some Koin@Module,@ComponentScan, and@KoinViewModelusage directly incommonMainshared modules. Seedocs/kmp-progress-review-2026.mdfor 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 passContext,Application, orActivityintocommonMain. Use Dependency Injection to provide platform-specific implementations fromandroidMainorapp. - ViewModels: Use
androidx.lifecycle.ViewModelandviewModelScopewithincommonMainfor 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.testandMockKfor unit tests incommonTest. AvoidRobolectricunless explicitly testing anandroidMaincomponent. Platform-specific unit tests (e.g. for Workers) should be relocated to theappmodule'stestsource set if they depend on Koin components. - Resources: Use Compose Multiplatform Resources (
core:resources) for all strings and drawables. Never use Androidstrings.xmlincommonMain. - Coroutines & Flows: Use
StateFlowandSharedFlowfor all asynchronous state management across the domain layer. - Persistence: Use
androidx.datastorefor preferences and Room KMP for complex relational data. - Dependency Injection: We use Koin Annotations + K2 Compiler Plugin. Per 2026 KMP industry standards, it is recommended to push Koin
@Module,@ComponentScan, and@KoinViewModelannotations intocommonMain. This encapsulates dependency graphs per feature, providing a Hilt-like experience (compile-time validation) while remaining fully multiplatform-compatible.
Document refreshed on 2026-03-10 as a historical companion to docs/kmp-progress-review-2026.md.