Skill: DI and Navigation 3 Architecture
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
- Annotations First: Use
@Module, @ComponentScan, and @KoinViewModel annotations directly in commonMain shared modules to encapsulate dependency graphs per feature.
- 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.
- No Platform Bleed: Don't put Android framework dependencies (
Context, Activity, Application) into shared commonMain business logic. Inject interfaces instead.
- 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
- Types: Use Navigation 3 types consistently (
NavKey, NavBackStack, EntryProviderScope).
- Typed Routes: Keep route definitions in
core:navigation/src/commonMain/.../Routes.kt as @Serializable sealed interface hierarchies. Don't use ad-hoc strings.
- Graph Assembly: Define feature navigation graphs as extension functions on
EntryProviderScope<NavKey> in commonMain (e.g., fun EntryProviderScope<NavKey>.settingsGraph(backStack)).
- Host Integration: Use
MeshtasticNavDisplay (from core:ui/commonMain) as the Navigation 3 host. Do not configure decorators manually inside feature modules.
- Back Handlers: Use
NavigationBackHandler from androidx.navigationevent:navigationevent-compose for back gestures in multiplatform code. Do not use Android's BackHandler.
- 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
1---2name: navigation-and-di3description: Skill: DI and Navigation 3 Architecture4---5# Skill: DI and Navigation 3 Architecture67## Description8This 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.910## Dependency Injection (Koin)1112### Guidelines131. **Annotations First:** Use `@Module`, `@ComponentScan`, and `@KoinViewModel` annotations directly in `commonMain` shared modules to encapsulate dependency graphs per feature.142. **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`.153. **No Platform Bleed:** Don't put Android framework dependencies (`Context`, `Activity`, `Application`) into shared `commonMain` business logic. Inject interfaces instead.164. **Resolution:** Resolve app-layer wrappers via `koinViewModel()` or injected bindings within Compose navigation graphs.1718### Anti-Patterns19- **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`.20- **Default Parameters:** Do **not** expect Koin to inject default parameters automatically. The K2 plugin's `skipDefaultValues = true` behavior skips parameters with default Kotlin values.2122### Koin Startup Pattern (K2 Compiler Plugin)23The 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:24```kotlin25// Bootstrap class — separate from @Module, references the root module graph26@KoinApplication(modules = [AppKoinModule::class])27object AndroidKoinApp2829// In Application.onCreate()30startKoin<AndroidKoinApp> {31 androidContext(this@MeshUtilApplication)32 workManagerFactory()33}34```35- `@KoinApplication` goes on a **dedicated bootstrap object**, not on a `@Module` class.36- `startKoin<T>()` (from `org.koin.plugin.module.dsl`) is a compiler plugin stub — if the plugin isn't applied, it throws `NotImplementedError`.37- `stopKoin()` uses the standard runtime API (`org.koin.core.context.stopKoin`).38- `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.39- 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.40- Hand-written DSL `module { }` definitions are not reachable by the assembled graph, which is why `:desktopApp` uses `@Module` classes.4142## Navigation 34344### Guidelines451. **Types:** Use Navigation 3 types consistently (`NavKey`, `NavBackStack`, `EntryProviderScope`).462. **Typed Routes:** Keep route definitions in `core:navigation/src/commonMain/.../Routes.kt` as `@Serializable sealed interface` hierarchies. Don't use ad-hoc strings.473. **Graph Assembly:** Define feature navigation graphs as extension functions on `EntryProviderScope<NavKey>` in `commonMain` (e.g., `fun EntryProviderScope<NavKey>.settingsGraph(backStack)`).484. **Host Integration:** Use `MeshtasticNavDisplay` (from `core:ui/commonMain`) as the Navigation 3 host. Do not configure decorators manually inside feature modules.495. **Back Handlers:** Use `NavigationBackHandler` from `androidx.navigationevent:navigationevent-compose` for back gestures in multiplatform code. Do not use Android's `BackHandler`.506. **Deep Links:** Use `DeepLinkRouter.route()` in `core:navigation` to synthesize typed backstacks from RESTful paths.5152### Anti-Patterns53- **Single Backstack for Multiple Tabs:** Do **not** use a single `NavBackStack` list for multiple tabs. Use `MultiBackstack` (from `core:navigation`).54- **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.55- **Custom Backstack Mutation:** Do **not** mutate back navigation with custom stacks disconnected from the app backstack. Mutate `NavBackStack<NavKey>` directly with `add(...)` and `removeLastOrNull()`.5657## Reference Anchors58- **App Startup / Koin Bootstrap:** `androidApp/src/main/kotlin/org/meshtastic/app/MeshUtilApplication.kt`59- **DI Bootstrap Object:** `androidApp/src/main/kotlin/org/meshtastic/app/di/AndroidKoinApp.kt`60- **DI App Wiring:** `androidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.kt`61- **Shared Routes:** `core/navigation/src/commonMain/kotlin/org/meshtastic/core/navigation/Routes.kt`62- **Desktop Nav Shell:** `desktopApp/src/main/kotlin/org/meshtastic/desktop/ui/DesktopMainScreen.kt`