Modularization & Multi-Module Architecture
📖 Concept
Modularization splits a monolithic app into independent Gradle modules. At senior level, this is essential knowledge — all large Android apps at Google use multi-module architecture.
Why modularize:
- Build speed — Only changed modules recompile. Parallel module compilation.
- Team scalability — Teams own modules independently with clear boundaries.
- Code isolation — Prevents accidental coupling, enforces API boundaries via
internalvisibility. - Dynamic delivery — Play Feature Delivery for on-demand modules.
- Reusability — Shared modules (design system, analytics) across apps.
Module types:
:app → Application module (thin, just wiring)
:feature:home → Feature module (UI + ViewModel)
:feature:profile → Feature module
:feature:settings → Feature module
:core:data → Data layer (repositories, data sources)
:core:domain → Domain layer (use cases, models)
:core:network → Network layer (Retrofit, interceptors)
:core:database → Database layer (Room, DAOs)
:core:ui → Shared UI components (design system)
:core:common → Utilities, extensions
Dependency rules:
:feature:*→ depends on:core:*:core:data→ depends on:core:domain,:core:network,:core:database:core:domain→ NO dependencies (pure Kotlin):feature:*→ does NOT depend on other:feature:*modules:app→ depends on all:feature:*modules
Navigation between features: Since features can't depend on each other, cross-feature navigation uses: Navigation component with deep links, or a shared navigation module with route definitions.
💻 Code Example
1// settings.gradle.kts — Module structure2include(3 ":app",4 ":feature:home",5 ":feature:profile",6 ":feature:settings",7 ":core:data",8 ":core:domain",9 ":core:network",10 ":core:database",11 ":core:ui",12 ":core:common"13)1415// :feature:home/build.gradle.kts16plugins {17 id("com.android.library")18 id("dagger.hilt.android.plugin")19}2021dependencies {22 implementation(project(":core:domain"))23 implementation(project(":core:ui"))24 implementation(project(":core:common"))25 // CANNOT depend on :feature:profile — features are isolated26}2728// Cross-feature navigation via shared routes29// :core:common/src/.../Navigation.kt30object Routes {31 const val HOME = "home"32 const val PROFILE = "profile/{userId}"33 const val SETTINGS = "settings"3435 fun profileRoute(userId: String) = "profile/$userId"36}3738// :app/src/.../NavGraph.kt39@Composable40fun AppNavGraph(navController: NavHostController) {41 NavHost(navController, startDestination = Routes.HOME) {42 // Each feature provides its own navigation subgraph43 homeNavGraph(navController)44 profileNavGraph(navController)45 settingsNavGraph(navController)46 }47}4849// :feature:home — provides its nav graph as an extension function50fun NavGraphBuilder.homeNavGraph(navController: NavController) {51 composable(Routes.HOME) {52 HomeScreen(53 onProfileClick = { userId ->54 navController.navigate(Routes.profileRoute(userId))55 }56 )57 }58}5960// Convention plugins for consistent module config61// build-logic/convention/src/.../AndroidFeaturePlugin.kt62class AndroidFeaturePlugin : Plugin<Project> {63 override fun apply(target: Project) {64 with(target) {65 pluginManager.apply("com.android.library")66 pluginManager.apply("dagger.hilt.android.plugin")67 dependencies {68 add("implementation", project(":core:ui"))69 add("implementation", project(":core:domain"))70 }71 }72 }73}
🏋️ Practice Exercise
Practice:
- Refactor a monolithic app into at least 4 modules and measure build time improvement
- Create a convention plugin that standardizes feature module configuration
- Implement cross-feature navigation without direct module dependencies
- Draw the dependency graph for a 10-module app and ensure no circular dependencies
- Set up a feature module with its own Hilt component
⚠️ Common Mistakes
Creating too many modules too early — start with 3-4 modules, split as needed
Circular dependencies between feature modules — features must be isolated, communicate via shared contracts
Putting all code in :core:common — it becomes a god module; split by responsibility
Not using convention plugins — each module has different Gradle config, causing maintenance overhead
Forgetting to use
internalfor module-private APIs — everything defaults to public in Kotlin
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Modularization & Multi-Module Architecture. Login to unlock this feature.