Claude Agent Skill · by Affaan M

Android Clean Architecture

Install Android Clean Architecture skill for Claude Code from affaan-m/everything-claude-code.

Install
Terminal · npx
$npx skills add https://github.com/vercel-labs/agent-skills --skill vercel-react-native-skills
Works with Paperclip

How Android Clean Architecture fits into a Paperclip company.

Android Clean Architecture drops into any Paperclip agent that handles this kind of work. Assign it to a specialist inside a pre-configured PaperclipOrg company and the skill becomes available on every heartbeat — no prompt engineering, no tool wiring.

S
SaaS FactoryPaired

Pre-configured AI company — 18 agents, 18 skills, one-time purchase.

$27$59
Explore pack
Source file
SKILL.md339 lines
Expand
---name: android-clean-architecturedescription: Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns.origin: ECC--- # Android Clean Architecture Clean Architecture patterns for Android and KMP projects. Covers module boundaries, dependency inversion, UseCase/Repository patterns, and data layer design with Room, SQLDelight, and Ktor. ## When to Activate - Structuring Android or KMP project modules- Implementing UseCases, Repositories, or DataSources- Designing data flow between layers (domain, data, presentation)- Setting up dependency injection with Koin or Hilt- Working with Room, SQLDelight, or Ktor in a layered architecture ## Module Structure ### Recommended Layout ```project/├── app/                  # Android entry point, DI wiring, Application class├── core/                 # Shared utilities, base classes, error types├── domain/               # UseCases, domain models, repository interfaces (pure Kotlin)├── data/                 # Repository implementations, DataSources, DB, network├── presentation/         # Screens, ViewModels, UI models, navigation├── design-system/        # Reusable Compose components, theme, typography└── feature/              # Feature modules (optional, for larger projects)    ├── auth/    ├── settings/    └── profile/``` ### Dependency Rules ```app → presentation, domain, data, corepresentation → domain, design-system, coredata → domain, coredomain → core (or no dependencies)core → (nothing)``` **Critical**: `domain` must NEVER depend on `data`, `presentation`, or any framework. It contains pure Kotlin only. ## Domain Layer ### UseCase Pattern Each UseCase represents one business operation. Use `operator fun invoke` for clean call sites: ```kotlinclass GetItemsByCategoryUseCase(    private val repository: ItemRepository) {    suspend operator fun invoke(category: String): Result<List<Item>> {        return repository.getItemsByCategory(category)    }} // Flow-based UseCase for reactive streamsclass ObserveUserProgressUseCase(    private val repository: UserRepository) {    operator fun invoke(userId: String): Flow<UserProgress> {        return repository.observeProgress(userId)    }}``` ### Domain Models Domain models are plain Kotlin data classes — no framework annotations: ```kotlindata class Item(    val id: String,    val title: String,    val description: String,    val tags: List<String>,    val status: Status,    val category: String) enum class Status { DRAFT, ACTIVE, ARCHIVED }``` ### Repository Interfaces Defined in domain, implemented in data: ```kotlininterface ItemRepository {    suspend fun getItemsByCategory(category: String): Result<List<Item>>    suspend fun saveItem(item: Item): Result<Unit>    fun observeItems(): Flow<List<Item>>}``` ## Data Layer ### Repository Implementation Coordinates between local and remote data sources: ```kotlinclass ItemRepositoryImpl(    private val localDataSource: ItemLocalDataSource,    private val remoteDataSource: ItemRemoteDataSource) : ItemRepository {     override suspend fun getItemsByCategory(category: String): Result<List<Item>> {        return runCatching {            val remote = remoteDataSource.fetchItems(category)            localDataSource.insertItems(remote.map { it.toEntity() })            localDataSource.getItemsByCategory(category).map { it.toDomain() }        }    }     override suspend fun saveItem(item: Item): Result<Unit> {        return runCatching {            localDataSource.insertItems(listOf(item.toEntity()))        }    }     override fun observeItems(): Flow<List<Item>> {        return localDataSource.observeAll().map { entities ->            entities.map { it.toDomain() }        }    }}``` ### Mapper Pattern Keep mappers as extension functions near the data models: ```kotlin// In data layerfun ItemEntity.toDomain() = Item(    id = id,    title = title,    description = description,    tags = tags.split("|"),    status = Status.valueOf(status),    category = category) fun ItemDto.toEntity() = ItemEntity(    id = id,    title = title,    description = description,    tags = tags.joinToString("|"),    status = status,    category = category)``` ### Room Database (Android) ```kotlin@Entity(tableName = "items")data class ItemEntity(    @PrimaryKey val id: String,    val title: String,    val description: String,    val tags: String,    val status: String,    val category: String) @Daointerface ItemDao {    @Query("SELECT * FROM items WHERE category = :category")    suspend fun getByCategory(category: String): List<ItemEntity>     @Upsert    suspend fun upsert(items: List<ItemEntity>)     @Query("SELECT * FROM items")    fun observeAll(): Flow<List<ItemEntity>>}``` ### SQLDelight (KMP) ```sql-- Item.sqCREATE TABLE ItemEntity (    id TEXT NOT NULL PRIMARY KEY,    title TEXT NOT NULL,    description TEXT NOT NULL,    tags TEXT NOT NULL,    status TEXT NOT NULL,    category TEXT NOT NULL); getByCategory:SELECT * FROM ItemEntity WHERE category = ?; upsert:INSERT OR REPLACE INTO ItemEntity (id, title, description, tags, status, category)VALUES (?, ?, ?, ?, ?, ?); observeAll:SELECT * FROM ItemEntity;``` ### Ktor Network Client (KMP) ```kotlinclass ItemRemoteDataSource(private val client: HttpClient) {     suspend fun fetchItems(category: String): List<ItemDto> {        return client.get("api/items") {            parameter("category", category)        }.body()    }} // HttpClient setup with content negotiationval httpClient = HttpClient {    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }    install(Logging) { level = LogLevel.HEADERS }    defaultRequest { url("https://api.example.com/") }}``` ## Dependency Injection ### Koin (KMP-friendly) ```kotlin// Domain moduleval domainModule = module {    factory { GetItemsByCategoryUseCase(get()) }    factory { ObserveUserProgressUseCase(get()) }} // Data moduleval dataModule = module {    single<ItemRepository> { ItemRepositoryImpl(get(), get()) }    single { ItemLocalDataSource(get()) }    single { ItemRemoteDataSource(get()) }} // Presentation moduleval presentationModule = module {    viewModelOf(::ItemListViewModel)    viewModelOf(::DashboardViewModel)}``` ### Hilt (Android-only) ```kotlin@Module@InstallIn(SingletonComponent::class)abstract class RepositoryModule {    @Binds    abstract fun bindItemRepository(impl: ItemRepositoryImpl): ItemRepository} @HiltViewModelclass ItemListViewModel @Inject constructor(    private val getItems: GetItemsByCategoryUseCase) : ViewModel()``` ## Error Handling ### Result/Try Pattern Use `Result<T>` or a custom sealed type for error propagation: ```kotlinsealed interface Try<out T> {    data class Success<T>(val value: T) : Try<T>    data class Failure(val error: AppError) : Try<Nothing>} sealed interface AppError {    data class Network(val message: String) : AppError    data class Database(val message: String) : AppError    data object Unauthorized : AppError} // In ViewModel — map to UI stateviewModelScope.launch {    when (val result = getItems(category)) {        is Try.Success -> _state.update { it.copy(items = result.value, isLoading = false) }        is Try.Failure -> _state.update { it.copy(error = result.error.toMessage(), isLoading = false) }    }}``` ## Convention Plugins (Gradle) For KMP projects, use convention plugins to reduce build file duplication: ```kotlin// build-logic/src/main/kotlin/kmp-library.gradle.ktsplugins {    id("org.jetbrains.kotlin.multiplatform")} kotlin {    androidTarget()    iosX64(); iosArm64(); iosSimulatorArm64()    sourceSets {        commonMain.dependencies { /* shared deps */ }        commonTest.dependencies { implementation(kotlin("test")) }    }}``` Apply in modules: ```kotlin// domain/build.gradle.ktsplugins { id("kmp-library") }``` ## Anti-Patterns to Avoid - Importing Android framework classes in `domain` — keep it pure Kotlin- Exposing database entities or DTOs to the UI layer — always map to domain models- Putting business logic in ViewModels — extract to UseCases- Using `GlobalScope` or unstructured coroutines — use `viewModelScope` or structured concurrency- Fat repository implementations — split into focused DataSources- Circular module dependencies — if A depends on B, B must not depend on A ## References See skill: `compose-multiplatform-patterns` for UI patterns.See skill: `kotlin-coroutines-flows` for async patterns.