Status: Accepted
Date: 2025-10-15
Android projects can be structured as a single monolithic module or split into multiple modules. As this is intended to be a reusable template project with potential for open-source contributions, we needed a scalable architecture that supports:
- Multiple feature teams working in parallel
- Independent feature development and testing
- Clear separation of concerns
- Reduced build times through module-level compilation
- Reusable components across features
We adopted a multi-module Clean Architecture with the following structure:
:app # Application orchestration
:core:* # Shared infrastructure
:feature:* # Feature modules
:feature:*:api # Navigation contracts
-
:coremodules - Shared infrastructure::core:ui- Design system and reusable UI components:core:navigation- Navigation3 wrapper and NavKey interfaces:core:network- Network configuration (Retrofit, OkHttp):core:data- Data layer (repositories, Room):core:domain- Business logic (pure Kotlin, no Android deps):core:datastore:*- Local data persistence
-
:featuremodules - Feature implementations::feature:recording- Recording functionality:feature:profile- User profile management- Each feature is self-contained with its own UI, ViewModels, and logic
-
:feature:*:apimodules - Navigation contracts:- Contain only route definitions (sealed interfaces)
- Enable cross-feature navigation without coupling
- Other features depend on APIs, not implementations
:app → :feature:*, :core:*
:feature:* → :feature:*:api, :core:*
:feature:*:api → :core:navigation only
:core:data → :core:network, :core:domain, :core:datastore:*
:core:network → :core:datastore:preferences (for token storage)
:core:domain → No Android dependencies (pure Kotlin)
- ✅ Parallel development: Teams can work on different features without conflicts
- ✅ Faster builds: Gradle only rebuilds changed modules
- ✅ Testability: Each module can be tested in isolation
- ✅ Reusability: Core modules can be extracted to separate libraries
- ✅ Clear boundaries: Enforced separation prevents spaghetti dependencies
- ✅ Feature flags: Easy to enable/disable features at build time
⚠️ Initial complexity: More files and build scripts to manage⚠️ Learning curve: Team needs to understand module boundaries⚠️ Overhead: Small projects might not benefit from this structure
- Convention plugins minimize boilerplate in each module
- Clear documentation (this ADR) explains the structure
- Feature scaffolding task automates new module creation
-
Single module monolith
- Rejected: Doesn't scale for multiple teams or large codebases
- Build times grow linearly with code size
-
Feature-based modules only (no core)
- Rejected: Leads to code duplication across features
- No clear place for shared infrastructure
-
Layer-based modules (ui, domain, data)
- Rejected: Doesn't support parallel feature development
- Still couples unrelated features together