Skip to main content
Version: 2.8

Architecture

The Meshtastic Android and Desktop apps follow a modular Kotlin Multiplatform (KMP) architecture with clear layer boundaries. iOS is a compile-only validation target; no iOS app ships.

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
  • DesktopRadioTransportFactory and a jSerialComm-based serial transport; the BLEBLE (Bluetooth Low Energy). The default wireless connection between a node and a phone client. and TCPTCP (Transmission Control Protocol). One of the three ways a client reaches a node, alongside serial and Bluetooth. Clients connect over TCP on port 4403. transport implementations it wires up are shared code — they live in core:network, built on core:ble's BLEBLE (Bluetooth Low Energy). The default wireless connection between a node and a phone client. primitives — not desktopApp-owned

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/USBUSB (Universal Serial Bus). The wired connection used to power a device, flash firmware onto it, and communicate with it over serial./TCPTCP (Transmission Control Protocol). One of the three ways a client reaches a node, alongside serial and Bluetooth. Clients connect over TCP on port 4403. connection management
feature:mapMap display, waypoints — shared state, policy and the waypoint editor
feature:map-maplibreMapLibre map surfaces — used by the fdroid flavor and Desktop; the google flavor uses Google Maps instead. Tile-source definitions and the custom-source editor are in feature:map, so both renderers share them
feature:map-terrainOffline terrain: elevation decode, hillshade shading and contour lines, shared by both map flavors
feature:nodeNode list, node detail, metrics
feature:settingsAll configuration screens
feature:firmwareFirmware update flow
feature:docsIn-app documentation browser
feature:wifi-provisionWi-Fi provisioning
feature:widgetAndroid home screen widgets
feature:discoveryMesh network discovery

Feature modules:

  • Use the meshtastic.kmp.feature convention plugin
  • Depend on core modules, not on other feature modules; the one exception is feature:map-maplibre, which builds on feature:map and feature:map-terrain
  • 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:domainUse cases / business logic
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:barcodeQRQRA scannable square code. In Meshtastic it encodes the Channel URL, which carries the channel and LoRa settings needed to join a mesh. / barcode scanning (channel-share QRQRA scannable square code. In Meshtastic it encodes the Channel URL, which carries the channel and LoRa settings needed to join a mesh. codes)
core:nfcNFCNFC (Near Field Communication). A very short-range wireless protocol. Some clients expose NFC-related settings. read/write support
core:takserverEmbedded TAKTAK (Team Awareness Kit). A situational awareness ecosystem. Meshtastic bridges to it by converting mesh messages to and from Cursor on Target format. server integration
core:testingTest utilities
core:konsistKonsist architecture/convention tests

ProtobufProtobuf (Protocol Buffers). A method developed by Google for serializing structured data, used in Meshtastic for efficient communication protocol between devices. models 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
├── jvmAndroidMain/ ← Shared by Android and desktop JVM
├── iosMain/ ← iOS-specific
├── nativeMain/ ← Kotlin/Native code, built for the one iOS target (iosSimulatorArm64)
├── jvmTest/ ← Desktop test host
├── androidHostTest/ ← Android host (JVM) unit tests
└── androidDeviceTest/ ← Instrumented tests (core:database, core:model)

jvmAndroidMain exists only in modules that apply meshtastic.kmp.jvm.android. nativeMain, androidHostTest (modules that call withHostTest) and androidDeviceTest exist only in the modules that need them.

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, tracerouteTracerouteA request that reports the path a message took through the mesh. The result lists the nodes along the way in both directions, with the signal-to-noise ratio at each hop., 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>
TracerouteResponseProviderTracerouteTracerouteA request that reports the path a message took through the mesh. The result lists the nodes along the way in both directions, with the signal-to-noise ratio at each hop. 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.