Communitygithub.com

android-clean-architecture

Patrones de Arquitectura Limpia para proyectos Android y Kotlin Multiplatform: estructura de módulos, reglas de dependencia, UseCases, Repositorios y patrones de capa de datos.

¿Qué es android-clean-architecture?

Esta habilidad proporciona una guía completa de Arquitectura Limpia para proyectos Android y Kotlin Multiplatform (KMP). Define reglas estrictas de dependencia entre módulos, como que `domain` nunca debe depender de `data` o `presentation`, asegurando un código puramente Kotlin y desacoplado. Incluye patrones detallados para implementar UseCases, Repositorios, DataSources y mappers, con ejemplos concretos de código para Room, SQLDelight y Ktor. También cubre la inyección de dependencias con Koin y Hilt, el manejo de errores con sellados como `Result` o `Try`, y el uso de plugins de convención en Gradle para reducir la duplicación. Es útil para desarrolladores que buscan una estructura modular, mantenible y testeable en sus aplicaciones Android o KMP.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/android-clean-architecture

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Arquitectura Limpia para Android

Patrones de Arquitectura Limpia para proyectos Android y KMP. Cubre límites de módulos, inversión de dependencias, patrones UseCase/Repository y diseño de capa de datos con Room, SQLDelight y Ktor.

Cuándo activar

  • Al estructurar módulos de un proyecto Android o KMP
  • Al implementar UseCases, Repositories o DataSources
  • Al diseñar el flujo de datos entre capas (domain, data, presentation)
  • Al configurar inyección de dependencias con Koin o Hilt
  • Al trabajar con Room, SQLDelight o Ktor en una arquitectura por capas

Estructura de módulos

Diseño recomendado

project/
├── app/                  # Punto de entrada Android, cableado de DI, clase Application
├── core/                 # Utilidades compartidas, clases base, tipos de error
├── domain/               # UseCases, modelos de dominio, interfaces de repositorio (Kotlin puro)
├── data/                 # Implementaciones de repositorio, DataSources, BD, red
├── presentation/         # Pantallas, ViewModels, modelos de UI, navegación
├── design-system/        # Componentes Compose reutilizables, tema, tipografía
└── feature/              # Módulos de funcionalidad (opcional, para proyectos grandes)
    ├── auth/
    ├── settings/
    └── profile/

Reglas de dependencia

app → presentation, domain, data, core
presentation → domain, design-system, core
data → domain, core
domain → core (o sin dependencias)
core → (nada)

Crítico: domain NUNCA debe depender de data, presentation ni de ningún framework. Contiene únicamente Kotlin puro.

Capa de dominio

Patrón UseCase

Cada UseCase representa una operación de negocio. Usa operator fun invoke para puntos de llamada limpios:

class GetItemsByCategoryUseCase(
    private val repository: ItemRepository
) {
    suspend operator fun invoke(category: String): Result<List<Item>> {
        return repository.getItemsByCategory(category)
    }
}

// UseCase basado en Flow para streams reactivos
class ObserveUserProgressUseCase(
    private val repository: UserRepository
) {
    operator fun invoke(userId: String): Flow<UserProgress> {
        return repository.observeProgress(userId)
    }
}

Modelos de dominio

Los modelos de dominio son clases de datos Kotlin simples, sin anotaciones de framework:

data 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 }

Interfaces de repositorio

Definidas en domain, implementadas en data:

interface ItemRepository {
    suspend fun getItemsByCategory(category: String): Result<List<Item>>
    suspend fun saveItem(item: Item): Result<Unit>
    fun observeItems(): Flow<List<Item>>
}

Capa de datos

Implementación del repositorio

Coordina entre fuentes de datos locales y remotas:

class 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() }
        }
    }
}

Patrón Mapper

Mantén los mappers como funciones de extensión cerca de los modelos de datos:

// En la capa de datos
fun 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)

@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
)

@Dao
interface 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)

-- Item.sq
CREATE 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;

Cliente de red Ktor (KMP)

class ItemRemoteDataSource(private val client: HttpClient) {

    suspend fun fetchItems(category: String): List<ItemDto> {
        return client.get("api/items") {
            parameter("category", category)
        }.body()
    }
}

// Configuración de HttpClient con negociación de contenido
val httpClient = HttpClient {
    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
    install(Logging) { level = LogLevel.HEADERS }
    defaultRequest { url("https://api.example.com/") }
}

Inyección de dependencias

Koin (compatible con KMP)

// Módulo de dominio
val domainModule = module {
    factory { GetItemsByCategoryUseCase(get()) }
    factory { ObserveUserProgressUseCase(get()) }
}

// Módulo de datos
val dataModule = module {
    single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
    single { ItemLocalDataSource(get()) }
    single { ItemRemoteDataSource(get()) }
}

// Módulo de presentación
val presentationModule = module {
    viewModelOf(::ItemListViewModel)
    viewModelOf(::DashboardViewModel)
}

Hilt (solo Android)

@Module
@InstallIn(SingletonComponent::class)
abstract class RepositoryModule {
    @Binds
    abstract fun bindItemRepository(impl: ItemRepositoryImpl): ItemRepository
}

@HiltViewModel
class ItemListViewModel @Inject constructor(
    private val getItems: GetItemsByCategoryUseCase
) : ViewModel()

Manejo de errores

Patrón Result/Try

Usa Result<T> o un tipo sellado personalizado para la propagación de errores:

sealed 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
}

// En el ViewModel — mapear a estado de UI
viewModelScope.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) }
    }
}

Plugins de convención (Gradle)

Para proyectos KMP, usa plugins de convención para reducir la duplicación de archivos de compilación:

// build-logic/src/main/kotlin/kmp-library.gradle.kts
plugins {
    id("org.jetbrains.kotlin.multiplatform")
}

kotlin {
    androidTarget()
    iosX64(); iosArm64(); iosSimulatorArm64()
    sourceSets {
        commonMain.dependencies { /* dependencias compartidas */ }
        commonTest.dependencies { implementation(kotlin("test")) }
    }
}

Aplicar en los módulos:

// domain/build.gradle.kts
plugins { id("kmp-library") }

Anti-patrones a evitar

  • Importar clases del framework Android en domain — mantenlo como Kotlin puro
  • Exponer entidades de base de datos o DTOs a la capa de UI — siempre mapea a modelos de dominio
  • Poner lógica de negocio en ViewModels — extráela a UseCases
  • Usar GlobalScope o corrutinas no estructuradas — usa viewModelScope o concurrencia estructurada
  • Implementaciones de repositorio demasiado grandes — divide en DataSources enfocados
  • Dependencias circulares entre módulos — si A depende de B, B no debe depender de A

Referencias

Consulta la habilidad: compose-multiplatform-patterns para patrones de UI. Consulta la habilidad: kotlin-coroutines-flows para patrones asíncronos.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills relacionados