15 KiB
Meshtastic Android - Agent Guide
This file serves as a comprehensive guide for AI agents and developers working on the Meshtastic-Android codebase. Use this as your primary reference for understanding the architecture, conventions, and strict rules of this project.
For execution-focused recipes, see docs/agent-playbooks/README.md.
1. Project Vision & Architecture
Meshtastic-Android is a Kotlin Multiplatform (KMP) application for off-grid, decentralized mesh networks. The goal is to decouple business logic from the Android framework, enabling future expansion to iOS and other platforms while maintaining a high-performance native Android experience.
- Language: Kotlin (primary), AIDL.
- Build System: Gradle (Kotlin DSL). JDK 17 is REQUIRED.
- Target SDK: API 36. Min SDK: API 26 (Android 8.0).
- Flavors:
fdroid: Open source only, no tracking/analytics.google: Includes Google Play Services (Maps) and DataDog analytics.
- Core Architecture: Modern Android Development (MAD) with KMP core.
- KMP Modules: Most
core:*modules. All declarejvm(),iosArm64(), andiosSimulatorArm64()targets and compile clean across all. - Android-only Modules:
core:api(AIDL),core:barcode(CameraX + flavor-specific decoder). Shared contracts abstracted intocore:ui/commonMain. - UI: Jetpack Compose Multiplatform (Material 3).
- DI: Koin Annotations with K2 compiler plugin. Root graph assembly is centralized in
appanddesktop. - Navigation: JetBrains Navigation 3 (Stable Scene-based architecture) with shared backstack state. Deep linking uses RESTful paths (e.g.
/nodes/1234) parsed byDeepLinkRouterincore:navigation. - Lifecycle: JetBrains multiplatform
lifecycle-viewmodel-composeandlifecycle-runtime-compose. - Adaptive UI: Material 3 Adaptive (v1.3+) with support for Large (1200dp) and Extra-large (1600dp) breakpoints.
- Database: Room KMP.
- KMP Modules: Most
2. Codebase Map
| Directory | Description |
|---|---|
app/ |
Main application module. Contains MainActivity, Koin DI modules, and app-level logic. Uses package org.meshtastic.app. |
build-logic/ |
Convention plugins for shared build configuration (e.g., meshtastic.kmp.feature, meshtastic.kmp.library, meshtastic.koin). |
config/ |
Detekt static analysis rules (config/detekt/detekt.yml) and Spotless formatting config (config/spotless/.editorconfig). |
docs/ |
Architecture docs and agent playbooks. See docs/agent-playbooks/README.md for version baseline and task recipes. |
core/model |
Domain models and common data structures. |
core:proto |
Protobuf definitions (Git submodule). |
core:common |
Low-level utilities, I/O abstractions (Okio), and common types. |
core:database |
Room KMP database implementation. |
core:datastore |
Multiplatform DataStore for preferences. |
core:repository |
High-level domain interfaces (e.g., NodeRepository, LocationRepository). |
core:domain |
Pure KMP business logic and UseCases. |
core:data |
Core manager implementations and data orchestration. |
core:network |
KMP networking layer using Ktor, MQTT abstractions, and shared transport (StreamFrameCodec, TcpTransport, SerialTransport, BleRadioInterface). |
core:di |
Common DI qualifiers and dispatchers. |
core:navigation |
Shared navigation keys/routes for Navigation 3. |
core:ui |
Shared Compose UI components (AlertHost, SharedDialogs, PlaceholderScreen, MainAppBar, dialogs, preferences) and platform abstractions. |
core:service |
KMP service layer; Android bindings stay in androidMain. |
core:api |
Public AIDL/API integration module for external clients. |
core:prefs |
KMP preferences layer built on DataStore abstractions. |
core:barcode |
Barcode scanning (Android-only). |
core:nfc |
NFC abstractions (KMP). Android NFC hardware implementation in androidMain. |
core/ble/ |
Bluetooth Low Energy stack using Kable. |
core/resources/ |
Centralized string and image resources (Compose Multiplatform). |
core/testing/ |
Shared test doubles, fakes, and utilities for commonTest across all KMP modules. |
feature/ |
Feature modules (e.g., settings, map, messaging, node, intro, connections, firmware, widget). All are KMP with jvm() and ios() targets except widget. Use meshtastic.kmp.feature convention plugin. |
desktop/ |
Compose Desktop application — first non-Android KMP target. Thin host shell relying entirely on feature modules for shared UI. Full Koin DI graph, TCP, Serial/USB, and BLE transports with want_config handshake. |
mesh_service_example/ |
Sample app showing core:api service integration. |
3. Development Guidelines & Coding Standards
A. UI Development (Jetpack Compose)
- Material 3: The app uses Material 3.
- Strings: MUST use the Compose Multiplatform Resource library in
core:resources(stringResource(Res.string.your_key)). For ViewModels or non-composable Coroutines, use the asynchronousgetStringSuspend(Res.string.your_key). NEVER use hardcoded strings, and NEVER use the blockinggetString()in a coroutine. - Dialogs: Use centralized components in
core:ui(e.g.,MeshtasticResourceDialog). - Alerts: Use
AlertHost(alertManager)fromcore:ui/commonMainin each platform host shell (Main.kt,DesktopMainScreen.kt). For global responses like traceroute and firmware validation, use the specialized common handlers:TracerouteAlertHandler(uiViewModel)andFirmwareVersionCheck(uiViewModel). Do NOT duplicate inline alert-rendering logic or trigger alerts directly during composition. For shared QR/contact dialogs, use theSharedDialogs(uiViewModel)composable. - Placeholders: For desktop/JVM features not yet implemented, use
PlaceholderScreen(name)fromcore:ui/commonMain. Do NOT define inline placeholder composables in feature modules. - Theme Picker: Use
ThemePickerDialogandThemeOptionfromfeature:settings/commonMain. Do NOT duplicate the theme dialog or enum in platform-specific source sets. - Adaptive Layouts: Use
currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true)to support the 2026 Desktop Experience breakpoints. Prioritize higher information density and mouse-precision interactions for Desktop and External Display (Android 16 QPR3) targets. Investigate 3-pane "Power User" scenes (e.g., Node List + Detail + Map/Charts) using Navigation 3 Scenes andThreePaneScaffoldfor widths ≥ 1200dp. - Platform/Flavor UI: Inject platform-specific behavior (e.g., map providers) via
CompositionLocalfromapp.
B. Logic & Data Layer
- KMP Focus: All business logic must reside in
commonMainof the respectivecoremodule. - Platform purity: Never import
java.*orandroid.*incommonMain. Use KMP alternatives:java.util.Locale→ Kotlinuppercase()/lowercase()orexpect/actual.java.util.concurrent.ConcurrentHashMap→atomicfuorMutex-guardedmutableMapOf().java.util.concurrent.locks.*→kotlinx.coroutines.sync.Mutex.java.io.*→ Okio (BufferedSource/BufferedSink).kotlinx.coroutines.Dispatchers.IO→org.meshtastic.core.common.util.ioDispatcher(expect/actual).
- Shared helpers over duplicated lambdas: When
androidMainandjvmMaincontain identical pure-Kotlin logic (formatting, action dispatch, validation), extract it to a function incommonMain. Examples:formatLogsTo()infeature:settings,handleNodeAction()infeature:node,findNodeByNameSuffix()infeature:connections,MeshtasticAppShellincore:ui/commonMain, andBaseRadioTransportFactoryincore:network/commonMain. - KMP file naming: In KMP modules,
commonMainand platform source sets (androidMain,jvmMain) share the same package namespace. If both contain a file with the same name (e.g.,LogExporter.kt), the Kotlin/JVM compiler will produce a duplicate class error. Use distinct filenames: keep theexpectdeclaration inLogExporter.ktand put shared helpers in a separate file likeLogFormatter.kt. - Concurrency: Use Kotlin Coroutines and Flow.
- Dependency Injection: Use Koin Annotations with the K2 compiler plugin (
koin-pluginin version catalog). Thekoin-annotationslibrary version is unified withkoin-core(both useversion.ref = "koin"). TheKoinConventionPluginuses the typedKoinGradleExtensionto configure the K2 plugin (e.g.,compileSafety.set(false)). Keep root graph assembly inapp. - ViewModels: Follow the MVI/UDF pattern. Use the multiplatform
androidx.lifecycle.ViewModelincommonMain. - BLE: All Bluetooth communication must route through
core:bleusing Kable. - Dependencies: Check
gradle/libs.versions.tomlbefore assuming a library is available. - JetBrains fork aliases: Version catalog aliases for JetBrains-forked AndroidX artifacts use the
jetbrains-*prefix (e.g.,jetbrains-lifecycle-runtime-compose,jetbrains-navigation3-ui). Plainandroidx-*aliases are true Google AndroidX artifacts. Never mix them up incommonMain. - Compose Multiplatform: Version catalog aliases for Compose Multiplatform artifacts use the
compose-multiplatform-*prefix (e.g.,compose-multiplatform-material3,compose-multiplatform-foundation). Never use plainandroidx.composedependencies in common Main. - Room KMP: Always use
factory = { MeshtasticDatabaseConstructor.initialize() }inRoom.databaseBuilderandinMemoryDatabaseBuilder. DAOs and Entities reside incommonMain. - QR Codes: Use
rememberQrCodePainterfromcore:ui/commonMain(powered byqrcode-kotlin) for generating QR codes. Do not use Android Bitmap or ZXing APIs in common code. - Testing: Write ViewModel and business logic tests in
commonTest. UseTurbinefor Flow testing,Kotestfor property-based testing, andMokkeryfor mocking. Usecore:testingshared fakes. - Build-logic conventions: In
build-logic/convention, prefer lazy Gradle configuration (configureEach,withPlugin, provider APIs). AvoidafterEvaluatein convention plugins unless there is no viable lazy alternative.
C. Namespacing
- Standard: Use the
org.meshtastic.*namespace for all code. - Legacy: Maintain the
com.geeksville.meshApplication ID.
4. Execution Protocol
A. Environment Setup
- JDK 17 MUST be used to prevent Gradle sync/build failures.
- Secrets: You must copy
secrets.defaults.propertiestolocal.properties:MAPS_API_KEY=dummy_key datadogApplicationId=dummy_id datadogClientToken=dummy_token
B. Strict Execution Commands
Always run commands in the following order to ensure reliability. Do not attempt to bypass clean if you are facing build issues.
Baseline (recommended order):
./gradlew clean
./gradlew spotlessCheck
./gradlew spotlessApply
./gradlew detekt
./gradlew assembleDebug
./gradlew test
Testing:
./gradlew test # Run local unit tests
./gradlew testFdroidDebugUnitTest testGoogleDebugUnitTest # CI-aligned Android unit tests (flavor-explicit)
./gradlew connectedAndroidTest # Run instrumented tests
./gradlew testFdroidDebug testGoogleDebug # Flavor-specific unit tests
./gradlew lintFdroidDebug lintGoogleDebug # Flavor-specific lint checks
Note: If testing Compose UI on the JVM (Robolectric) with Java 17, pin your tests to @Config(sdk = [34]) to avoid SDK 35 compatibility crashes.
CI workflow conventions (GitHub Actions):
- Reusable CI is split into a host job and an Android matrix job in
.github/workflows/reusable-check.yml. - Host job runs style/static checks, explicit Android lint tasks, unit tests, and Kover XML coverage uploads once.
- Android matrix job runs explicit assemble tasks for
appandmesh_service_example; instrumentation is enabled by input and matrix API. - Prefer explicit Gradle task paths in CI (for example
app:lintFdroidDebug,app:connectedGoogleDebugAndroidTest) instead of shorthand tasks likelintDebug. - Pull request CI is main-only (
.github/workflows/pull-request.ymltargetsmainbranch). - Gradle cache writes are trusted on
mainand merge queue runs (merge_group/gh-readonly-queue/*); other refs use read-only cache mode in reusable CI. - PR
check-changespath filtering lives in.github/workflows/pull-request.ymland must include module dirs plus build/workflow entrypoints (build-logic/**,gradle/**,.github/workflows/**,gradlew,settings.gradle.kts, etc.) so CI is not skipped for infra-only changes. - Runner strategy (three tiers):
ubuntu-24.04-arm— Lightweight/utility jobs (status checks, labelers, triage, changelog, release metadata, stale, moderation). These run only shell scripts or GitHub API calls and benefit from ARM runners' shorter queue times.ubuntu-24.04— Gradle-heavy jobs (CI host-check, android-check, release builds, Dokka, CodeQL, publish, dependency-submission). Pinned for reproducibility; avoidubuntu-latestto prevent breakage when GitHub rolls the alias forward.- Desktop release matrix —
[macos-latest, windows-latest, ubuntu-24.04, ubuntu-24.04-arm]for cross-platform native packaging (DMG, MSI, deb/rpm/AppImage for x64 and ARM).
- CI JVM tuning:
gradle.propertiesis tuned for local dev (8g heap, 4g Kotlin daemon). CI workflows override viaGRADLE_OPTSenv var to fit the 7GB RAM budget of standard runners:-Xmx4gGradle heap,-Xmx2gKotlin daemon, VFS watching disabled, workers capped at 4. - KMP Smoke Compile: Use
./gradlew kmpSmokeCompileinstead of listing individual module compile tasks. ThekmpSmokeCompilelifecycle task (registered inRootConventionPlugin) auto-discovers all KMP modules and depends on theircompileKotlinJvm+compileKotlinIosSimulatorArm64tasks. mavenLocal()gated: ThemavenLocal()repository is disabled by default to prevent CI cache poisoning. For local JitPack testing, pass-PuseMavenLocalto Gradle.- Terminal Pagers: When running shell commands like
git difforgit log, ALWAYS use--no-pager(e.g.,git --no-pager diff) to prevent the agent from getting stuck in an interactive prompt. - Text Search: Prefer using
rg(ripgrep) overgreporfindfor fast text searching across the codebase.
C. Documentation Sync
Update documentation continuously as part of the same change. If you modify architecture, module targets, CI tasks, validation commands, or agent workflow rules, update the relevant docs (AGENTS.md, .github/copilot-instructions.md, GEMINI.md, docs/agent-playbooks/*, docs/kmp-status.md, and docs/decisions/architecture-review-2026-03.md).
5. Troubleshooting
- Build Failures: Check
gradle/libs.versions.tomlfor dependency conflicts. - Missing Secrets: Check
local.properties. - JDK Version: JDK 17 is required.
- Configuration Cache: Add
--no-configuration-cacheflag if cache-related issues persist. - Koin Injection Failures: Verify the KMP component is included in
approot module wiring (AppKoinModule).