Files
vnidrop/.codex/skills/compose-skill/references/navigation-3-di.md
Hammed Abass fd4fff2c86 docs: add compose development guidelines and skills
Added a comprehensive collection of Compose development guidelines, best practices, and skill definitions in the .codex directory.

Co-authored-by: Junie <junie@jetbrains.com>
2026-07-07 16:55:43 +02:00

5.1 KiB
Raw Blame History

Navigation 3 + Dependency Injection

DI wiring for Nav 3 entries: entry-scoped ViewModels, modularization, and multi-module entry providers with Hilt and Koin.

For Nav 3 core reference (routes, NavDisplay, scenes, animations), see navigation-3.md. For shared navigation concepts and anti-patterns, see navigation.md.

Entry-Scoped ViewModels

Nav 3 scopes ViewModels to entries via rememberViewModelStoreNavEntryDecorator(). Each entry gets its own ViewModelStoreOwner — VMs are created when the entry is added to the back stack and cleared when popped.

BAD: Globally-scoped ViewModel for per-screen data

val viewModel: DetailViewModel = viewModel() // scoped too broadly, not entry-scoped

GOOD: Entry-scoped ViewModel

// Requires rememberViewModelStoreNavEntryDecorator() in entryDecorators
val viewModel: DetailViewModel = viewModel() // scoped to entry via decorator

For shared state across entries, lift state to a parent composable or use a shared ViewModel at the Activity/App scope.

Hilt Integration

For general Hilt setup, modules, and scopes, see hilt.md. Below covers Nav 3specific patterns only.

hiltViewModel in entry blocks (Android only)

entry<Home> {
    val viewModel = hiltViewModel<HomeViewModel>()
    HomeScreen(viewModel = viewModel)
}

Factory parameters with @AssistedInject

When the ViewModel needs values from the navigation key that aren't in SavedStateHandle:

entry<Create> { createKey ->
    val viewModel = hiltViewModel<CreationViewModel, CreationViewModel.Factory>(
        creationCallback = { factory -> factory.create(originalImageUrl = createKey.fileName) },
    )
    CreationScreen(viewModel = viewModel)
}

Multibinding entry providers for modularization

Each feature module contributes an entry builder via Hilt multibindings. The app module aggregates them automatically:

// Feature module
@Module @InstallIn(ActivityRetainedComponent::class)
object FeatureAModule {
    @IntoSet @Provides
    fun provideEntryBuilder(): EntryProviderScope<NavKey>.() -> Unit = {
        featureAEntryBuilder()
    }
}

// App module — MainActivity
@Inject
lateinit var entryBuilders: Set<@JvmSuppressWildcards EntryProviderScope<NavKey>.() -> Unit>

NavDisplay(
    entryProvider = entryProvider {
        entryBuilders.forEach { builder -> this.builder() }
    },
    // ...
)

Koin Integration

For general Koin setup, modules, and scopes, see koin.md. Below covers Nav 3specific patterns only.

koinViewModel in entry blocks (Android + CMP)

entry<Details> { key ->
    val viewModel = koinViewModel<DetailViewModel> { parametersOf(key.id) }
    DetailScreen(viewModel = viewModel)
}

Koin navigation DSL + koinEntryProvider

Declare navigation entries inside Koin modules. Koin aggregates them automatically — no manual entry provider needed:

val appModule = module {
    navigation<HomeRoute> { HomeScreen(viewModel = koinViewModel()) }
    navigation<DetailRoute> { route ->
        DetailScreen(viewModel = koinViewModel { parametersOf(route.id) })
    }
}

NavDisplay(
    backStack = rememberNavBackStack(HomeRoute),
    onBack = { backStack.removeLastOrNull() },
    entryProvider = koinEntryProvider(),
)

Platform-specific extensions

Function Platform Description
koinEntryProvider<T>() All (CMP) Composable entry provider — use in commonMain
getEntryProvider<T>() Android Eager entry provider via AndroidScopeComponent

Modularization

api / impl module split

feature-home/
  api/
    HomeNavKey.kt             -- @Serializable data object HomeNavKey : NavKey
  impl/
    HomeScreen.kt             -- composable UI
    HomeEntryBuilder.kt       -- extension function on EntryProviderScope
  • api — contains only the NavKey route definitions. Other features depend on this.
  • impl — contains UI, ViewModels, and entry builder. Depends on its own api + other features' api modules.

Entry builder extension functions

Each feature exposes an extension function; the app module aggregates them:

// feature-home/impl
fun EntryProviderScope<NavKey>.homeEntry(navigator: Navigator) {
    entry<HomeNavKey> {
        HomeScreen(onItemClick = { navigator.navigate(DetailsNavKey(it)) })
    }
}

// app module
NavDisplay(
    entryProvider = entryProvider {
        homeEntry(navigator)
        searchEntry(navigator)
        profileEntry(navigator)
    },
    // ...
)

How you wire the ViewModel and state inside each entry depends on your project's architecture. Navigation is driven by ViewModel effects — the route layer translates semantic effects to back-stack operations.

Koin module aggregation (CMP)

// Feature module
val featureModule = module {
    navigation<HomeNavKey> { HomeScreen(viewModel = koinViewModel()) }
    navigation<ProfileNavKey> { ProfileScreen(viewModel = koinViewModel()) }
}

// App module
NavDisplay(
    backStack = backStack,
    onBack = { backStack.removeLastOrNull() },
    entryProvider = koinEntryProvider(),
)