Navigation 3 Integration
Alpha Status - Not Production Ready
Navigation 3 is currently in alpha and this Koin integration is experimental. APIs may change without notice. Consider using stable Navigation 2.x for production apps. See Koin Compose for stable navigation patterns.
Koin provides integration with AndroidX Navigation 3 to help you build type-safe navigation graphs with dependency injection.
Setup
Add the Navigation 3 integration dependency to your project:
For Multiplatform Projects
commonMain.dependencies {
implementation("io.insert-koin:koin-compose-navigation3:$koin_version")
}
For Android-only Projects
dependencies {
implementation("io.insert-koin:koin-compose-navigation3:$koin_version")
}
This is an experimental API marked with @KoinExperimentalAPI. The Navigation 3 library is currently in alpha.
Key Concepts
Navigation 3 integration introduces three main components:
EntryProvider- A function that maps route objects to navigation entriesEntryProviderInstaller- A function that registers navigation entries in Koinnavigation<T>- A DSL function to declare navigation destinations in Koin modules
Declaring Navigation Entries
Use the navigation<T>() DSL function in your Koin modules to declare navigation destinations:
Basic Module-level Navigation
val appModule = module {
single { Navigator() }
viewModel { HomeViewModel() }
viewModel { DetailViewModel() }
// Declare navigation entries
navigation<HomeRoute> { route ->
HomeScreen(viewModel = koinViewModel())
}
navigation<DetailRoute> { route ->
DetailScreen(
viewModel = koinViewModel(),
itemId = route.itemId
)
}
}
// Define your routes
@Serializable
object HomeRoute
@Serializable
data class DetailRoute(val itemId: String)
Scoped Navigation
You can also declare navigation entries within Koin scopes, useful for scoping ViewModels and dependencies to specific parts of your navigation graph:
val appModule = module {
// Activity scope
activityRetainedScope {
scoped { Navigator() }
viewModel { ProfileViewModel() }
navigation<ProfileRoute> { route ->
ProfileScreen(viewModel = koinViewModel())
}
}
// also with custom scope
// Activity scope
scope<ComponentActivity> {
scoped { Navigator() }
viewModel { ProfileViewModel() }
navigation<ProfileRoute> { route ->
ProfileScreen(viewModel = koinViewModel())
}
}
}
Using Navigation in Compose
Retrieving the EntryProvider
Use the koinEntryProvider() composable function to retrieve the aggregated navigation entries from Koin:
@Composable
fun App() {
val entryProvider = koinEntryProvider()
NavigationHost(
entryProvider = entryProvider,
startDestination = HomeRoute
) {
// Navigation setup
}
}
With Custom Scope
You can provide a specific Koin scope to retrieve entries from:
@Composable
fun CheckoutFlow() {
val checkoutScope = rememberKoinScope(named("checkout"))
val entryProvider = koinEntryProvider(scope = checkoutScope.value)
NavigationHost(
entryProvider = entryProvider,
startDestination = CheckoutRoute.Start
) {
// Checkout navigation
}
}
Android-specific: ComponentCallbacks Extensions
For Android applications, you can use the ComponentCallbacks extensions to retrieve the entry provider from Activities or Fragments:
class MainActivity : ComponentActivity() {
// Lazy initialization
private val entryProvider by entryProvider()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
NavigationHost(
entryProvider = entryProvider,
startDestination = HomeRoute
)
}
}
}
Or use eager initialization:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val entryProvider = getEntryProvider()
setContent {
NavigationHost(
entryProvider = entryProvider,
startDestination = HomeRoute
)
}
}
}
Accessing Route Parameters
Navigation 3 uses type-safe routes. Access route parameters directly from the route object:
@Serializable
data class DetailRoute(
val itemId: String,
val fromSearch: Boolean = false
)
val appModule = module {
navigation<DetailRoute> { route ->
DetailScreen(
itemId = route.itemId,
fromSearch = route.fromSearch,
viewModel = koinViewModel()
)
}
}
Combining with Koin Scopes
Navigation entries can leverage Koin's scoping capabilities:
val appModule = module {
// Define a scope archetype
scope<ComponentActivity> {
scoped { UserSession() }
viewModel { params ->
UserProfileViewModel(
userId = params.get(),
session = get()
)
}
navigation<UserProfileRoute> { route ->
UserProfileScreen(
viewModel = koinViewModel { parametersOf(route.userId) }
)
}
}
}
Complete Example
Here's a complete example showing a typical navigation setup:
// Define routes
@Serializable
object HomeRoute
@Serializable
object ProfileRoute
@Serializable
data class DetailRoute(val id: String)
@Serializable
data class SettingsRoute(val section: String? = null)
// Koin module
val navigationModule = module {
// Shared dependencies
single { ApiClient() }
// ViewModels
viewModel { HomeViewModel(get()) }
viewModel { ProfileViewModel(get()) }
viewModel { params -> DetailViewModel(get(), params.get()) }
viewModel { SettingsViewModel() }
// Navigation entries
navigation<HomeRoute> { route ->
HomeScreen(viewModel = koinViewModel())
}
navigation<ProfileRoute> { route ->
ProfileScreen(viewModel = koinViewModel())
}
navigation<DetailRoute> { route ->
DetailScreen(
id = route.id,
viewModel = koinViewModel { parametersOf(route.id) }
)
}
navigation<SettingsRoute> { route ->
SettingsScreen(
initialSection = route.section,
viewModel = koinViewModel()
)
}
}
// Compose app
@Composable
fun App() {
val entryProvider = koinEntryProvider()
val navController = rememberNavController()
NavigationHost(
navController = navController,
entryProvider = entryProvider,
startDestination = HomeRoute
) {
// Navigation configuration
}
}
// Main entry point
fun main() = application {
KoinApplication(application = {
modules(navigationModule)
}) {
App()
}
}
Migration from Navigation 2.x
If you're migrating from the Navigation 2.x integration:
Before (Navigation 2.x)
NavHost(navController, startDestination = "home") {
composable("home") {
HomeScreen(viewModel = koinViewModel())
}
composable("detail/{id}") { backStackEntry ->
val id = backStackEntry.arguments?.getString("id")
DetailScreen(id = id, viewModel = koinViewModel())
}
}
After (Navigation 3)
// Define routes
@Serializable
object HomeRoute
@Serializable
data class DetailRoute(val id: String)
// Declare in module
val appModule = module {
navigation<HomeRoute> { route ->
HomeScreen(viewModel = koinViewModel())
}
navigation<DetailRoute> { route ->
DetailScreen(id = route.id, viewModel = koinViewModel())
}
}
// Use in app
@Composable
fun App() {
val entryProvider = koinEntryProvider()
val navController = rememberNavController()
NavigationHost(
navController = navController,
entryProvider = entryProvider,
startDestination = HomeRoute
)
}
API Reference
DSL Functions
Module.navigation<T>(definition)- Declares a singleton navigation entryScopeDSL.navigation<T>(definition)- Declares a scoped navigation entry
Composable Functions
koinEntryProvider(scope)- Retrieves entry provider from given Koin scope
Android Extensions
ComponentCallbacks.entryProvider()- Lazy entry provider initializationComponentCallbacks.getEntryProvider()- Eager entry provider initialization
Limitations
- Navigation 3 is currently in alpha - API may change
- Type-safe navigation requires Kotlin serialization plugin
- Some advanced Navigation 2.x features may not be available yet