Mode d’emploi
- Copiez la commande et examinez le paquet avant de l’installer dans votre environnement agent.
- Consultez le document original pour les outils, identifiants et environnements requis.
- Décrivez les entrées et le livrable souhaité ; comparez la sortie aux sources.
npx skills add https://github.com/rcosteira79/android-skills --skill android-data-layerAvant de commencer
- L’installation n’inclut ni abonnement au modèle, ni quota API, ni accès aux fichiers.
- Étoiles et installations reflètent l’activité, pas un test indépendant de qualité ou de sécurité.
Rendu depuis le dépôt source en conservant titres, exemples, code, tableaux, liens et images.
Android Data Layer
The data layer coordinates data from multiple sources; its public API is repository interfaces, and its internals (DAOs, API services, DTOs) never leak upward. This skill is the canonical home for the error-propagation model, plus the KMP-Room setup. Related: android-skills:android-retrofit (Retrofit service/OkHttp/Hilt wiring), android-skills:android-dev (overall app architecture).
Error propagation — the layered model
The goal: data-layer errors (IOException / HttpException / SQLiteException) must never reach the ViewModel. Map them to a domain error type at the repository boundary. If the project already has an error convention, match it; otherwise a sealed DataError hierarchy is a reasonable default:
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())) }
}The boundary moves outward by one layer when a domain layer exists:
- Data sources throw platform/library exceptions (
IOException,HttpException,SQLiteException). - Repository is the error boundary — catch those and remap to
DataError; never let raw platform types leak. Without a domain layer (simple MVVM): the repository returnsResult<T>directly and the ViewModel handles it without knowing about platform exceptions. - Use cases (when present) are the
Resultboundary — the repository instead throwsDataError, and the use case catches it and returnsResult<T>with a domain-specific error model. Use cases never catch platform exception types. - ViewModels handle
Result<T>and map it to UI state (explicit loading / success / error).
(Default to the Entity suffix — ArticleEntity — for Room types; match an existing convention such as a Cached prefix if the project already uses one.)
Room in KMP (commonMain)
Room has been KMP-stable since 2.7.0. The shared setup differs from the Android-only setup in three places:
- `@ConstructedBy(...)` on the
@Database, paired with anexpect objectRoom generates per-platformactuals for. - `BundledSQLiteDriver` (from
androidx.sqlite:sqlite-bundled) — pins the same SQLite version across Android/iOS/JVM/web (Android's system SQLite drifts between API levels and devices). - `setQueryCoroutineContext(Dispatchers.IO)` — Android Room defaults this; KMP doesn't.
// 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()Each platform provides its own builder — Android Room.databaseBuilder(context, AppDatabase::class.java, "app.db"); iOS/JVM Room.databaseBuilder<AppDatabase>(databasePath = …). KSP must be wired per target (ksp(libs.androidx.room.compiler) is Android-only):
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 trackingFor pure-Android projects, skip @ConstructedBy and call Room.databaseBuilder(context, AppDatabase::class.java, "app.db") directly — Room behaves identically; the KMP setup is opt-in when the data layer must live in commonMain.



