Communitygithub.com

Arquitectura Limpia para Android

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. Actívalo al estructurar módulos, capas o flujos de datos en un proyecto Android o KMP.

¿Qué es Arquitectura Limpia para Android?

Esta habilidad proporciona un conjunto completo de patrones de Arquitectura Limpia diseñados específicamente para proyectos Android y Kotlin Multiplatform (KMP). Define una estructura de módulos clara (app, core, domain, data, presentation, design-system y feature), estableciendo reglas de dependencia estrictas que garantizan que la capa de dominio permanezca puramente Kotlin y sin acoplamiento a frameworks. Incluye implementaciones detalladas de UseCases, Repositorios y DataSources, con ejemplos prácticos para Room, SQLDelight y Ktor. También cubre la inyección de dependencias con Koin y Hilt, el manejo de errores mediante Result/Try, y el uso de convenciones Gradle para reducir la duplicación en proyectos KMP. Es ideal para equipos que buscan mantener una separación de responsabilidades robusta, testeable y escalable en sus aplicaciones Android y multiplataforma.

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

Preguntar en tu IA favorita

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

Documentación

¿Qué hace 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 la capa de datos con Room, SQLDelight y Ktor.

Cuándo activarla

  • 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 la 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. Solo contiene Kotlin puro.

Capa de dominio

Patrón UseCase

Cada UseCase representa una operación de negocio. Usa operator fun invoke para invocaciones limpias:

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

// UseCase basado en Flow para flujos 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 frameworks:

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 junto a 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 propagar 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 al 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 configuració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")) }
    }
}

Aplica en los módulos:

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

Antipatrones a evitar

  • Importar clases del framework de 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 los 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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

api-connector-builder

Build a new API connector or provider by matching the target repo

Skills relacionados