Audit-driven renames: - DirectRadioControllerImpl -> RadioControllerImpl: the "Direct" prefix was vestigial (it once contrasted with the AIDL-routed AndroidRadioControllerImpl, now deleted); it is the sole impl and now matches its parts (AdminControllerImpl, etc.). - RadioController.getPacketId() -> generatePacketId(): a `get`-prefixed function that generates a fresh id each call violates the getter-is-idempotent convention; also aligns with CommandSender.generatePacketId(). - RequestController -> QueryController (+ Impl, + the `requestController` params/mocks): clearer intent for the pull/query surface; "request" was generic. - RequestTimer param `label` -> `logLabel`. - AdminControllerImpl DEFAULT_REBOOT_DELAY -> DEFAULT_DELAY_SECONDS (shared by reboot + shutdown; conveys the unit). Interface-consumer-safe; docs/READMEs/architecture guide updated to match. Koin graph verifies on Android + desktop; affected test suites green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
7.0 KiB
title, parent, nav_order, last_updated, aliases
| title | parent | nav_order | last_updated | aliases | ||||
|---|---|---|---|---|---|---|---|---|
| Architecture | Developer Guide | 1 | 2026-05-29 |
|
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:
| 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 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: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:proto |
Protobuf definitions |
core:testing |
Test utilities |
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.