Files
Meshtastic-Android/GEMINI.md

12 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 declare jvm(), iosArm64(), and iosSimulatorArm64() targets and compile clean across all.
    • Android-only Modules: core:api (AIDL), core:barcode (CameraX + flavor-specific decoder). Shared contracts abstracted into core:ui/commonMain.
    • UI: Jetpack Compose Multiplatform (Material 3).
    • DI: Koin Annotations with K2 compiler plugin. Root graph assembly is centralized in app and desktop.
    • Navigation: JetBrains Navigation 3 (Multiplatform fork) with shared backstack state.
    • Lifecycle: JetBrains multiplatform lifecycle-viewmodel-compose and lifecycle-runtime-compose.
    • Database: Room KMP.

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).
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 asynchronous getStringSuspend(Res.string.your_key). NEVER use hardcoded strings, and NEVER use the blocking getString() in a coroutine.
  • Dialogs: Use centralized components in core:ui (e.g., MeshtasticResourceDialog).
  • Alerts: Use AlertHost(alertManager) from core:ui/commonMain in each platform host shell (Main.kt, DesktopMainScreen.kt). Do NOT duplicate inline alert-rendering boilerplate. For shared QR/contact dialogs, use the SharedDialogs composable.
  • Placeholders: For desktop/JVM features not yet implemented, use PlaceholderScreen(name) from core:ui/commonMain. Do NOT define inline placeholder composables in feature modules.
  • Theme Picker: Use ThemePickerDialog and ThemeOption from feature:settings/commonMain. Do NOT duplicate the theme dialog or enum in platform-specific source sets.
  • Platform/Flavor UI: Inject platform-specific behavior (e.g., map providers) via CompositionLocal from app.

B. Logic & Data Layer

  • KMP Focus: All business logic must reside in commonMain of the respective core module.
  • Platform purity: Never import java.* or android.* in commonMain. Use KMP alternatives:
    • java.util.Locale → Kotlin uppercase() / lowercase() or expect/actual.
    • java.util.concurrent.ConcurrentHashMapatomicfu or Mutex-guarded mutableMapOf().
    • java.util.concurrent.locks.*kotlinx.coroutines.sync.Mutex.
    • java.io.* → Okio (BufferedSource/BufferedSink).
    • kotlinx.coroutines.Dispatchers.IOorg.meshtastic.core.common.util.ioDispatcher (expect/actual).
  • Shared helpers over duplicated lambdas: When androidMain and jvmMain contain identical pure-Kotlin logic (formatting, action dispatch, validation), extract it to a function in commonMain. Examples: formatLogsTo() in feature:settings, handleNodeAction() in feature:node, findNodeByNameSuffix() in feature:connections.
  • KMP file naming: In KMP modules, commonMain and 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 the expect declaration in LogExporter.kt and put shared helpers in a separate file like LogFormatter.kt.
  • Concurrency: Use Kotlin Coroutines and Flow.
  • Dependency Injection: Use Koin Annotations with the K2 compiler plugin (koin-plugin in version catalog). The koin-annotations library version is unified with koin-core (both use version.ref = "koin"). The KoinConventionPlugin uses the typed KoinGradleExtension to configure the K2 plugin (e.g., compileSafety.set(false)). Keep root graph assembly in app.
  • ViewModels: Follow the MVI/UDF pattern. Use the multiplatform androidx.lifecycle.ViewModel in commonMain.
  • BLE: All Bluetooth communication must route through core:ble using Kable.
  • Dependencies: Check gradle/libs.versions.toml before 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). Plain androidx-* aliases are true Google AndroidX artifacts. Never mix them up in commonMain.
  • Compose Multiplatform: Version catalog aliases for Compose Multiplatform artifacts use the compose-multiplatform-* prefix (e.g., compose-multiplatform-material3, compose-multiplatform-foundation). Never use plain androidx.compose dependencies in common Main.
  • Room KMP: Always use factory = { MeshtasticDatabaseConstructor.initialize() } in Room.databaseBuilder and inMemoryDatabaseBuilder. DAOs and Entities reside in commonMain.
  • QR Codes: Use rememberQrCodePainter from core:ui/commonMain (powered by qrcode-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. Use Turbine for Flow testing, Kotest for property-based testing, and Mokkery for mocking. Use core:testing shared fakes.
  • Build-logic conventions: In build-logic/convention, prefer lazy Gradle configuration (configureEach, withPlugin, provider APIs). Avoid afterEvaluate in 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.mesh Application ID.

4. Execution Protocol

A. Environment Setup

  1. JDK 17 MUST be used to prevent Gradle sync/build failures.
  2. Secrets: You must copy secrets.defaults.properties to local.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 app and mesh_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 like lintDebug.
  • Pull request CI is main-only (.github/workflows/pull-request.yml targets main branch).
  • Gradle cache writes are trusted on main and merge queue runs (merge_group / gh-readonly-queue/*); other refs use read-only cache mode in reusable CI.
  • PR check-changes path filtering lives in .github/workflows/pull-request.yml and 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.

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.toml for dependency conflicts.
  • Missing Secrets: Check local.properties.
  • JDK Version: JDK 17 is required.
  • Configuration Cache: Add --no-configuration-cache flag if cache-related issues persist.
  • Koin Injection Failures: Verify the KMP component is included in app root module wiring (AppKoinModule).