Skip to main content

Architecture

The Meshtastic Android/Desktop/iOS application follows a modular Kotlin Multiplatform (KMP) architecture with clear layer boundaries.

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:

ModuleResponsibility
feature:introOnboarding/welcome flow
feature:messagingMessages, channels, contacts, quick chat
feature:connectionsBluetooth/USB/TCP connection management
feature:mapMap display, waypoints
feature:nodeNode list, node detail, metrics
feature:settingsAll configuration screens
feature:firmwareFirmware update flow
feature:docsIn-app documentation browser
feature:wifi-provisionWiFi provisioning
feature:widgetAndroid home screen widgets
feature:discoveryMesh network discovery
feature:carAndroid Auto / Car App Library — google flavor only, conditionally registered in the google FlavorModule

Feature modules:

  • Use the meshtastic.kmp.feature convention plugin
  • Depend on core modules, never on other feature modules
  • Own their navigation entries and DI registrations
  • Contain platform-specific implementations in androidMain/jvmMain/iosMain

core/* — Core Modules

Shared infrastructure used by all features:

ModuleResponsibility
core:commonUtilities, extensions, build config
core:navigationRoutes, deep links, Navigation 3
core:uiShared Compose components, icons, theme
core:resourcesShared string resources
core:modelDomain models
core:dataData layer abstractions
core:databaseRoom KMP database
core:datastoreDataStore preferences
core:prefsApp preferences
core:repositoryRepository interfaces
core:serviceMesh service layer
core:diDI utilities
core:networkHTTP/serial/transport
core:bleBluetooth LE abstractions
core:testingTest utilities

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 in commonMain
  • Platform-specific code goes in appropriate source set
  • Prefer interfaces + DI over expect/actual for complex behaviors
  • Use expect/actual only for simple declarations

Dependency Injection

The project uses Koin with annotation processing:

  • @Module, @Single, @Factory annotations
  • @ComponentScan for automatic registration
  • Feature modules export their own Feature*Module class
  • 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-interfaceResponsibility
AdminControllerConfig, channels, owner, device lifecycle, editSettings { } transactions
MessagingControllerSend packets, reactions, shared contacts
NodeControllerFavorite, ignore, mute, remove nodes
QueryControllerTelemetry, 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:

InterfaceResponsibility
ConnectionStateProviderRead-only connectionState: StateFlow<ConnectionState>
TracerouteResponseProviderTraceroute response state + clear
NeighborInfoResponseProviderNeighbor info response state + clear
ServiceStateWriterWrite-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 uses Navigation 3 with typed routes:

  • All routes defined in core/navigation/Routes.kt
  • Routes are @Serializable data classes/objects
  • Deep links resolved through DeepLinkRouter
  • Each feature registers its own navigation entries

See Navigation & Deep Links for details.