android-data-layer について
Android 開発者向けのデータレイヤースキルで、リポジトリ、エラー伝播、Room 永続化の構成を扱います。プラットフォーム例外を変換する層と、Kotlin Multiplatform で Room を設定する方法を説明します。
主な機能
- 01リポジトリ境界の設計
- 02層ごとのエラーマッピング
- 03アーキテクチャ層別の Result 処理
- 04KMP Room の設定指針
- 05ターゲット別 KSP 設定
活用シーン
- 01プラットフォーム例外の ViewModel への漏出防止
- 02リポジトリとユースケースのエラー境界決定
- 03KMP での共有 Room データベース設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ rcosteira79/ android-skills --skill android-data-layerインストール後、Claude Code で/android-data-layerを入力して読み込まれたか確認します。
詳細
Android データレイヤー
データレイヤーは複数のソースからのデータを調整します。公開 API はリポジトリインターフェースであり、内部の要素(DAO、API サービス、DTO)が上位レイヤーに漏れることはありません。このスキルは、エラー伝播モデルと KMP-Room のセットアップに関する標準の参照先です。関連: android-skills:android-retrofit(Retrofit サービス/OkHttp/Hilt の配線)、android-skills:android-dev(アプリ全体のアーキテクチャ)。
エラー伝播 — レイヤー化モデル
目標は、データレイヤーのエラー(IOException/HttpException/SQLiteException)を ViewModel に決して到達させないことです。リポジトリの境界で、これらをドメインエラー型にマッピングします。プロジェクトに既存のエラー規約がある場合はそれに合わせ、ない場合は sealed な DataError 階層が妥当なデフォルトです。
sealed class DataError(message: String, cause: Throwable? = null) : Exception(message, cause) {
class Network(cause: Throwable) : DataError("Network error", cause)
class Server(val code: Int, message: String?) : DataError("Server error $code: $message")
class Local(cause: Throwable) : DataError("Local storage error", cause)
}
// repository = error boundary: catch platform exceptions, return Result<T> (no domain layer) or throw DataError (with one)
suspend fun refreshNews(): Result<Unit> = withContext(io) {
try { newsDao.insertAll(newsApi.fetchLatest().map(NewsDto::toEntity)); Result.success(Unit) }
catch (e: IOException) { Result.failure(DataError.Network(e)) }
catch (e: HttpException) { Result.failure(DataError.Server(e.code(), e.message())) }
}
ドメインレイヤーがある場合、境界は1つ外側のレイヤーに移ります。
- データソースは、プラットフォーム/ライブラリの例外(
IOException、HttpException、SQLiteException)をスローします。 - リポジトリはエラー境界です。これらをキャッチして
DataErrorにマッピングし、生のプラットフォーム型を決して漏らしません。ドメインレイヤーがない場合(シンプルな MVVM): リポジトリはResult<T>を直接返し、ViewModel はプラットフォーム例外を知らずに処理します。 - ユースケース(存在する場合)は
Resultの境界です。リポジトリは代わりにDataErrorを スロー し、ユースケースがそれをキャッチして、ドメイン固有のエラーモデルを含むResult<T>を返します。ユースケースはプラットフォーム例外型を決してキャッチしません。 - ViewModelは
Result<T>を処理し、UI 状態(明示的な読み込み/成功/エラー)にマッピングします。
(Room の型には Entity 接尾辞(ArticleEntity)をデフォルトとして使用します。プロジェクトですでに Cached 接頭辞などの規約が使われている場合は、それに合わせます。)
KMP の Room(commonMain)
Room は 2.7.0 以降、KMP で安定しています。共有セットアップは、Android 専用のセットアップと次の3点で異なります。
@Databaseに付ける@ConstructedBy(...)と、Room がプラットフォームごとのactualを生成する対象となるexpect objectを組み合わせます。BundledSQLiteDriver(androidx.sqlite:sqlite-bundled由来)を使い、Android/iOS/JVM/web で同じ SQLite バージョンに固定します(Android のシステム SQLite のバージョンは API レベルやデバイスによって異なります)。setQueryCoroutineContext(Dispatchers.IO)を指定します。Android の Room ではこれがデフォルトですが、KMP ではデフォルトではありません。
// commonMain
@Database(entities = [ArticleEntity::class], version = 1, exportSchema = true)
@ConstructedBy(AppDatabaseConstructor::class)
abstract class AppDatabase : RoomDatabase() { abstract fun articleDao(): ArticleDao }
@Suppress("KotlinNoActualForExpect")
expect object AppDatabaseConstructor : RoomDatabaseConstructor<AppDatabase>
fun getRoomDatabase(builder: RoomDatabase.Builder<AppDatabase>): AppDatabase =
builder.setDriver(BundledSQLiteDriver()).setQueryCoroutineContext(Dispatchers.IO).build()
各プラットフォームで独自のビルダーを用意します。Android では Room.databaseBuilder(context, AppDatabase::class.java, "app.db")、iOS/JVM では Room.databaseBuilder<AppDatabase>(databasePath = …) を使用します。KSP はターゲットごとに設定する必要があります(ksp(libs.androidx.room.compiler) は Android 専用です)。
dependencies {
add("kspAndroid", libs.androidx.room.compiler)
add("kspIosArm64", libs.androidx.room.compiler)
add("kspIosSimulatorArm64", libs.androidx.room.compiler)
// … one per target
}
room { schemaDirectory("$projectDir/schemas") } // export the schema for migration-history tracking
Android 専用プロジェクトでは、@ConstructedBy を省略し、Room.databaseBuilder(context, AppDatabase::class.java, "app.db") を直接呼び出します。Room の動作は同じです。データレイヤーを commonMain に配置する必要がある場合に、KMP セットアップを任意で選択します。