# Android Modularization in 2026: Multi-Module Architecture and Interview Questions > Master Android multi-module architecture with convention plugins, Gradle version catalogs, and feature modules. Includes common interview questions on modularization strategies. - Published: 2026-09-17 - Updated: 2026-09-17 - Author: Anthony Fillion-Maillet - Tags: android, kotlin, gradle, architecture, modularization - Reading time: 9 min --- Android modularization transforms monolithic codebases into maintainable, scalable systems where teams work in parallel without stepping on each other's code. A well-modularized Android app builds faster, tests in isolation, and onboards new developers in days rather than weeks. > **Modularization at a Glance** > > Modularization splits an Android app into independent Gradle modules with clear boundaries. Low coupling between modules and high cohesion within each module are the guiding principles. Google's Now in Android reference app demonstrates this pattern with 40+ modules. ## Why Multi-Module Architecture Matters for Android Apps A single-module Android app recompiles everything when one line changes. The networking layer modification triggers UI recompilation. Two developers editing different features create merge conflicts in shared files. Build times stretch to 4+ minutes on incremental builds. Multi-module architecture addresses these pain points directly: - **Faster builds**: Gradle only recompiles affected modules. A change in `:feature:checkout` leaves `:feature:profile` untouched. - **Parallel development**: Teams own separate modules with defined APIs. Merge conflicts drop significantly. - **Isolated testing**: Unit tests run against a single module without loading the entire app. - **Code reuse**: A `:core:ui` design system module becomes a library usable across multiple apps. The [official Android modularization guide](https://developer.android.com/topic/modularization) recommends structuring modules around features or layers that change together, following the Single Responsibility Principle. ## Module Types and Their Responsibilities The [Now in Android repository](https://github.com/android/nowinandroid) establishes a proven module taxonomy that scales to enterprise apps. ```kotlin // settings.gradle.kts - Module structure include(":app") // Core modules - shared utilities include(":core:common") include(":core:data") include(":core:database") include(":core:datastore") include(":core:designsystem") include(":core:domain") include(":core:model") include(":core:network") include(":core:ui") // Feature modules - screen-specific logic include(":feature:home") include(":feature:search") include(":feature:bookmarks") include(":feature:settings") ``` **App module**: Contains `MainActivity`, navigation setup, and app-level scaffolding. Depends on all feature modules and required core modules. **Core modules**: Provide shared functionality across features. `:core:network` handles API calls. `:core:database` manages Room entities. `:core:designsystem` defines the app's visual components. **Feature modules**: Encapsulate a single screen or user flow. Each feature module depends only on the core modules it needs, never on other feature modules. This structure enforces unidirectional dependencies: features depend on core, and app depends on features. ## Gradle Version Catalogs for Dependency Management Managing dependencies across 25+ modules without version conflicts requires centralized configuration. [Gradle Version Catalogs](https://docs.gradle.org/current/userguide/version_catalogs.html) solve this with a single `libs.versions.toml` file. ```toml # gradle/libs.versions.toml [versions] agp = "8.7.0" kotlin = "2.1.0" compose-bom = "2026.09.00" ksp = "2.1.0-1.0.29" hilt = "2.54" room = "2.7.0" retrofit = "2.11.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version = "1.15.0" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" } hilt-compiler = { group = "com.google.dagger", name = "hilt-android-compiler", version.ref = "hilt" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } retrofit-core = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } [bundles] compose = ["androidx-compose-ui", "androidx-compose-material3"] room = ["room-runtime", "room-ktx"] [plugins] android-application = { id = "com.android.application", version.ref = "agp" } android-library = { id = "com.android.library", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } ``` Modules then reference dependencies with type-safe accessors: ```kotlin // feature/home/build.gradle.kts dependencies { implementation(libs.bundles.compose) implementation(libs.hilt.android) ksp(libs.hilt.compiler) } ``` Gradle generates these accessors during sync, enabling IDE autocompletion and compile-time validation. ## Convention Plugins: DRY Build Configuration A 25-module project without convention plugins repeats the same `compileSdk`, `minSdk`, `composeOptions`, and plugin applications in each `build.gradle.kts`. Different modules can accidentally use different configurations. Convention plugins centralize this logic in a `build-logic` included build. ```kotlin // build-logic/convention/src/main/kotlin/AndroidLibraryConventionPlugin.kt import com.android.build.api.dsl.LibraryExtension import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.kotlin.dsl.configure class AndroidLibraryConventionPlugin : Plugin { override fun apply(target: Project) { with(target) { pluginManager.apply("com.android.library") pluginManager.apply("org.jetbrains.kotlin.android") extensions.configure { compileSdk = 35 defaultConfig { minSdk = 26 testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" } compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } } } } } ``` Register the plugin in `build-logic/convention/build.gradle.kts`: ```kotlin // build-logic/convention/build.gradle.kts plugins { `kotlin-dsl` } dependencies { compileOnly(libs.android.gradlePlugin) compileOnly(libs.kotlin.gradlePlugin) } gradlePlugin { plugins { register("androidLibrary") { id = "myapp.android.library" implementationClass = "AndroidLibraryConventionPlugin" } register("androidFeature") { id = "myapp.android.feature" implementationClass = "AndroidFeatureConventionPlugin" } register("androidHilt") { id = "myapp.android.hilt" implementationClass = "AndroidHiltConventionPlugin" } } } ``` Feature modules now require minimal configuration: ```kotlin // feature/home/build.gradle.kts plugins { alias(libs.plugins.myapp.android.feature) alias(libs.plugins.myapp.android.hilt) } dependencies { implementation(projects.core.data) implementation(projects.core.designsystem) } ``` Google's [Now in Android build-logic](https://github.com/android/nowinandroid/blob/main/build-logic/README.md) uses this exact pattern. ## Module Communication Patterns Feature modules must not depend on each other directly. This constraint preserves parallel compilation and prevents circular dependencies. Three patterns handle cross-feature communication. ### Navigation via Shared Routes ```kotlin // core/navigation/src/main/kotlin/Routes.kt object Routes { const val HOME = "home" const val PRODUCT_DETAIL = "product/{productId}" const val CHECKOUT = "checkout" fun productDetail(productId: String) = "product/$productId" } ``` Feature modules import routes from `:core:navigation` without knowing which module implements each destination. ### Shared Domain Models ```kotlin // core/model/src/main/kotlin/Product.kt data class Product( val id: String, val name: String, val price: BigDecimal, val imageUrl: String ) ``` Both `:feature:catalog` and `:feature:checkout` depend on `:core:model` for the `Product` class. ### Event Bus or Shared ViewModel For runtime communication, a shared event bus in `:core:common` or a scoped ViewModel handles cross-feature events: ```kotlin // core/common/src/main/kotlin/CartEventBus.kt object CartEventBus { private val _events = MutableSharedFlow() val events: SharedFlow = _events.asSharedFlow() suspend fun emit(event: CartEvent) { _events.emit(event) } } sealed interface CartEvent { data class ItemAdded(val productId: String) : CartEvent data class ItemRemoved(val productId: String) : CartEvent } ``` ## Interview Questions on Android Modularization Modularization appears frequently in senior Android interviews. Interviewers assess understanding of build systems, architecture decisions, and practical trade-offs. > **Interview Tip** > > When discussing modularization in interviews, reference concrete metrics: build time improvements, team velocity gains, or specific module counts from projects. Abstract answers about "better separation of concerns" are less compelling than "build times dropped from 4 minutes to 45 seconds after modularization." **Q: What's the difference between api and implementation dependencies in Gradle?** `implementation` dependencies are internal to the module. Consumers of the module cannot access them. `api` dependencies are exposed transitively: if module A uses `api(libs.retrofit)`, module B depending on A can use Retrofit classes directly. Rule of thumb: use `implementation` by default. Only use `api` when the dependency is part of the module's public API. Overusing `api` breaks encapsulation and slows builds. **Q: How do you handle navigation between feature modules that don't depend on each other?** Create a `:core:navigation` module containing route definitions and navigation interfaces. Feature modules depend on this shared module. The app module wires destinations to implementations. This follows the dependency inversion principle: features depend on abstractions, not concrete implementations. **Q: What are the trade-offs of having too many modules?** Each module adds Gradle configuration overhead: build files, resource processing, manifest merging. Initial sync times increase. The tipping point varies by project, but 50+ modules is common in large apps. The benefits (parallel builds, isolated tests, clear ownership) typically outweigh the overhead for teams of 5+ developers. **Q: How do convention plugins differ from buildSrc?** `buildSrc` changes trigger a full project rebuild. Convention plugins in an included build only recompile when the plugin code changes, not when consuming modules change. This makes convention plugins faster for iterative development. Additionally, convention plugins can be published as a separate artifact for reuse across repositories. **Q: How do you test a feature module in isolation?** Feature modules depend on core modules through interfaces. Create fake implementations in test fixtures: ```kotlin // core/data/src/testFixtures/kotlin/FakeProductRepository.kt class FakeProductRepository : ProductRepository { private val products = mutableListOf() override suspend fun getProducts(): List = products fun addProduct(product: Product) { products.add(product) } } ``` Feature module tests inject fakes via Hilt's testing APIs or manual constructor injection. For more practice questions on Android architecture, see the [MVVM architecture interview questions](/technologies/android/interview-questions/android-mvvm-architecture) and [dependency injection module](/technologies/android/interview-questions/android-dependency-injection). ## Key Takeaways for Multi-Module Android Projects - Structure modules by feature (`:feature:home`, `:feature:checkout`) and layer (`:core:data`, `:core:ui`). Features depend on core, never on each other. - Use Gradle Version Catalogs (`libs.versions.toml`) to centralize dependency versions. Bundles group related dependencies for cleaner build files. - Implement convention plugins in `build-logic` to eliminate duplicated configuration across modules. One plugin change updates all consuming modules. - Enforce unidirectional dependencies: app depends on features, features depend on core. Tools like [Dependency Guard](https://github.com/dropbox/dependency-guard) can automate enforcement. - Measure build performance before and after modularization. Track incremental build times, not just clean builds, to validate improvements. - Keep module boundaries stable. Frequent restructuring negates the benefits of modularization. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/android/android-modularization-multi-module-architecture-2026