tezvyn:

Navigate between feature modules without direct dependencies?

Curated by the Tezvyn teamSource: developer.android.comadvanced
Navigate between feature modules without direct dependencies?

Tests dependency inversion in modular apps. Answer by defining navigation interfaces in a shared module, implementing them in the :app module, and using DI to connect them. A red flag is suggesting direct module dependencies or using reflection for navigation.

WHAT THIS TESTS: This question tests your understanding of dependency inversion and architectural patterns for maintaining strict module boundaries. The interviewer wants to see if you can design a system where modules communicate (navigate) without creating tight coupling, which would negate the benefits of modularization like improved build times and team autonomy.

A GOOD ANSWER COVERS: An excellent answer describes a solution using inversion of control. First, state the problem: a direct dependency from :feature_profile to :feature_settings is an anti-pattern that leads to a tangled dependency graph and slower builds. Second, propose an abstraction. Feature modules should only depend on a lightweight contract or API module, for example, :navigation-api. This module defines interfaces for navigation, like interface SettingsNavigator { fun openSettings() }. Third, explain the implementation. The :app module, which has visibility over all feature modules, provides the concrete implementation of these interfaces. It uses dependency injection (e.g., Hilt) to bind the interface to a class that can access the root NavController. Fourth, describe the flow: the profile feature gets the SettingsNavigator injected, calls openSettings(), and the implementation in the :app module executes navController.navigate(R.id.settings_graph).

COMMON WRONG ANSWERS: Adding a direct Gradle dependency like implementation project(":feature_settings") in the :feature_profile module's build.gradle.kts file. This is the primary anti-pattern the question is designed to catch. Another red flag is suggesting reflection to find and instantiate fragment or activity classes by name. This is brittle, not type-safe, and can be broken by code shrinking tools like R8/ProGuard. Finally, relying on manually constructed implicit intents with string actions for all internal navigation is a weaker pattern; it lacks the compile-time safety of a navigator interface.

LIKELY FOLLOW-UPS: Expect questions about data transfer and results. For passing arguments, you'd add parameters to the navigator interface method (e.g., openSettings(userId: String)) and pass them via a Bundle or Safe Args. To get a result back, you would use the Jetpack Navigation Component's result APIs, like setFragmentResultListener and setFragmentResult, which work across module boundaries.

ONE CONCRETE EXAMPLE: :navigation-api module: Contains interface SettingsNavigator { fun navigateToSettings() }. :feature_profile module: Depends on :navigation-api. Its ViewModel receives SettingsNavigator via its constructor (@Inject constructor(private val navigator: SettingsNavigator)) and calls navigator.navigateToSettings(). :app module: Depends on :navigation-api, :feature_profile, and :feature_settings. It contains the Hilt binding: class AppSettingsNavigator @Inject constructor(...) : SettingsNavigator { override fun navigateToSettings() { /* get NavController and navigate to settings graph */ } }.

Read the original → developer.android.com

Get five bites like this every day.

Tezvyn delivers a daily feed of 60-second tech bites with quizzes to lock in what you learn.

Navigate between feature modules without direct dependencies? · Tezvyn