Communitygithub.com

Android Clean Architecture

Android および Kotlin Multiplatform プロジェクト向けのクリーンアーキテクチャパターン。モジュール構成、依存関係ルール、ユースケース、リポジトリ、データ層パターンを提供します。Android または KMP プロジェクトでモジュール、レイヤー、データフローを構築する際に使用します。

Android Clean Architecture とは?

このスキルは、Android および Kotlin Multiplatform (KMP) プロジェクトにおいて Clean Architecture を実践するための包括的なパターンを提供します。モジュール構成や依存関係のルール、ユースケースとリポジトリパターン、データ層 (Room、SQLDelight、Ktor) の設計など、プロジェクト構造のエバンジェリズムに必要なガイドラインを網羅しています。特に `domain` 層を純粋な Kotlin に保ち、`data` 層や `presentation` 層への依存を禁止する「依存ルール」は最も重要な原則です。また Koin や Hilt による依存性注入、エラーハンドリング (Result/Try パターン)、Gradle Convention Plugin によるビルド重複削減など、実践的なコード例とベストプラクティスを提供します。Android 単体でも KMP でも適用可能で、UI パターンや非同期処理に関する関連スキルも参照できます。

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

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

Android Clean Architecture は何をしますか?

Android および KMP プロジェクト向けの Clean Architecture パターン。モジュール境界、依存関係逆転、UseCase/Repository パターン、Room、SQLDelight、Ktor を用いたデータ層設計をカバーします。

アクティブにするタイミング

  • Android または KMP プロジェクトのモジュールを構築するとき
  • UseCase、Repository、DataSource を実装するとき
  • レイヤー間(domain、data、presentation)のデータフローを設計するとき
  • Koin または Hilt で依存性注入を設定するとき
  • レイヤードアーキテクチャで Room、SQLDelight、または Ktor を使用するとき

モジュール構造

推奨レイアウト

project/
├── app/                  # Android エントリポイント、DI 配線、Application クラス
├── core/                 # 共有ユーティリティ、基底クラス、エラー型
├── domain/               # UseCase、ドメインモデル、リポジトリインターフェース(純粋 Kotlin)
├── data/                 # リポジトリ実装、DataSource、DB、ネットワーク
├── presentation/         # Screen、ViewModel、UI モデル、ナビゲーション
├── design-system/        # 再利用可能な Compose コンポーネント、テーマ、タイポグラフィ
└── feature/              # 機能モジュール(大規模プロジェクトでは任意)
    ├── auth/
    ├── settings/
    └── profile/

依存関係ルール

app → presentation, domain, data, core
presentation → domain, design-system, core
data → domain, core
domain → core(または依存なし)
core →(依存なし)

重要: domain は data、presentation、またはいかなるフレームワークにも依存してはなりません。純粋な Kotlin のみで構成します。

ドメイン層

UseCase パターン

各 UseCase は 1 つのビジネス操作を表します。operator fun invoke を使用してクリーンな呼び出しサイトを実現します:

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

// Flow ベースの UseCase(リアクティブストリーム用)
class ObserveUserProgressUseCase(
    private val repository: UserRepository
) {
    operator fun invoke(userId: String): Flow<UserProgress> {
        return repository.observeProgress(userId)
    }
}

ドメインモデル

ドメインモデルはプレーンな Kotlin データクラスです(フレームワークのアノテーションは付けません):

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 }

リポジトリインターフェース

domain で定義し、data で実装します:

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

データ層

リポジトリ実装

ローカルデータソースとリモートデータソースを調整します:

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

マッパーパターン

マッパーはデータモデル近くの拡張関数として保持します:

// data レイヤー内
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 データベース(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;

Ktor ネットワーククライアント(KMP)

class ItemRemoteDataSource(private val client: HttpClient) {

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

// HttpClient のセットアップ(コンテンツネゴシエーション付き)
val httpClient = HttpClient {
    install(ContentNegotiation) { json(Json { ignoreUnknownKeys = true }) }
    install(Logging) { level = LogLevel.HEADERS }
    defaultRequest { url("https://api.example.com/") }
}

依存性注入

Koin(KMP フレンドリー)

// Domain モジュール
val domainModule = module {
    factory { GetItemsByCategoryUseCase(get()) }
    factory { ObserveUserProgressUseCase(get()) }
}

// Data モジュール
val dataModule = module {
    single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
    single { ItemLocalDataSource(get()) }
    single { ItemRemoteDataSource(get()) }
}

// Presentation モジュール
val presentationModule = module {
    viewModelOf(::ItemListViewModel)
    viewModelOf(::DashboardViewModel)
}

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

エラーハンドリング

Result/Try パターン

エラー伝播には Result<T> またはカスタム sealed 型を使用します:

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
}

// ViewModel 内 — 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) }
    }
}

Convention Plugin(Gradle)

KMP プロジェクトでは Convention Plugin を使用してビルドファイルの重複を削減します:

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

kotlin {
    androidTarget()
    iosX64(); iosArm64(); iosSimulatorArm64()
    sourceSets {
        commonMain.dependencies { /* 共有依存関係 */ }
        commonTest.dependencies { implementation(kotlin("test")) }
    }
}

モジュールで適用:

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

避けるべきアンチパターン

  • domain に Android フレームワーククラスをインポートする — 純粋 Kotlin を維持すること
  • データベースエンティティや DTO を UI 層に公開する — 必ずドメインモデルにマッピングすること
  • ビジネスロジックを ViewModel に置く — UseCase に抽出すること
  • GlobalScope や非構造化コルーチンを使用する — viewModelScope または構造化された並行性を使用すること
  • リポジトリ実装を肥大化させる — フォーカスした DataSource に分割すること
  • 循環モジュール依存 — A が B に依存するなら、B は A に依存してはならない

参考

UI パターンについてはスキル compose-multiplatform-patterns を参照。 非同期パターンについてはスキル kotlin-coroutines-flows を参照。

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.

関連スキル