
Android Clean Architecture
FreeImplement clean architecture patterns for Android and KMP projects.
Free · Opens the source repo
What Android Clean Architecture does
The Android Clean Architecture skill provides a structured approach to organizing Android and Kotlin Multiplatform (KMP) projects. It emphasizes the use of clean architecture principles, including modular structure, dependency inversion, and the implementation of UseCase and Repository patterns. This skill is designed for developers looking to create maintainable and scalable applications by adhering to best practices in software architecture.
The skill outlines a recommended project layout that separates concerns into distinct modules such as app, core, domain, data, and presentation. This modular approach allows developers to manage dependencies effectively, ensuring that the domain layer remains independent of any frameworks or external libraries. The skill also provides guidance on implementing data flow between layers, enabling a clear and efficient communication structure within the application.
Additionally, the skill covers the use of popular libraries such as Room, SQLDelight, and Ktor for data management and networking. It includes code examples for defining UseCases, repositories, and data sources, demonstrating how to implement these components in a way that promotes reusability and testability. By using this skill, developers can streamline their project setup and ensure that their applications are built on a solid architectural foundation.
Overall, this skill is ideal for Android developers and teams adopting Kotlin Multiplatform who want to follow clean architecture principles to enhance code quality and maintainability.
When to use it
Use this skill when starting a new Android or KMP project and you want to implement clean architecture from the ground up.
When not to use it
This skill may not be suitable for small projects or prototypes where a simpler architecture could suffice.
What you can build with it
New Android Project Setup
When starting a new Android project, this skill provides a clear framework for structuring your application using clean architecture principles.
Implementing Use Cases
Use this skill to define and implement Use Cases in your application, ensuring that business logic is cleanly separated from other layers.
Data Layer Design
Leverage this skill to design your data layer using Room or SQLDelight, facilitating smooth data management and retrieval.
How to install Android Clean Architecture
View source1. Install with the skills CLI
npx skills add affaan-m/ecc/android-clean-architecture --agent claude-code2. Or install it manually
Download the skill folder and drop it into ~/.claude/skills/ for all projects, or .claude/skills/ to scope it to one repo. Restart Claude Code so it picks up the new skill.
Anthropic's agentic coding CLI, and the reference implementation of Agent Skills. Drop a skill folder into ~/.claude/skills and Claude Code loads it automatically whenever a task matches the skill's description. Claude Code docs
Inside SKILL.md
Written by affaan-mAndroid クリーンアーキテクチャ
Android と KMP プロジェクトのクリーンアーキテクチャパターン。モジュール境界、依存関係の逆転、UseCase/Repository パターン、Room・SQLDelight・Ktor を使用したデータ層設計をカバーします。
起動タイミング
- Android または KMP プロジェクトモジュールの構造化
- UseCase、Repository、DataSource の実装
- 層間のデータフロー設計(ドメイン、データ、プレゼンテーション)
- Koin または Hilt による依存性注入のセットアップ
- 層状アーキテクチャでの Room、SQLDelight、Ktor の使用
モジュール構造
推奨レイアウト
project/
├── app/ # Android エントリポイント、DI ワイヤリング、Application クラス
├── core/ # 共有ユーティリティ、基底クラス、エラー型
├── domain/ # UseCase、ドメインモデル、リポジトリインターフェース(純粋 Kotlin)
├── data/ # リポジトリ実装、DataSource、DB、ネットワーク
├── presentation/ # スクリーン、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)
}
}
// リアクティブストリーム向けフローベースの 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 }
リポジトリインターフェース
ドメインで定義し、データで実装する:
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() }
}
}
}
マッパーパターン
マッパーはデータモデルの近くに拡張関数として保持する:
// データ層
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 フレンドリー)
// ドメインモジュール
val domainModule = module {
factory { GetItemsByCategoryUseCase(get()) }
factory { ObserveUserProgressUseCase(get()) }
}
// データモジュール
val dataModule = module {
single<ItemRepository> { ItemRepositoryImpl(get(), get()) }
single { ItemLocalDataSource(get()) }
single { ItemRemoteDataSource(get()) }
}
// プレゼンテーションモジュール
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 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) }
}
}
コンベンションプラグイン(Gradle)
KMP プロジェクトでは、ビルドファイルの重複を削減するためにコンベンションプラグインを使用する:
// 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 を参照。
Frequently asked questions about Android Clean Architecture
Similar skills
Python PyPI Package Builder
Streamline the process of creating and publishing Python packages.
Minecraft Plugin Development
Streamline your Minecraft server plugin creation.
MCP Server Builder
Easily build .NET MCP servers with the latest standards.
CommunityToolkit.Mvvm Messenger
Decoupled communication for ViewModels in .NET applications.
MVVM Toolkit DI
Streamline ViewModel integration with Dependency Injection in .NET.
MCP Apps Builder
Essential guidelines for MCP server development.
