Skill: DI and Navigation 3 Architecture

SkillDev tools

Lets your agent look up the Android app for controlling Meshtastic mesh radio devices.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Skill: DI and Navigation 3 Architecture skill

About this capability

Android application for Meshtastic

What this skill tells your AI

The instructions your AI receives, as published by meshtastic/meshtastic-android in .skills/navigation-and-di/SKILL.md and read by ahel’s review.

Description

This skill covers dependency injection (Koin Annotations 4.2.x) and JetBrains Navigation 3 (1.1.x) architecture, constraints, and anti-patterns within the Meshtastic-Android KMP codebase.

Dependency Injection (Koin)

Guidelines

  1. Annotations First: Use @Module, @ComponentScan, and @KoinViewModel annotations directly in commonMain shared modules to encapsulate dependency graphs per feature.
  2. App Root Assembly: Don't assume feature/core @Module classes are active automatically. Ensure they are included by the app root module (@Module(includes = [...])) in androidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt and desktopApp/.../DesktopKoinModule.kt.
  3. No Platform Bleed: Don't put Android framework dependencies (Context, Activity, Application) into shared commonMain business logic. Inject interfaces instead.
  4. Resolution: Resolve app-layer wrappers via koinViewModel() or injected bindings within Compose navigation graphs.

Anti-Patterns

  • Compile Safety Outside An Entry Point: Do not enable compileSafety on a library module. Validation is whole-graph and runs at the @KoinApplication entry point, so a library validates against a graph it cannot see and reports KOIN-D003 for definitions its consumers supply. KoinConventionPlugin enables it only for the modules in KOIN_ENTRY_POINTS.
  • Default Parameters: Do not expect Koin to inject default parameters automatically. The K2 plugin's skipDefaultValues = true behavior skips parameters with default Kotlin values.

Koin Startup Pattern (K2 Compiler Plugin)

The project uses the K2 Compiler Plugin (koin-compiler-plugin, not KSP). The canonical startup uses the plugin's typed startKoin<T>() stub, which the plugin transforms at compile time via IR:

// Bootstrap class — separate from @Module, references the root module graph
@KoinApplication(modules = [AppKoinModule::class])
object AndroidKoinApp

// In Application.onCreate()
startKoin<AndroidKoinApp> {
    androidContext(this@MeshUtilApplication)
    workManagerFactory()
}
  • @KoinApplication goes on a dedicated bootstrap object, not on a @Module class.
  • startKoin<T>() (from org.koin.plugin.module.dsl) is a compiler plugin stub — if the plugin isn't applied, it throws NotImplementedError.
  • stopKoin() uses the standard runtime API (org.koin.core.context.stopKoin).
  • compileSafety is on at the entry points only (:androidApp, :desktopApp). Plugin 1.1.0 replaced per-module validation with whole-graph validation, so the flag is only meaningful where the graph is assembled. A new app target must be added to KOIN_ENTRY_POINTS or it is never validated.
  • A definition two @Module(includes = ...) levels below the entry point is invisible to the index. The flavor modules carry @Configuration as well as their includes for this reason; dropping the includes removes them from the runtime graph, which KoinVerificationTest catches.
  • Hand-written DSL module { } definitions are not reachable by the assembled graph, which is why :desktopApp uses @Module classes.

Navigation 3

Guidelines

  1. Types: Use Navigation 3 types consistently (NavKey, NavBackStack, EntryProviderScope).
  2. Typed Routes: Keep route definitions in core:navigation/src/commonMain/.../Routes.kt as @Serializable sealed interface hierarchies. Don't use ad-hoc strings.
  3. Graph Assembly: Define feature navigation graphs as extension functions on EntryProviderScope<NavKey> in commonMain (e.g., fun EntryProviderScope<NavKey>.settingsGraph(backStack)).
  4. Host Integration: Use MeshtasticNavDisplay (from core:ui/commonMain) as the Navigation 3 host. Do not configure decorators manually inside feature modules.
  5. Back Handlers: Use NavigationBackHandler from androidx.navigationevent:navigationevent-compose for back gestures in multiplatform code. Do not use Android's BackHandler.
  6. Deep Links: Use DeepLinkRouter.route() in core:navigation to synthesize typed backstacks from RESTful paths.

Anti-Patterns

  • Single Backstack for Multiple Tabs: Do not use a single NavBackStack list for multiple tabs. Use MultiBackstack (from core:navigation).
  • Decorator Reuse Across Tabs: Do not reuse the same NavEntryDecorator instances across different backstacks. When rendering an active tab in MeshtasticNavDisplay, you must supply a fresh set of decorators (using remember(backStack) { ... }) bound to the active backstack instance to prevent permanent ViewModelStore destruction.
  • Custom Backstack Mutation: Do not mutate back navigation with custom stacks disconnected from the app backstack. Mutate NavBackStack<NavKey> directly with add(...) and removeLastOrNull().

Reference Anchors

  • App Startup / Koin Bootstrap: androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt
  • DI Bootstrap Object: androidApp/src/main/kotlin/org/meshtastic/app/di/AndroidKoinApp.kt
  • DI App Wiring: androidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt
  • Shared Routes: core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt
  • Desktop Nav Shell: desktopApp/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt

Signals

GitHub stars
2k
Forks
515
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
navigation-and-di
Source
github.com/meshtastic/meshtastic-android