Skip to main content
The Compose Project Template uses a multi-module architecture to enforce separation of concerns, enable parallel builds, and improve code organization. Each module has a specific responsibility and well-defined dependencies.

Module types

The project contains three types of modules:

Feature modules

UI implementations for specific features

Domain modules

Business logic and domain models

Data modules

Data sources and repositories

Complete module structure

Here’s the full module hierarchy as defined in settings.gradle.kts:
rootProject.name = "CPT"

// Application module
include(":app")

// Shared modules
include(":common")              // Pure Kotlin utilities
include(":commonAndroid")       // Android-specific shared code
include(":navigation")          // Navigation definitions

// Data layer modules
include(":data:local")          // Room database implementation
include(":data:remote")         // Retrofit API implementation
include(":data:repository")     // Repository implementations
include(":data:session")        // SharedPreferences implementation
include(":data:source")         // Data source interfaces

// Domain layer modules
include(":domain:gateway")      // Repository interfaces
include(":domain:model")        // Business objects
include(":domain:useCase")      // Use case implementations

// Feature layer modules
include(":feature:home")        // Home screen feature
include(":feature:launcher")    // Launcher/splash screen
include(":feature:articledetail") // Article detail screen
The project uses TYPESAFE_PROJECT_ACCESSORS to reference modules type-safely as projects.domain.model instead of ":domain:model".

Application module (app)

The app module is the entry point that assembles all features and handles application-level configuration.

Responsibilities

  • Application class with Hilt setup
  • Main Activity and navigation host
  • Application-level theme and configuration
  • Dependency aggregation from all feature modules

Key files

@HiltAndroidApp
class App : Application() {
    override fun onCreate() {
        super.onCreate()
    }
}

Dependencies

The app module depends on all feature, data, and shared modules:
dependencies {
    // Feature modules
    implementation(projects.feature.home)
    implementation(projects.feature.launcher)
    implementation(projects.feature.articledetail)
    
    // Data modules (for Hilt module installation)
    implementation(projects.data.local)
    implementation(projects.data.repository)
    implementation(projects.data.remote)
    implementation(projects.data.source)
    implementation(projects.data.session)
    
    // Shared modules
    implementation(projects.commonAndroid)
    implementation(projects.navigation)
    
    // Hilt for DI
    implementation(libs.hilt.android)
    implementation(libs.hilt.navigation)
    ksp(libs.hilt.compiler)
}
The app module includes data modules to ensure Hilt DI modules are installed, even though feature modules don’t directly depend on them.

Domain layer modules

The domain layer is the heart of the application and contains zero Android dependencies.

domain/model

Defines business objects (Bo) used throughout the application. Dependencies:
dependencies {
    // No dependencies on other project modules
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
}
Example structure:
package es.mobiledev.domain.model.article

data class ArticleBo(
    val id: Long,
    val title: String,
    val authors: List<AuthorBo>,
    val url: String,
    val imageUrl: String,
    val newsSite: String,
    val summary: String,
    val publishedAt: String,
    val updatedAt: String,
)

data class AuthorBo(
    val name: String,
    val socials: SocialsBo?,
)
The Bo suffix stands for “Business Object” and distinguishes domain models from DTOs (Data Transfer Objects) and DBOs (Database Objects).

domain/gateway

Defines repository interfaces that the data layer implements. Dependencies:
dependencies {
    implementation(projects.domain.model)
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
}
Example structure:
package es.mobiledev.domain.gateway.article

interface ArticleGateway {
    suspend fun getArticles(limit: Long, offset: Long): Flow<ArticleResponseBo>
    suspend fun getArticleById(id: Long): Flow<ArticleBo>
    suspend fun getFavoriteArticles(): Flow<List<ArticleBo>>
    suspend fun saveFavoriteArticle(articleBo: ArticleBo)
    suspend fun removeFavoriteArticle(articleBo: ArticleBo)
    suspend fun isArticleFavorite(id: Long): Flow<Boolean>
}

domain/useCase

Contains use case implementations and Hilt DI modules. Dependencies:
dependencies {
    implementation(projects.domain.model)
    implementation(projects.domain.gateway)
    
    // Hilt for dependency injection
    implementation(libs.hilt.android)
    implementation(libs.hilt.navigation)
    ksp(libs.hilt.compiler)
}
Example structure:
package es.mobiledev.domain.usecase.article

interface GetArticlesUseCase {
    suspend operator fun invoke(
        limit: Long,
        offset: Long
    ): Flow<ArticleResponseBo>
}

class GetArticlesUseCaseImpl(
    private val articleGateway: ArticleGateway,
) : GetArticlesUseCase {
    override suspend fun invoke(
        limit: Long,
        offset: Long
    ): Flow<ArticleResponseBo> = articleGateway.getArticles(limit, offset)
}
  • GetArticlesUseCase: Fetch article list
  • GetArticleByIdUseCase: Get article details
  • GetFavoriteArticlesUseCase: Get favorited articles
  • IsArticleFavoriteUseCase: Check favorite status
  • SaveOrRemoveFavoriteArticleUseCase: Toggle favorite
  • GetLastOpenTimeUseCase: Get last app open time
  • SaveLastOpenTimeUseCase: Save app open timestamp

Data layer modules

The data layer implements domain interfaces and provides data from various sources.

data/source

Defines data source interfaces (contracts for data providers). Dependencies:
dependencies {
    implementation(projects.domain.model)
    implementation(libs.androidx.core.ktx)
}
Example structure:
package es.mobiledev.data.source.article

interface ArticleRemoteDataSource {
    suspend fun getArticles(limit: Long, offset: Long): ArticleResponseBo
    suspend fun getArticleById(id: Long): ArticleBo
}

interface ArticleLocalDataSource {
    suspend fun saveFavoriteArticle(article: ArticleBo)
    suspend fun removeFavoriteArticle(article: ArticleBo)
    suspend fun getFavoriteArticles(): List<ArticleBo>
    suspend fun isArticleFavorite(id: Long): Boolean
}

data/remote

Implements remote data sources using Retrofit. Dependencies:
dependencies {
    implementation(projects.data.source)
    implementation(projects.domain.model)
    
    // Retrofit and networking
    implementation(libs.retrofit)
    implementation(libs.moshi)
    implementation(libs.moshi.kotlin)
    implementation(libs.okhttp.logging)
    
    // Hilt
    implementation(libs.hilt.android)
    ksp(libs.hilt.compiler)
}
Structure:
data/remote/
├── article/
│   ├── ArticleRemoteDataSourceImpl.kt  # Implementation
│   ├── ArticleWs.kt                    # Retrofit interface
│   ├── Mapper.kt                       # DTO to Bo conversion
│   └── dto/
│       ├── ArticleDto.kt
│       ├── ArticleResponseDto.kt
│       ├── AuthorDto.kt
│       └── SocialsDto.kt
├── di/
│   └── RemoteModule.kt                 # Hilt DI module
└── util/
    └── RemoteConstants.kt              # API constants

data/local

Implements local data sources using Room. Dependencies:
dependencies {
    implementation(projects.data.source)
    implementation(projects.domain.model)
    
    // Room database
    implementation(libs.room.runtime)
    implementation(libs.room.ktx)
    ksp(libs.room.compiler)
    
    // Hilt
    implementation(libs.hilt.android)
    ksp(libs.hilt.compiler)
}
Structure:
data/local/
├── article/
│   ├── dao/
│   │   ├── ArticleDao.kt
│   │   └── Mapper.kt                   # DBO to Bo conversion
│   ├── datasource/
│   │   └── ArticleLocalDataSourceImpl.kt
│   └── dbo/
│       └── ArticleDbo.kt               # Room entity
├── di/
│   └── LocalModule.kt                  # Hilt DI module
├── AppRoomDatabase.kt
└── RoomConstants.kt

data/session

Implements SharedPreferences for user preferences. Dependencies:
dependencies {
    implementation(projects.data.source)
    
    // Hilt
    implementation(libs.hilt.android)
    ksp(libs.hilt.compiler)
}
Structure:
data/session/
├── PreferencesDataSourceImpl.kt
├── di/
│   └── SessionModule.kt
└── util/
    ├── PrefKeys.kt
    ├── SessionConstants.kt
    └── SessionUtils.kt

data/repository

Implements gateway interfaces and coordinates between data sources. Dependencies:
dependencies {
    implementation(projects.data.source)
    implementation(projects.domain.gateway)
    implementation(projects.domain.model)
    
    // Hilt
    implementation(libs.hilt.android)
    ksp(libs.hilt.compiler)
}
Structure:
package es.mobiledev.data.repository.article

class ArticleRepository(
    private val remote: ArticleRemoteDataSource,
    private val local: ArticleLocalDataSource,
) : ArticleGateway {
    override suspend fun getArticles(
        limit: Long,
        offset: Long
    ): Flow<ArticleResponseBo> = flowOf(remote.getArticles(limit, offset))
    
    override suspend fun getFavoriteArticles(): Flow<List<ArticleBo>> = 
        flowOf(local.getFavoriteArticles())
    
    // ... other implementations
}
Separating interfaces (data/source) from implementations allows:
  • Feature modules depend on interfaces only
  • Swap implementations without changing consumers
  • Test with mock implementations easily
  • Follow Dependency Inversion Principle
Each data module converts between its own models and domain models:Remote: ArticleDtotoBo()ArticleBoLocal: ArticleBotoDbo()ArticleDbo (and reverse)Repository: Coordinates transformations transparently

Feature layer modules

Feature modules contain UI implementations using Jetpack Compose.

feature/home

Home screen with article list and favorites. Dependencies:
dependencies {
    // Domain layer
    implementation(projects.domain.model)
    implementation(projects.domain.useCase)
    
    // Shared UI components
    implementation(projects.commonAndroid)
    
    // Compose and Android
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.material3)
    
    // Hilt
    implementation(libs.hilt.android)
    implementation(libs.hilt.navigation)
    ksp(libs.hilt.compiler)
}
Structure:
feature/home/
├── component/
│   ├── HomeScreenContent.kt
│   └── HomeScreenTopBar.kt
├── screen/
│   └── HomeScreen.kt
├── state/
│   └── HomeUiState.kt
└── viewmodel/
    └── HomeViewModel.kt

feature/articledetail

Article detail screen with full content display. Structure:
feature/articledetail/
├── component/
│   ├── ArticleDetailActionRow.kt
│   ├── ArticleDetailBody.kt
│   ├── ArticleDetailBottomBar.kt
│   ├── ArticleDetailImage.kt
│   └── ArticleDetailScreenContent.kt
├── screen/
│   └── ArticleDetailScreen.kt
├── state/
│   └── ArticleDetailUiState.kt
└── viewmodel/
    └── ArticleDetailViewModel.kt

feature/launcher

Splash screen with app initialization logic. Structure:
feature/launcher/
├── component/
│   └── LauncherScreenContent.kt
├── screen/
│   └── LauncherScreen.kt
├── state/
│   └── LauncherUiState.kt
└── viewmodel/
    └── LauncherViewModel.kt
All feature modules follow the same structure: component/, screen/, state/, and viewmodel/ directories.

Shared modules

Shared modules contain code reused across multiple features.

common

Pure Kotlin utilities with no Android dependencies. Dependencies:
dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.appcompat)
}
Contents:
  • CommonConstants.kt: Application-wide constants
  • extensions/DateExtensions.kt: Date utility functions
  • extensions/StringExtensions.kt: String utility functions
  • util/AppDispatchers.kt: Coroutine dispatchers wrapper

commonAndroid

Android-specific shared code including base classes and reusable UI components. Dependencies:
dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.lifecycle.viewmodel.compose)
    implementation(platform(libs.androidx.compose.bom))
    implementation(libs.androidx.ui)
    implementation(libs.androidx.material3)
}
Contents:
commonAndroid/
├── ui/
│   ├── base/
│   │   ├── BaseScreen.kt
│   │   ├── BaseViewModel.kt
│   │   ├── ScreenWrapper.kt
│   │   └── UiState.kt
│   └── component/
│       ├── article/ArticleItem.kt
│       ├── button/CPTButton.kt
│       ├── navigationBar/CptNavigationBar.kt
│       ├── topBar/CptTopBar.kt
│       └── ...
└── util/
    ├── ComposableUtils.kt
    ├── IntentUtils.kt
    └── TimeUtils.kt
Contains navigation-related definitions and utilities. Dependencies:
dependencies {
    implementation(libs.androidx.core.ktx)
    implementation(libs.androidx.navigation.runtime.ktx)
    implementation(libs.androidx.navigation.compose)
}

Module dependency graph

Here’s how modules depend on each other:
Green modules represent the domain layer, which has no dependencies on outer layers.

Module guidelines

1

Keep domain pure

Domain modules (domain/*) must not depend on Android framework classes or data/feature modules.
2

Features depend on domain only

Feature modules depend on domain/useCase and domain/model, never on data modules directly.
3

Data implements domain

Data modules implement interfaces defined in the domain layer through dependency inversion.
4

One responsibility per module

Each module should have a single, well-defined purpose. Split large modules when they grow.
5

Minimize dependencies

Only add dependencies that are truly necessary. Fewer dependencies mean faster builds.

Benefits of modular architecture

Parallel builds

Gradle can build independent modules in parallel, reducing build times significantly.

Clear boundaries

Module boundaries enforce separation of concerns and prevent unwanted dependencies.

Team collaboration

Different teams can work on different modules without merge conflicts.

Testability

Modules can be tested independently with their own test suites.

Reusability

Common modules can be extracted and reused in other projects.

Encapsulation

Implementation details are hidden within modules, exposing only public APIs.

Adding a new module

To add a new feature module:
1

Create module directory

mkdir -p feature/myfeature/src/main/java/es/mobiledev/feature/myfeature
2

Add build.gradle.kts

Create feature/myfeature/build.gradle.kts with standard dependencies:
plugins {
    alias(libs.plugins.android.library)
    alias(libs.plugins.kotlin.android)
    alias(libs.plugins.kotlin.compose)
    alias(libs.plugins.ksp)
    alias(libs.plugins.hilt)
}

dependencies {
    implementation(projects.domain.model)
    implementation(projects.domain.useCase)
    implementation(projects.commonAndroid)
    // ... standard feature dependencies
}
3

Include in settings.gradle.kts

include(":feature:myfeature")
4

Add to app module

implementation(projects.feature.myfeature)
5

Sync Gradle

Run ./gradlew --refresh-dependencies to sync the project.
Always follow the dependency rules: features depend on domain, data implements domain, domain depends on nothing.

Next steps

Dependency injection

Learn how Hilt wires modules together

Architecture overview

Review high-level architecture

Build docs developers (and LLMs) love