Communitygithub.com

android-clean-architecture

Patrons d'architecture Clean Architecture pour les projets Android et Kotlin Multiplatform — structure des modules, règles de dépendances, UseCases, Repositories et patrons de la couche de données. À utiliser lors de la structuration des modules, des couches ou du flux de données dans un projet Android ou KMP.

Qu'est-ce que android-clean-architecture ?

Ce skill implémente les patrons Clean Architecture pour les projets Android et Kotlin Multiplatform (KMP). Il définit des règles strictes de séparation des couches (domaine, données, présentation) où le domaine ne dépend d'aucun framework et reste en Kotlin pur. Il fournit des exemples concrets pour les UseCases, les interfaces Repository, les implémentations de données avec Room, SQLDelight et Ktor, ainsi que les patterns de mapping et d'injection de dépendances avec Koin ou Hilt. Le skill détaille aussi la gestion d'erreurs via des sealed classes et l'utilisation de plugins de convention Gradle pour réduire la duplication dans les projets KMP. Il est activé lorsqu'il s'agit de structurer des modules Android ou KMP, d'implémenter des UseCases/Repositories, de concevoir le flux de données entre les couches, de configurer l'injection de dépendances, ou de travailler avec Room, SQLDelight ou Ktor dans une architecture en couches.

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

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Android Clean Architecture

Patrons d'architecture propre (Clean Architecture) pour les projets Android et KMP. Couvre les limites de modules, l'inversion de dépendances, les patrons UseCase/Repository et la conception de la couche de données avec Room, SQLDelight et Ktor.

Quand activer

  • Structurer des modules de projet Android ou KMP
  • Implémenter des UseCases, Repositories ou DataSources
  • Concevoir le flux de données entre les couches (domaine, données, présentation)
  • Mettre en place l'injection de dépendances avec Koin ou Hilt
  • Travailler avec Room, SQLDelight ou Ktor dans une architecture en couches

Structure des modules

Disposition recommandée

project/
├── app/                  # Point d'entrée Android, câblage DI, classe Application
├── core/                 # Utilitaires partagés, classes de base, types d'erreur
├── domain/               # UseCases, modèles du domaine, interfaces Repository (Kotlin pur)
├── data/                 # Implémentations Repository, DataSources, base de données, réseau
├── presentation/         # Écrans, ViewModels, modèles UI, navigation
├── design-system/        # Composants Compose réutilisables, thème, typographie
└── feature/              # Modules fonctionnalités (optionnel, pour les grands projets)
    ├── auth/
    ├── settings/
    └── profile/

Règles de dépendances

app → presentation, domain, data, core
presentation → domain, design-system, core
data → domain, core
domain → core (ou aucune dépendance)
core → (rien)

Critique : domain ne doit JAMAIS dépendre de data, presentation ou d'aucun framework. Il contient uniquement du Kotlin pur.

Couche Domaine

Patron UseCase

Chaque UseCase représente une opération métier. Utilisez operator fun invoke pour des appels propres :

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

// UseCase basé sur Flow pour les flux réactifs
class ObserveUserProgressUseCase(
    private val repository: UserRepository
) {
    operator fun invoke(userId: String): Flow<UserProgress> {
        return repository.observeProgress(userId)
    }
}

Modèles du domaine

Les modèles du domaine sont des classes de données Kotlin simples — sans annotations 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 Repository

Définies dans le domaine, implémentées dans la couche de données :

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

Couche de Données

Implémentation Repository

Coordonne les sources de données locales et distantes :

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

Patron Mapper

Gardez les mappers comme fonctions d'extension près des modèles de données :

// Dans la couche de données
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
)

Base de données Room (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;

Client réseau Ktor (KMP)

class ItemRemoteDataSource(private val client: HttpClient) {

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

// Configuration HttpClient avec ContentNegotiation
val httpClient = HttpClient {
    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
    install(Logging) { level = LogLevel.HEADERS }
    defaultRequest { url("https://api.example.com/") }
}

Injection de Dépendances

Koin (compatible KMP)

// Module domaine
val domainModule = module {
    factory { GetItemsByCategoryUseCase(get()) }
    factory { ObserveUserProgressUseCase(get()) }
}

// Module données
val dataModule = module {
    single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
    single { ItemLocalDataSource(get()) }
    single { ItemRemoteDataSource(get()) }
}

// Module présentation
val presentationModule = module {
    viewModelOf(::ItemListViewModel)
    viewModelOf(::DashboardViewModel)
}

Hilt (Android uniquement)

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

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

Gestion des Erreurs

Patron Result/Try

Utilisez Result<T> ou un type sealed personnalisé pour la propagation des erreurs :

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
}

// Dans le ViewModel — mapper vers l'état 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 Convention (Gradle)

Pour les projets KMP, utilisez des plugins de convention pour réduire la duplication des fichiers 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 { /* dépendances partagées */ }
        commonTest.dependencies { implementation(kotlin("test")) }
    }
}

Appliquez dans les modules :

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

Anti-patrons à éviter

  • Importer des classes du framework Android dans domain — gardez-le en Kotlin pur
  • Exposer les entités de base de données ou DTOs à la couche UI — mappez toujours vers des modèles du domaine
  • Mettre la logique métier dans les ViewModels — extrayez-la dans des UseCases
  • Utiliser GlobalScope ou des coroutines non structurées — utilisez viewModelScope ou la concurrence structurée
  • Implémentations Repository trop chargées — divisez en DataSources ciblées
  • Dépendances circulaires entre modules — si A dépend de B, B ne doit pas dépendre de A

Références

Voir le skill : compose-multiplatform-patterns pour les patrons UI. Voir le skill : kotlin-coroutines-flows pour les patrons asynchrones.

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 associés