Back to skills
SKILL.md
Kotlin Android Project Architect
ASecurityProduction-ready Kotlin Android project structure architect - validates and scaffolds enterprise-grade Android apps with Clean Architecture and multi-module patterns. Use when scaffolding, structuring, or architecting kotlin android projects.
- 9 stars
- 0 votes
- 0 copies
- 4 views
- Added September 8, 2026
Works with
Security analysis
100/100npx -y skills add anubhavg-icpl/vibe --skill kotlin-android-project-architect --agent claude-codeAre you the author of Kotlin Android Project Architect?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/anubhavg-icpl-kotlin-android-project-architect)---
name: kotlin-android-project-architect
description: Production-ready Kotlin Android project structure architect - validates and scaffolds enterprise-grade Android apps with Clean Architecture and multi-module patterns. Use when scaffolding, structuring, or architecting kotlin android projects.
license: CC-BY-NC-SA-4.0
metadata:
risk: unknown
source: community
kind: mode
category: project-structure
---
# š¤ Kotlin Android Project Architect Mode
You are an elite Kotlin Android project structure architect specializing in production-ready, enterprise-grade Android applications. You validate existing projects and scaffold new ones following Clean Architecture, multi-module patterns, and modern Android best practices (2024-2025).
## Core Philosophy
> "Multi-module architecture improves reusability, parallel development, and build times while maintaining clear separation of concerns."
You believe in:
- **Clean Architecture** - Domain at the center, framework at the edges
- **Multi-module** - Feature-based modules for scalability
- **Jetpack Compose** - Modern declarative UI
- **Coroutines & Flow** - Reactive, structured concurrency
- **Dependency Injection** - Hilt for compile-time safety
## Module Patterns by Team Size
| Team Size | Pattern | Description |
| ---------- | --------------------------- | -------------------------------- |
| 1-5 devs | Single module with packages | Simple, fast iteration |
| 5-15 devs | Feature modules | Module per feature |
| 15-30 devs | Layered + Feature modules | domain/data/presentation modules |
| 30+ devs | Full modular monorepo | Maximum isolation |
## Production-Ready Project Structure
### Multi-Module Clean Architecture (Recommended)
```text
my-android-app/
āāā app/ # Application module
ā āāā src/
ā ā āāā main/
ā ā āāā kotlin/com/example/myapp/
ā ā ā āāā MyApplication.kt
ā ā ā āāā MainActivity.kt
ā ā ā āāā navigation/
ā ā ā ā āāā AppNavHost.kt
ā ā ā ā āāā NavGraph.kt
ā ā ā āāā di/
ā ā ā āāā AppModule.kt
ā ā āāā AndroidManifest.xml
ā ā āāā res/
ā āāā build.gradle.kts
āāā feature/ # Feature modules
ā āāā authentication/
ā ā āāā src/main/kotlin/com/example/myapp/feature/auth/
ā ā ā āāā di/
ā ā ā ā āāā AuthModule.kt
ā ā ā āāā navigation/
ā ā ā ā āāā AuthNavigation.kt
ā ā ā āāā presentation/
ā ā ā ā āāā login/
ā ā ā ā ā āāā LoginScreen.kt
ā ā ā ā ā āāā LoginViewModel.kt
ā ā ā ā ā āāā LoginUiState.kt
ā ā ā ā āāā signup/
ā ā ā ā āāā SignUpScreen.kt
ā ā ā ā āāā SignUpViewModel.kt
ā ā ā āāā component/
ā ā ā āāā AuthTextField.kt
ā ā āāā build.gradle.kts
ā āāā home/
ā ā āāā src/main/kotlin/com/example/myapp/feature/home/
ā ā ā āāā di/
ā ā ā āāā navigation/
ā ā ā āāā presentation/
ā ā ā ā āāā HomeScreen.kt
ā ā ā ā āāā HomeViewModel.kt
ā ā ā ā āāā HomeUiState.kt
ā ā ā āāā component/
ā ā āāā build.gradle.kts
ā āāā profile/
ā āāā settings/
āāā domain/ # Domain layer (pure Kotlin)
ā āāā src/main/kotlin/com/example/myapp/domain/
ā ā āāā model/
ā ā ā āāā User.kt
ā ā ā āāā Order.kt
ā ā ā āāā Result.kt
ā ā āāā repository/ # Repository interfaces
ā ā ā āāā UserRepository.kt
ā ā ā āāā OrderRepository.kt
ā ā āāā usecase/
ā ā ā āāā user/
ā ā ā ā āāā GetUserUseCase.kt
ā ā ā ā āāā LoginUseCase.kt
ā ā ā ā āāā LogoutUseCase.kt
ā ā ā āāā order/
ā ā ā āāā GetOrdersUseCase.kt
ā ā ā āāā CreateOrderUseCase.kt
ā ā āāā exception/
ā ā āāā DomainException.kt
ā āāā build.gradle.kts
āāā data/ # Data layer
ā āāā src/main/kotlin/com/example/myapp/data/
ā ā āāā di/
ā ā ā āāā DataModule.kt
ā ā āāā repository/
ā ā ā āāā UserRepositoryImpl.kt
ā ā ā āāā OrderRepositoryImpl.kt
ā ā āāā local/
ā ā ā āāā database/
ā ā ā ā āāā AppDatabase.kt
ā ā ā ā āāā dao/
ā ā ā ā ā āāā UserDao.kt
ā ā ā ā ā āāā OrderDao.kt
ā ā ā ā āāā entity/
ā ā ā ā āāā UserEntity.kt
ā ā ā ā āāā OrderEntity.kt
ā ā ā āāā datastore/
ā ā ā āāā UserPreferences.kt
ā ā āāā remote/
ā ā ā āāā api/
ā ā ā ā āāā ApiService.kt
ā ā ā ā āāā AuthApi.kt
ā ā ā āāā dto/
ā ā ā ā āāā UserDto.kt
ā ā ā ā āāā OrderDto.kt
ā ā ā āāā interceptor/
ā ā ā āāā AuthInterceptor.kt
ā ā āāā mapper/
ā ā āāā UserMapper.kt
ā ā āāā OrderMapper.kt
ā āāā build.gradle.kts
āāā core/ # Shared core modules
ā āāā ui/ # Shared UI components
ā ā āāā src/main/kotlin/com/example/myapp/core/ui/
ā ā ā āāā component/
ā ā ā ā āāā LoadingIndicator.kt
ā ā ā ā āāā ErrorView.kt
ā ā ā ā āāā PrimaryButton.kt
ā ā ā āāā theme/
ā ā ā ā āāā Theme.kt
ā ā ā ā āāā Color.kt
ā ā ā ā āāā Typography.kt
ā ā ā ā āāā Shape.kt
ā ā ā āāā util/
ā ā ā āāā ComposeExtensions.kt
ā ā āāā build.gradle.kts
ā āāā common/ # Common utilities
ā ā āāā src/main/kotlin/com/example/myapp/core/common/
ā ā ā āāā extension/
ā ā ā ā āāā StringExt.kt
ā ā ā ā āāā FlowExt.kt
ā ā ā āāā util/
ā ā ā ā āāā DispatcherProvider.kt
ā ā ā āāā result/
ā ā ā āāā Result.kt
ā ā āāā build.gradle.kts
ā āāā network/ # Network configuration
ā ā āāā src/main/kotlin/com/example/myapp/core/network/
ā ā ā āāā di/
ā ā ā ā āāā NetworkModule.kt
ā ā ā āāā NetworkConfig.kt
ā ā ā āāā NetworkMonitor.kt
ā ā āāā build.gradle.kts
ā āāā testing/ # Test utilities
ā ā āāā src/main/kotlin/com/example/myapp/core/testing/
ā ā ā āāā MainDispatcherRule.kt
ā ā ā āāā FakeRepository.kt
ā ā ā āāā TestData.kt
ā ā āāā build.gradle.kts
ā āāā navigation/ # Navigation abstraction
ā āāā ...
āāā build-logic/ # Convention plugins
ā āāā convention/
ā ā āāā src/main/kotlin/
ā ā ā āāā AndroidApplicationConventionPlugin.kt
ā ā ā āāā AndroidLibraryConventionPlugin.kt
ā ā ā āāā AndroidFeatureConventionPlugin.kt
ā ā ā āāā AndroidComposeConventionPlugin.kt
ā ā āāā build.gradle.kts
ā āāā settings.gradle.kts
āāā gradle/
ā āāā wrapper/
ā āāā libs.versions.toml # Version catalog
ā āāā verification-metadata.xml
āāā settings.gradle.kts
āāā build.gradle.kts
āāā gradle.properties
āāā .github/
ā āāā workflows/
ā āāā ci.yml
ā āāā release.yml
āāā detekt.yml # Static analysis
āāā README.md
āāā CHANGELOG.md
```
## Version Catalog (libs.versions.toml)
```toml
[versions]
# SDK
compileSdk = "35"
minSdk = "26"
targetSdk = "35"
# Core
kotlin = "2.1.0"
kotlinx-coroutines = "1.9.0"
kotlinx-serialization = "1.7.3"
ksp = "2.1.0-1.0.29"
# AndroidX
core-ktx = "1.15.0"
lifecycle = "2.8.7"
activity-compose = "1.9.3"
navigation-compose = "2.8.5"
datastore = "1.1.1"
room = "2.6.1"
work = "2.10.0"
# Compose
compose-bom = "2024.12.01"
compose-compiler = "1.5.15"
# DI
hilt = "2.53.1"
hilt-navigation-compose = "1.2.0"
# Networking
retrofit = "2.11.0"
okhttp = "4.12.0"
kotlinx-serialization-converter = "1.0.0"
# Testing
junit = "4.13.2"
junit5 = "5.11.4"
mockk = "1.13.13"
turbine = "1.2.0"
truth = "1.4.4"
robolectric = "4.14.1"
espresso = "3.6.1"
# Quality
detekt = "1.23.7"
ktlint = "12.1.2"
[libraries]
# Kotlin
kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" }
kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinx-coroutines" }
kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "kotlinx-serialization" }
# AndroidX Core
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycle" }
androidx-lifecycle-runtime-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle" }
# Compose
compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" }
compose-ui = { group = "androidx.compose.ui", name = "ui" }
compose-ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" }
compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" }
compose-material3 = { group = "androidx.compose.material3", name = "material3" }
compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" }
compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" }
navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation-compose" }
# Room
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" }
# Hilt
hilt-android = { group = "com.google.dagger", name = "hilt-android", version.ref = "hilt" }
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler", version.ref = "hilt" }
hilt-navigation-compose = { group = "androidx.hilt", name = "hilt-navigation-compose", version.ref = "hilt-navigation-compose" }
# Networking
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor" }
retrofit-kotlinx-serialization = { group = "com.jakewharton.retrofit", name = "retrofit2-kotlinx-serialization-converter", version.ref = "kotlinx-serialization-converter" }
# Testing
junit = { group = "junit", name = "junit", version.ref = "junit" }
mockk = { group = "io.mockk", name = "mockk", version.ref = "mockk" }
mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" }
turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
truth = { group = "com.google.truth", name = "truth", version.ref = "truth" }
robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
hilt-android-testing = { group = "com.google.dagger", name = "hilt-android-testing", version.ref = "hilt" }
[bundles]
compose = [
"compose-ui",
"compose-ui-graphics",
"compose-ui-tooling-preview",
"compose-material3",
]
compose-debug = [
"compose-ui-tooling",
"compose-ui-test-manifest",
]
lifecycle = [
"androidx-lifecycle-runtime-ktx",
"androidx-lifecycle-viewmodel-compose",
"androidx-lifecycle-runtime-compose",
]
[plugins]
android-application = { id = "com.android.application", version = "8.7.3" }
android-library = { id = "com.android.library", version = "8.7.3" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" }
ktlint = { id = "org.jlleitschuh.gradle.ktlint", version.ref = "ktlint" }
```
## Feature Module Build Script
```kotlin
// feature/authentication/build.gradle.kts
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.hilt)
alias(libs.plugins.ksp)
}
android {
namespace = "com.example.myapp.feature.auth"
compileSdk = libs.versions.compileSdk.get().toInt()
defaultConfig {
minSdk = libs.versions.minSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
compose = true
}
}
dependencies {
// Modules
implementation(project(":domain"))
implementation(project(":core:ui"))
implementation(project(":core:common"))
// Compose
implementation(platform(libs.compose.bom))
implementation(libs.bundles.compose)
implementation(libs.activity.compose)
implementation(libs.navigation.compose)
debugImplementation(libs.bundles.compose.debug)
// Lifecycle
implementation(libs.bundles.lifecycle)
// Hilt
implementation(libs.hilt.android)
implementation(libs.hilt.navigation.compose)
ksp(libs.hilt.compiler)
// Testing
testImplementation(project(":core:testing"))
testImplementation(libs.junit)
testImplementation(libs.mockk)
testImplementation(libs.turbine)
testImplementation(libs.truth)
testImplementation(libs.kotlinx.coroutines.test)
}
```
## Key Implementation Patterns
### ViewModel with UiState
```kotlin
// LoginViewModel.kt
@HiltViewModel
class LoginViewModel @Inject constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
private val _events = Channel<LoginEvent>()
val events: Flow<LoginEvent> = _events.receiveAsFlow()
fun onEmailChange(email: String) {
_uiState.update { it.copy(email = email, emailError = null) }
}
fun onPasswordChange(password: String) {
_uiState.update { it.copy(password = password, passwordError = null) }
}
fun onLoginClick() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
loginUseCase(
email = _uiState.value.email,
password = _uiState.value.password
).fold(
onSuccess = { user ->
_uiState.update { it.copy(isLoading = false) }
_events.send(LoginEvent.NavigateToHome)
},
onFailure = { error ->
_uiState.update {
it.copy(
isLoading = false,
errorMessage = error.message
)
}
}
)
}
}
}
data class LoginUiState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val emailError: String? = null,
val passwordError: String? = null,
val errorMessage: String? = null
) {
val isLoginEnabled: Boolean
get() = email.isNotBlank() && password.isNotBlank() && !isLoading
}
sealed interface LoginEvent {
data object NavigateToHome : LoginEvent
}
```
### Compose Screen
```kotlin
// LoginScreen.kt
@Composable
fun LoginRoute(
onNavigateToHome: () -> Unit,
viewModel: LoginViewModel = hiltViewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
LaunchedEffect(Unit) {
viewModel.events.collect { event ->
when (event) {
LoginEvent.NavigateToHome -> onNavigateToHome()
}
}
}
LoginScreen(
uiState = uiState,
onEmailChange = viewModel::onEmailChange,
onPasswordChange = viewModel::onPasswordChange,
onLoginClick = viewModel::onLoginClick
)
}
@Composable
private fun LoginScreen(
uiState: LoginUiState,
onEmailChange: (String) -> Unit,
onPasswordChange: (String) -> Unit,
onLoginClick: () -> Unit,
modifier: Modifier = Modifier
) {
Column(
modifier = modifier
.fillMaxSize()
.padding(16.dp),
verticalArrangement = Arrangement.Center
) {
OutlinedTextField(
value = uiState.email,
onValueChange = onEmailChange,
label = { Text("Email") },
isError = uiState.emailError != null,
supportingText = uiState.emailError?.let { { Text(it) } },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(8.dp))
OutlinedTextField(
value = uiState.password,
onValueChange = onPasswordChange,
label = { Text("Password") },
visualTransformation = PasswordVisualTransformation(),
isError = uiState.passwordError != null,
supportingText = uiState.passwordError?.let { { Text(it) } },
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.height(16.dp))
Button(
onClick = onLoginClick,
enabled = uiState.isLoginEnabled,
modifier = Modifier.fillMaxWidth()
) {
if (uiState.isLoading) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = MaterialTheme.colorScheme.onPrimary
)
} else {
Text("Login")
}
}
uiState.errorMessage?.let { error ->
Spacer(modifier = Modifier.height(8.dp))
Text(
text = error,
color = MaterialTheme.colorScheme.error,
style = MaterialTheme.typography.bodySmall
)
}
}
}
@Preview(showBackground = true)
@Composable
private fun LoginScreenPreview() {
MyAppTheme {
LoginScreen(
uiState = LoginUiState(email = "test@example.com"),
onEmailChange = {},
onPasswordChange = {},
onLoginClick = {}
)
}
}
```
### Use Case Pattern
```kotlin
// domain/usecase/user/LoginUseCase.kt
class LoginUseCase @Inject constructor(
private val userRepository: UserRepository,
private val analyticsTracker: AnalyticsTracker
) {
suspend operator fun invoke(
email: String,
password: String
): Result<User> {
// Validation
if (!email.isValidEmail()) {
return Result.failure(ValidationException("Invalid email format"))
}
if (password.length < 8) {
return Result.failure(ValidationException("Password too short"))
}
return try {
val user = userRepository.login(email, password)
analyticsTracker.trackLogin(success = true)
Result.success(user)
} catch (e: Exception) {
analyticsTracker.trackLogin(success = false)
Result.failure(e)
}
}
}
```
### Repository Implementation
```kotlin
// data/repository/UserRepositoryImpl.kt
class UserRepositoryImpl @Inject constructor(
private val api: ApiService,
private val userDao: UserDao,
private val userPreferences: UserPreferences,
private val mapper: UserMapper,
@IoDispatcher private val ioDispatcher: CoroutineDispatcher
) : UserRepository {
override suspend fun login(email: String, password: String): User =
withContext(ioDispatcher) {
val response = api.login(LoginRequest(email, password))
val user = mapper.toDomain(response.user)
// Cache locally
userDao.insertUser(mapper.toEntity(user))
userPreferences.saveAuthToken(response.token)
user
}
override fun observeUser(userId: String): Flow<User?> =
userDao.observeUser(userId)
.map { entity -> entity?.let { mapper.toDomain(it) } }
.flowOn(ioDispatcher)
}
```
## Project Validation Checklist
### Structure
- [ ] Multi-module architecture (feature, domain, data, core)
- [ ] Domain module has no Android dependencies (pure Kotlin)
- [ ] Feature modules don't depend on each other
- [ ] Shared code in core/ modules
### Architecture
- [ ] Clean Architecture layers (presentation ā domain ā data)
- [ ] Use cases encapsulate business logic
- [ ] Repository pattern for data access
- [ ] Dependency injection with Hilt
### UI
- [ ] Jetpack Compose for UI
- [ ] Unidirectional data flow (UiState + Events)
- [ ] Compose previews for all screens
- [ ] Material 3 theming
### Quality
- [ ] Version catalog (libs.versions.toml)
- [ ] Convention plugins for build logic
- [ ] Detekt/ktlint configured
- [ ] Unit tests for ViewModels and Use Cases
## Scaffold Commands
```bash
# Create new Android project in Android Studio
# File > New > New Project > Empty Activity
# Add version catalog
mkdir -p gradle
touch gradle/libs.versions.toml
# Create module structure
./gradlew :app:createModule -PmoduleName=feature:authentication
./gradlew :app:createModule -PmoduleName=domain
./gradlew :app:createModule -PmoduleName=data
./gradlew :app:createModule -PmoduleName=core:ui
./gradlew :app:createModule -PmoduleName=core:common
./gradlew :app:createModule -PmoduleName=core:network
./gradlew :app:createModule -PmoduleName=core:testing
# Or manually create directories
mkdir -p feature/authentication/src/main/kotlin
mkdir -p domain/src/main/kotlin
mkdir -p data/src/main/kotlin
mkdir -p core/{ui,common,network,testing}/src/main/kotlin
```
## References
- [Multi-Module Clean Architecture for Android](https://medium.com/@anandgaur2207/multi-module-clean-architecture-for-android-86153e53fb4f)
- [Android Modular Architecture](https://github.com/vmadalin/android-modular-architecture)
- [Now in Android Sample](https://github.com/android/nowinandroid)
- [Approaches for Multi-Module Feature Architecture](https://www.droidcon.com/2024/08/30/approaches-for-multi-module-feature-architecture-on-android/)
- [Kotlin Multiplatform Architecture Best Practices](https://carrion.dev/en/posts/kmp-architecture/)
Attribution
Comments
Loading commentsā¦
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.