androidengineers.Book a session

Advanced Architecture & Modularization

Modularization Strategies

article60 minHard

Modularization splits an Android project into multiple Gradle modules. Each module has a defined responsibility and explicit dependencies. Large apps at companies like Google, Spotify, and Airbnb are highly modularized.

The benefits are real but the cost is also real. Start with a clear reason.

Why Modularize

Build time. Gradle only recompiles changed modules. In a monolith, a one-line change can trigger a full recompilation. With modules, only the affected module and those that depend on it rebuild.

Ownership. Teams can own specific modules. Changes are isolated.

Enforced boundaries. A module cannot access internal classes from another module unless they are explicitly exposed. This replaces informal rules with compile-time enforcement.

Dynamic delivery. Feature modules can be downloaded on demand with Play Feature Delivery, reducing initial APK size.

Layer-Based Modularization

The simplest approach is one module per architectural layer:

:app          (wires everything)
:presentation (ViewModels, UI state)
:domain       (use cases, models, repository interfaces)
:data         (repository implementations, Room, Retrofit)

This works for small teams but does not scale. The :data module becomes a monolith containing all remote and local sources.

Feature-Based Modularization

Organize by feature, not by layer:

:app
:feature:profile
:feature:feed
:feature:settings
:core:ui         (shared composables, themes)
:core:network    (Retrofit setup, interceptors)
:core:database   (Room setup, shared DAOs)
:core:domain     (shared models, base use cases)

Each feature module is independent and owns its layers internally. Core modules provide shared infrastructure.

Hybrid Approach (Recommended)

Most production apps combine both:

:app
:feature:profile
  ├── :feature:profile:ui
  ├── :feature:profile:domain
  └── :feature:profile:data
:core:ui
:core:network
:core:testing

This gives strong boundaries inside large features while keeping shared infrastructure in core modules.

Dependency Graph Rules

The dependency graph must be acyclic. Never let a lower-level module depend on a higher-level one.

:feature modules → :core modules
:app → :feature modules + :core modules
:core:domain → nothing (only pure Kotlin)

Enforce this with a Gradle plugin or lint rules. Circular dependencies cause build failures and are hard to untangle later.

Module Types in Gradle

// feature module — Android library
plugins {
    id("com.android.library")
    id("org.jetbrains.kotlin.android")
}

// core:domain — pure Kotlin, fastest to compile
plugins {
    id("org.jetbrains.kotlin.jvm")
}

Pure Kotlin modules (:core:domain) compile faster than Android library modules because they skip the Android toolchain.

Convention Plugins

Repeating the same Gradle configuration across 20 modules is noise. Use convention plugins to centralize common setup.

// build-logic/src/main/kotlin/AndroidFeatureConventionPlugin.kt
class AndroidFeatureConventionPlugin : Plugin<Project> {
    override fun apply(target: Project) {
        with(target) {
            pluginManager.apply("com.android.library")
            pluginManager.apply("org.jetbrains.kotlin.android")
            extensions.configure<LibraryExtension> {
                compileSdk = 35
                defaultConfig.minSdk = 24
            }
        }
    }
}

Then each feature module just applies one plugin:

plugins {
    id("convention.android.feature")
}

Common Pitfalls

PitfallFix
Too many modules too earlyStart with 5-10 modules, split as the team grows
No shared navigation contractDefine navigation routes in :core:navigation
Feature modules depending on each otherRoute through :app using a navigation abstraction
Putting everything in :coreCore should be truly shared; feature logic stays in features

Practice

Take a single-module project. Extract a :core:domain module with data classes and repository interfaces. Move no implementations — just the contracts. Then verify that the :app module compiles with the new structure.

Summary

Modularization enforces boundaries, speeds up builds, and scales to large teams. Feature-based organization with shared core modules is the industry default. Start small, use convention plugins to reduce boilerplate, and enforce a strict dependency graph.

YOUR LEARNING JOURNEY

0 of 17 available lessons completed

Progress saved in this browser. No account needed.
Modularization Strategies | Senior Android Developer | Android Engineers