Skill v1.0.3
currentAutomated scan100/1003 files
version: "1.0.3" name: navigation-and-di description: Koin Annotations dependency injection and JetBrains Navigation 3 in Meshtastic-Android, including the anti-patterns that compile cleanly and then fail at runtime. Use this whenever you add a screen, a route, a ViewModel or a Koin module, or when navigation or injection behaves unexpectedly.
Skill: DI and Navigation 3 Architecture
Description
This skill covers dependency injection (Koin Annotations 4.2.x) and Navigation 3 1.2 (the JetBrains navigation3-ui mirror over AndroidX navigation3-runtime) architecture, constraints, and anti-patterns within the Meshtastic-Android KMP codebase.
Dependency Injection (Koin)
Guidelines
- Annotations First: Use
@Module,@ComponentScan, and@KoinViewModelannotations directly incommonMainshared modules to encapsulate dependency graphs per feature. - App Root Assembly: Don't assume feature/core
@Moduleclasses are active automatically. Ensure they are included by the app root module (@Module(includes = [...])) inandroidApp/src/main/kotlin/org/meshtastic/app/di/AppKoinModule.ktanddesktopApp/.../DesktopKoinModule.kt. - No Platform Bleed: Don't put Android framework dependencies (
Context,Activity,Application) into sharedcommonMainbusiness 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
compileSafetyon a library module. Validation is whole-graph and runs at the@KoinApplicationentry point, so a library validates against a graph it cannot see and reportsKOIN-D003for definitions its consumers supply.KoinConventionPluginenables it only for the modules inKOIN_ENTRY_POINTS. - Default Parameters: Do not expect Koin to inject default parameters automatically. The K2 plugin's
skipDefaultValues = truebehavior 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()}
@KoinApplicationgoes on a dedicated bootstrap object, not on a@Moduleclass.startKoin<T>()(fromorg.koin.plugin.module.dsl) is a compiler plugin stub — if the plugin isn't applied, it throwsNotImplementedError.stopKoin()uses the standard runtime API (org.koin.core.context.stopKoin).compileSafetyis 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 toKOIN_ENTRY_POINTSor it is never validated.- A definition two
@Module(includes = ...)levels below the entry point is invisible to the index. The flavor modules carry@Configurationas well as theirincludesfor this reason; dropping theincludesremoves them from the runtime graph, whichKoinVerificationTestcatches. - Hand-written DSL
module { }definitions are not reachable by the assembled graph, which is why:desktopAppuses@Moduleclasses.
Navigation 3
Guidelines
- Types: Use Navigation 3 types consistently (
NavKey,NavBackStack,EntryProviderScope). - Typed Routes: Keep route definitions in
core:navigation/src/commonMain/.../Routes.ktas@Serializable sealed interfacehierarchies. Don't use ad-hoc strings. - Graph Assembly: Define feature navigation graphs as extension functions on
EntryProviderScope<NavKey>incommonMain(e.g.,fun EntryProviderScope<NavKey>.settingsGraph(backStack)). - Host Integration: Use
MeshtasticNavDisplay(fromcore:ui/commonMain) as the Navigation 3 host. It owns the entry decorators; do not create them in app hosts or feature modules. - Scenes:
MeshtasticNavDisplayrendersListDetailSceneStrategyscenes (listPane(),detailPane(),extraPane()entry metadata) and falls back to a single pane. It registers no dialog or supporting-pane strategy, so that metadata has no effect. - Back Handlers: Use
NavigationBackHandlerfromandroidx.navigationevent:navigationevent-composefor back gestures in multiplatform code. Do not use Android'sBackHandler. - Deep Links: Use
DeepLinkRouter.route()incore:navigationto synthesize typed backstacks from RESTful paths. - Tab Lifetime: A hidden tab's entry ViewModels and saved state live until that entry is popped from its own stack; switching tabs does not clear them.
Anti-Patterns
- Single Backstack for Multiple Tabs: Do not use a single
NavBackStacklist for multiple tabs. UseMultiBackstack(fromcore:navigation). - Decorator Reuse Across Tabs: Do not decorate several back stacks with one
NavEntryDecoratorset. Navigation 3 pops every entry missing from the stack it is given, so a shared saveable-state or ViewModel-store decorator clears the tab you just left. TheMultiBackstackoverload ofMeshtasticNavDisplaygives every tab's stack its own saveable-state and ViewModel-store decorators throughrememberDecoratedNavEntries, following the per-stack decorators of the Navigation 3 multiple back stacks recipe, and passes only the active tab's entries toNavDisplay. ItsentryProvidermust therefore resolve every tab's keys, not only the active tab's. - Custom Backstack Mutation: Do not mutate back navigation with custom stacks disconnected from the app backstack. Mutate
NavBackStack<NavKey>directly withadd(...)andremoveLastOrNull(). - Inline Entries in Nav Tests: Do not write a test's entries inline in a composable host.
entryProviderandentry<K>areinline, so inline entries become remembered lambdas the compiler updates in place and a stale back-stack capture passes unseen. Declare them in a plainEntryProviderScope<NavKey>extension, as feature graphs do.
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