Communitygithub.com

clean-architecture-android

Padrões de Arquitetura Limpa para projetos Android e Kotlin Multiplatform — estrutura de módulos, regras de dependência, UseCases, Repositórios e padrões da camada de dados. Ative ao estruturar módulos, camadas ou fluxo de dados em um projeto Android ou KMP.

O que é clean-architecture-android?

Esta skill fornece uma referência completa para implementar Arquitetura Limpa em projetos Android e Kotlin Multiplatform. Ela cobre a estruturação de módulos com regras de dependência claras, onde o módulo `domain` permanece puro Kotlin sem dependências de framework. Inclui padrões para UseCases, interfaces de Repositório e suas implementações na camada de dados, integrando bancos de dados locais como Room e SQLDelight, além de clientes de rede Ktor. A skill também demonstra como configurar injeção de dependência com Koin ou Hilt, mapeamento de entidades para modelos de domínio, tratamento de erros com tipos selados e uso de plugins de convenção Gradle para projetos KMP. É útil sempre que você precisar organizar módulos, definir fluxos de dados entre camadas ou evitar anti-padrões arquiteturais como lógica de negócios em ViewModels ou dependências circulares.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

Android Clean Architecture

Padrões de Arquitetura Limpa para projetos Android e KMP. Aborda limites de módulos, inversão de dependência, padrões UseCase/Repository e design da camada de dados com Room, SQLDelight e Ktor.

Quando Ativar

  • Estruturando módulos de projeto Android ou KMP
  • Implementando UseCases, Repositories ou DataSources
  • Projetando fluxo de dados entre camadas (domain, data, presentation)
  • Configurando injeção de dependência com Koin ou Hilt
  • Trabalhando com Room, SQLDelight ou Ktor em uma arquitetura em camadas

Estrutura de Módulos

Layout Recomendado

project/
├── app/                  # Ponto de entrada Android, fiação DI, classe Application
├── core/                 # Utilitários compartilhados, classes base, tipos de erro
├── domain/               # UseCases, modelos de domínio, interfaces de repositório (Kotlin puro)
├── data/                 # Implementações de repositório, DataSources, BD, rede
├── presentation/         # Telas, ViewModels, modelos de UI, navegação
├── design-system/        # Componentes Compose reutilizáveis, tema, tipografia
└── feature/              # Módulos de funcionalidade (opcional, para projetos maiores)
    ├── auth/
    ├── settings/
    └── profile/

Regras de Dependência

app → presentation, domain, data, core
presentation → domain, design-system, core
data → domain, core
domain → core (ou sem dependências)
core → (nada)

Crítico: domain NUNCA deve depender de data, presentation ou qualquer framework. Contém apenas Kotlin puro.

Camada de Domínio

Padrão UseCase

Cada UseCase representa uma operação de negócio. Use operator fun invoke para chamadas limpas:

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

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

Modelos de Domínio

Modelos de domínio são classes de dados Kotlin puras — sem anotações 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 Repositório

Definidas no domínio, implementadas em data:

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

Camada de Dados

Implementação do Repositório

Coordena entre fontes de dados locais e 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() }
        }
    }
}

Padrão Mapper

Mantenha mappers como funções de extensão próximas aos modelos de dados:

// Na camada de dados
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 Rede Ktor (KMP)

class ItemRemoteDataSource(private val client: HttpClient) {

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

// Configuração do HttpClient com negociação de conteúdo
val httpClient = HttpClient {
    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
    install(Logging) { level = LogLevel.HEADERS }
    defaultRequest { url("https://api.example.com/") }
}

Injeção de Dependência

Koin (compatível com KMP)

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

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

// Módulo de apresentação
val presentationModule = module {
    viewModelOf(::ItemListViewModel)
    viewModelOf(::DashboardViewModel)
}

Hilt (apenas 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()

Tratamento de Erros

Padrão Result/Try

Use Result<T> ou um tipo selado personalizado para propagação de erros:

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
}

// No ViewModel — mapear para estado da 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 Convenção (Gradle)

Para projetos KMP, use plugins de convenção para reduzir a duplicação de arquivos de build:

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

kotlin {
    androidTarget()
    iosX64(); iosArm64(); iosSimulatorArm64()
    sourceSets {
        commonMain.dependencies { /* dependências compartilhadas */ }
        commonTest.dependencies { implementation(kotlin("test")) }
    }
}

Aplique nos módulos:

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

Anti-Padrões a Evitar

  • Importar classes do framework Android em domain — mantenha Kotlin puro
  • Expor entidades de banco de dados ou DTOs para a camada de UI — sempre mapeie para modelos de domínio
  • Colocar lógica de negócios em ViewModels — extraia para UseCases
  • Usar GlobalScope ou corrotinas não estruturadas — use viewModelScope ou concorrência estruturada
  • Implementações de repositório inchadas — divida em DataSources focados
  • Dependências circulares entre módulos — se A depende de B, B não deve depender de A

Referências

Veja a skill: compose-multiplatform-patterns para padrões de UI. Veja a skill: kotlin-coroutines-flows para padrões assí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

Habilidades Relacionadas