Architecture
The Meshtastic Android and Desktop apps follow a modular Kotlin Multiplatform (KMP) architecture with clear layer boundaries (iOS is currently a compile-only validation target — there is no shipping iOS app yet).
Layer Overview
┌─────────────────────────────────────────────┐
│ androidApp / desktopApp │ Platform entry points
├─────────────────────────────────────────────┤
│ feature/* modules │ UI + Business Logic
├─────────────────────────────────────────────┤
│ core/* modules │ Shared infrastructure
├─────────────────────────────────────────────┤
│ Platform (Android/JVM/iOS) │ OS-specific bindings
└─────────────────────────────────────────────┘
Module Categories
androidApp/ — Android Application
The Android application entry point:
- Activity, Application, and Manifest definitions
- Koin DI module composition (
AppKoinModule) - Flavor-specific bindings (
google/,fdroid/) - Android-only integrations (widgets, services)
desktopApp/ — Desktop JVM Application
The Desktop (Linux/macOS/Windows) entry point:
- Compose Desktop window management
- Desktop-specific DI (
DesktopKoinModule) - Platform stubs for Android-only capabilities
- BLE (Kable), Serial, and TCP transport implementations
feature/* — Feature Modules
Each feature/ module owns a vertical slice of functionality:
| Module | Responsibility |
|---|---|
feature:intro | Onboarding/welcome flow |
feature:messaging | Messages, channels, contacts, quick chat |
feature:connections | Bluetooth/USB/TCP connection management |
feature:map | Map display, waypoints |
feature:node | Node list, node detail, metrics |
feature:settings | All configuration screens |
feature:firmware | Firmware update flow |
feature:docs | In-app documentation browser |
feature:wifi-provision | WiFi provisioning |
feature:widget | Android home screen widgets |
feature:discovery | Mesh network discovery |
feature:car | Android Auto / Car App Library — google flavor only, conditionally registered in the google FlavorModule |
Feature modules:
- Use the
meshtastic.kmp.featureconvention plugin - Depend on
coremodules, never on otherfeaturemodules - Own their navigation entries and DI registrations
- Contain platform-specific implementations in
androidMain/jvmMain/iosMain
core/* — Core Modules
Shared infrastructure used by all features:
| Module | Responsibility |
|---|---|
core:common | Utilities, extensions, build config |
core:navigation | Routes, deep links, Navigation 3 |
core:ui | Shared Compose components, icons, theme |
core:resources | Shared string resources |
core:model | Domain models |
core:data | Data layer abstractions |
core:domain | Use cases / business logic |
core:database | Room KMP database |
core:datastore | DataStore preferences |
core:prefs | App preferences |
core:repository | Repository interfaces |
core:service | Mesh service layer |
core:di | DI utilities |
core:network | HTTP/serial/transport |
core:ble | Bluetooth LE abstractions |
core:barcode | QR / barcode scanning (channel-share QR codes) |
core:nfc | NFC read/write support |
core:takserver | Embedded TAK server integration |
core:testing | Test utilities |
core:konsist | Konsist architecture/convention tests |
Protobuf models are no longer a local module — they come from the external org.meshtastic:protobufs Maven artifact (pinned in gradle/libs.versions.toml).
KMP Source Sets
Each module uses the standard KMP source set hierarchy:
src/
├── commonMain/ ← Shared code (all platforms)
├── commonTest/ ← Shared tests
├── androidMain/ ← Android-specific
├── jvmMain/ ← Desktop JVM-specific
├── iosMain/ ← iOS-specific
└── jvmTest/ ← Desktop test host
Golden Rules:
- No
android.*imports incommonMain - Platform-specific code goes in appropriate source set
- Prefer interfaces + DI over
expect/actualfor complex behaviors - Use
expect/actualonly for simple declarations
Dependency Injection
The project uses Koin with annotation processing:
@Module,@Single,@Factoryannotations@ComponentScanfor automatic registration- Feature modules export their own
Feature*Moduleclass - App/Desktop compose all modules in their root DI configuration
Radio Control
Features issue radio commands through RadioController (core:repository), a composite of four focused sub-interfaces so callers can depend on just the slice they need:
| Sub-interface | Responsibility |
|---|---|
AdminController | Config, channels, owner, device lifecycle, editSettings { } transactions |
MessagingController | Send packets, reactions, shared contacts |
NodeController | Favorite, ignore, mute, remove nodes |
QueryController | Telemetry, traceroute, position/user-info queries |
RadioControllerImpl (core:service) is the in-process composition root for all targets (Desktop, iOS, single-process Android). It assembles the four sub-controllers via Kotlin interface delegation and adds the cross-cutting concerns (connection state, packet-id, location, device-address switching). Commands are direct suspend calls; admin writes are fire-and-forget because the device is the source of truth (local persistence is an optimistic cache). The layered shape mirrors the meshtastic-sdk AdminApi/TelemetryApi design to ease a future SDK migration.
Service Repository
ServiceRepository is the reactive bridge between the mesh service and all feature/UI layers. It is decomposed into focused provider interfaces following the Interface Segregation Principle:
| Interface | Responsibility |
|---|---|
ConnectionStateProvider | Read-only connectionState: StateFlow<ConnectionState> |
TracerouteResponseProvider | Traceroute response state + clear |
NeighborInfoResponseProvider | Neighbor info response state + clear |
ServiceStateWriter | Write-side for handlers (set, emit, clear*) |
ServiceRepository extends all four interfaces — consumers inject the narrowest interface they actually need. For example, ContactsViewModel injects only ConnectionStateProvider rather than the entire ServiceRepository, preventing accidental access to write operations from UI code. RadioController also extends ConnectionStateProvider so VMs that already inject a controller sub-interface can read connection state without a separate dependency.
Navigation
Navigation uses Navigation 3 with typed routes:
- All routes defined in
core/navigation/Routes.kt - Routes are
@Serializabledata classes/objects - Deep links resolved through
DeepLinkRouter - Each feature registers its own navigation entries
See Navigation & Deep Links for details.