Navigation between feature modules

Architecture and dependency injection Senior Kotlin 2.0+ API 24+ Reviewed By Best Latest Apps

Order the steps

Order the steps to let Feed open Profile without depending on it.

  • In :app, build the NavHost and pass { navController.navigate(ProfileRoute(it)) }
  • Define @Serializable data class ProfileRoute(val id: String) in :feature:profile:api
  • Give the Feed screen an onOpenProfile: (String) -> Unit parameter
  • Add fun NavGraphBuilder.profileGraph() in :feature:profile

Choose an answer, then open Show answer below to check it.

Show answer

Answer: B D C A

  1. Define @Serializable data class ProfileRoute(val id: String) in :feature:profile:api The route is the only shared type.
  2. Add fun NavGraphBuilder.profileGraph() in :feature:profile The feature registers its own destinations.
  3. Give the Feed screen an onOpenProfile: (String) -> Unit parameter Feed knows nothing about navigation.
  4. In :app, build the NavHost and pass { navController.navigate(ProfileRoute(it)) } Only the app module wires features together.

30-second answer

Each feature exposes a small public API: its route types and a NavGraphBuilder.featureGraph() extension, often in a separate api module. The app module, which depends on all features, assembles the NavHost and passes navigation callbacks like onOpenProfile: (String) -> Unit into each feature. Features only see routes and lambdas, never each other's screens.

Practise saying it · 35 s. No recording, just a timer

Explain navigation across feature modules.

Say your answer out loud, then check which of these points you covered:

  • Routes in api modules
  • Features register their graphs
  • Lambdas for navigation
  • App module wires it together

Deep dive

Callbacks keep features testable: a test can assert the lambda was called.

Deep links are declared next to the route in the feature that owns it.

For dynamic feature modules, navigation needs the dynamic navigation library or a reflection-free entry point.

Avoid a global Navigator singleton that every feature imports and calls.

Common mistakes

  • Importing another feature's screen composable.
  • Passing the NavController deep into feature screens.
  • A god navigation module that knows every screen's internals.

Senior take & follow-ups

Draw the dependency graph: app → features → api/core. That answers it.

Follow-up questions

Think of your answer first, then open the hint.

  1. Why pass lambdas instead of NavController?

    Show hint

    Features stay independent and testable.

  2. Where are routes defined?

    Show hint

    In the owning feature's api module or a shared navigation module.

Free tool

Deep link tester

Free tool: build the adb and intent:// commands for any link and check an intent filter before you ship it.

Suggest a fix if something here is wrong or out of date.