Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Expert skill for Android Room persistence library
.claude/skills/a5c-ai-room-database/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-16 | ✗→✓ | ▲ Improved | 403% | 0% |
| case-17 | ✗→✓ | ▲ Improved | 390% | 0% |
| case-23 | ✗→✓ | ▲ Improved | 390% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 340% | 0% |
| case-02 | ✓→✓ | = Same ✓ | 272% | 0% |
This skill provides expert capabilities for Android Room persistence library. It enables designing database schemas, implementing DAOs, configuring migrations, and integrating with modern Android architecture components.
bash - Execute Gradle commands and Android build toolsread - Analyze Room entities and DAO fileswrite - Generate Room database componentsedit - Update existing Room configurationsglob - Search for database-related filesgrep - Search for patterns in database codeThis skill integrates with the following processes:
android-room-database.js - Room implementationoffline-first-architecture.js - Offline data strategiesmobile-security-implementation.js - Secure data storagekotlin// build.gradle.kts (app) plugins { id("com.google.devtools.ksp") } dependencies { implementation(libs.room.runtime) implementation(libs.room.ktx) ksp(libs.room.compiler) // Optional - Paging 3 Integration implementation(libs.room.paging) // Testing testImplementation(libs.room.testing) }
toml# gradle/libs.versions.toml [versions] room = "2.6.1" [libraries] room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" } room-paging = { group = "androidx.room", name = "room-paging", version.ref = "room" } room-testing = { group = "androidx.room", name = "room-testing", version.ref = "room" }
kotlin// data/local/entity/UserEntity.kt package com.example.app.data.local.entity import androidx.room.* @Entity( tableName = "users", indices = [ Index(value = ["email"], unique = true), Index(value = ["created_at"]) ] ) data class UserEntity( @PrimaryKey @ColumnInfo(name = "id") val id: String, @ColumnInfo(name = "email") val email: String, @ColumnInfo(name = "display_name") val displayName: String, @ColumnInfo(name = "avatar_url") val avatarUrl: String?, @ColumnInfo(name = "created_at") val createdAt: Long, @ColumnInfo(name = "updated_at") val updatedAt: Long, @Embedded(prefix = "settings_") val settings: UserSettings ) data class UserSettings( @ColumnInfo(name = "notifications_enabled") val notificationsEnabled: Boolean = true, @ColumnInfo(name = "theme") val theme: String = "system" )
kotlin// data/local/entity/PostEntity.kt package com.example.app.data.local.entity import androidx.room.* @Entity( tableName = "posts", foreignKeys = [ ForeignKey( entity = UserEntity::class, parentColumns = ["id"], childColumns = ["author_id"], onDelete = ForeignKey.CASCADE ) ], indices = [Index(value = ["author_id"])] ) data class PostEntity( @PrimaryKey @ColumnInfo(name = "id") val id: String, @ColumnInfo(name = "author_id") val authorId: String, @ColumnInfo(name = "title") val title: String, @ColumnInfo(name = "content") val content: String, @ColumnInfo(name = "published_at") val publishedAt: Long?, @ColumnInfo(name = "is_draft") val isDraft: Boolean = true ) // Relation class for queries data class PostWithAuthor( @Embedded val post: PostEntity, @Relation( parentColumn = "author_id", entityColumn = "id" ) val author: UserEntity )
kotlin// data/local/converter/Converters.kt package com.example.app.data.local.converter import androidx.room.TypeConverter import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId class Converters { @TypeConverter fun fromTimestamp(value: Long?): LocalDateTime? { return value?.let { LocalDateTime.ofInstant(Instant.ofEpochMilli(it), ZoneId.systemDefault()) } } @TypeConverter fun toTimestamp(date: LocalDateTime?): Long? { return date?.atZone(ZoneId.systemDefault())?.toInstant()?.toEpochMilli() } @TypeConverter fun fromStringList(value: List<String>?): String? { return value?.joinToString(",") } @TypeConverter fun toStringList(value: String?): List<String>? { return value?.split(",")?.map { it.trim() } } }
kotlin// data/local/dao/UserDao.kt package com.example.app.data.local.dao import androidx.room.* import kotlinx.coroutines.flow.Flow @Dao interface UserDao { @Query("SELECT * FROM users ORDER BY display_name ASC") fun observeAllUsers(): Flow<List<UserEntity>> @Query("SELECT * FROM users WHERE id = :userId") fun observeUserById(userId: String): Flow<UserEntity?> @Query("SELECT * FROM users WHERE id = :userId") suspend fun getUserById(userId: String): UserEntity? @Query("SELECT * FROM users WHERE email = :email LIMIT 1") suspend fun getUserByEmail(email: String): UserEntity? @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUser(user: UserEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertUsers(users: List<UserEntity>) @Update suspend fun updateUser(user: UserEntity) @Delete suspend fun deleteUser(user: UserEntity) @Query("DELETE FROM users WHERE id = :userId") suspend fun deleteUserById(userId: String) @Query("DELETE FROM users") suspend fun deleteAllUsers() @Transaction suspend fun replaceAllUsers(users: List<UserEntity>) { deleteAllUsers() insertUsers(users) } }
kotlin// data/local/dao/PostDao.kt package com.example.app.data.local.dao import androidx.room.* import androidx.paging.PagingSource import kotlinx.coroutines.flow.Flow @Dao interface PostDao { @Transaction @Query("SELECT * FROM posts WHERE is_draft = 0 ORDER BY published_at DESC") fun observePublishedPostsWithAuthor(): Flow<List<PostWithAuthor>> @Transaction @Query("SELECT * FROM posts WHERE is_draft = 0 ORDER BY published_at DESC") fun getPublishedPostsPagingSource(): PagingSource<Int, PostWithAuthor> @Transaction @Query("SELECT * FROM posts WHERE id = :postId") suspend fun getPostWithAuthor(postId: String): PostWithAuthor? @Query("SELECT * FROM posts WHERE author_id = :authorId ORDER BY published_at DESC") fun observePostsByAuthor(authorId: String): Flow<List<PostEntity>> @Query(""" SELECT * FROM posts WHERE title LIKE '%' || :query || '%' OR content LIKE '%' || :query || '%' ORDER BY published_at DESC """) suspend fun searchPosts(query: String): List<PostEntity> @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertPost(post: PostEntity) @Insert(onConflict = OnConflictStrategy.REPLACE) suspend fun insertPosts(posts: List<PostEntity>) @Update suspend fun updatePost(post: PostEntity) @Query("UPDATE posts SET is_draft = :isDraft WHERE id = :postId") suspend fun updateDraftStatus(postId: String, isDraft: Boolean) @Delete suspend fun deletePost(post: PostEntity) }
kotlin// data/local/AppDatabase.kt package com.example.app.data.local import androidx.room.* import com.example.app.data.local.converter.Converters import com.example.app.data.local.dao.PostDao import com.example.app.data.local.dao.UserDao import com.example.app.data.local.entity.PostEntity import com.example.app.data.local.entity.UserEntity @Database( entities = [ UserEntity::class, PostEntity::class ], version = 2, autoMigrations = [ AutoMigration(from = 1, to = 2) ], exportSchema = true ) @TypeConverters(Converters::class) abstract class AppDatabase : RoomDatabase() { abstract fun userDao(): UserDao abstract fun postDao(): PostDao }
kotlin// di/DatabaseModule.kt package com.example.app.di import android.content.Context import androidx.room.Room import com.example.app.data.local.AppDatabase import com.example.app.data.local.dao.PostDao import com.example.app.data.local.dao.UserDao import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) object DatabaseModule { @Provides @Singleton fun provideAppDatabase( @ApplicationContext context: Context ): AppDatabase { return Room.databaseBuilder( context, AppDatabase::class.java, "app_database" ) .fallbackToDestructiveMigration() .build() } @Provides fun provideUserDao(database: AppDatabase): UserDao = database.userDao() @Provides fun providePostDao(database: AppDatabase): PostDao = database.postDao() }
kotlin// data/repository/UserRepositoryImpl.kt package com.example.app.data.repository import com.example.app.data.local.dao.UserDao import com.example.app.data.local.entity.UserEntity import com.example.app.data.remote.api.UserApi import com.example.app.domain.model.User import com.example.app.domain.repository.UserRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import javax.inject.Inject class UserRepositoryImpl @Inject constructor( private val userDao: UserDao, private val userApi: UserApi ) : UserRepository { override fun observeUsers(): Flow<List<User>> { return userDao.observeAllUsers().map { entities -> entities.map { it.toDomain() } } } override fun observeUser(userId: String): Flow<User?> { return userDao.observeUserById(userId).map { it?.toDomain() } } override suspend fun refreshUsers() { val remoteUsers = userApi.getUsers() val entities = remoteUsers.map { it.toEntity() } userDao.replaceAllUsers(entities) } override suspend fun getUser(userId: String): User? { return userDao.getUserById(userId)?.toDomain() } } // Extension functions for mapping private fun UserEntity.toDomain() = User( id = id, email = email, displayName = displayName, avatarUrl = avatarUrl )
kotlin-compose - Android UI developmentoffline-storage - Cross-platform patternsmobile-security - Encrypted databases| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 5,483 | 11,481 | +109% | 1 | 1 | 0% | 1,081 | 4,752 | +340% | 0 | 0 | — |
case-02 | pass→pass | 6,079 | 8,118 | +34% | 1 | 1 | 0% | 1,279 | 4,759 | +272% | 0 | 0 | — |
case-03 | pass→pass | 9,731 | 5,851 | -40% | 1 | 1 | 0% | 1,445 | 4,582 | +217% | 0 | 0 | — |
case-04 | pass→pass | 10,079 | 6,438 | -36% | 1 | 1 | 0% | 1,574 | 4,454 | +183% | 0 | 0 | — |
case-05 | fail→fail | 11,293 | 12,119 | +7% | 1 | 1 | 0% | 1,688 | 5,295 | +214% | 0 | 0 | — |
case-06 | pass→pass | 6,418 | 7,400 | +15% | 1 | 1 | 0% | 995 | 4,979 | +400% | 0 | 0 | — |
case-07 | pass→pass | 6,667 | 6,076 | -9% | 1 | 1 | 0% | 1,348 | 4,662 | +246% | 0 | 0 | — |
case-08 | pass→pass | 9,552 | 5,978 | -37% | 1 | 1 | 0% | 1,568 | 4,616 | +194% | 0 | 0 | — |
case-09 | pass→pass | 5,821 | 6,082 | +4% | 1 | 1 | 0% | 994 | 4,585 | +361% | 0 | 0 | — |
case-10 | pass→pass | 5,711 | 6,741 | +18% | 1 | 1 | 0% | 1,105 | 4,544 | +311% | 0 | 0 | — |
case-11 | pass→pass | 10,198 | 6,997 | -31% | 1 | 1 | 0% | 1,507 | 4,553 | +202% | 0 | 0 | — |
case-12 | pass→pass | 7,679 | 5,524 | -28% | 1 | 1 | 0% | 1,198 | 4,533 | +278% | 0 | 0 | — |
case-13 | pass→pass | 9,272 | 7,220 | -22% | 1 | 1 | 0% | 1,261 | 4,339 | +244% | 0 | 0 | — |
case-14 | pass→pass | 7,672 | 8,242 | +7% | 1 | 1 | 0% | 1,510 | 5,166 | +242% | 0 | 0 | — |
case-15 | pass→pass | 7,505 | 6,010 | -20% | 1 | 1 | 0% | 1,288 | 4,650 | +261% | 0 | 0 | — |
case-16 | fail→pass | 4,301 | 5,394 | +25% | 1 | 1 | 0% | 852 | 4,287 | +403% | 0 | 0 | — |
case-17 | fail→pass | 5,934 | 3,171 | -47% | 1 | 1 | 0% | 826 | 4,047 | +390% | 0 | 0 | — |
case-18 | pass→pass | 8,272 | 5,255 | -36% | 1 | 1 | 0% | 1,279 | 4,568 | +257% | 0 | 0 | — |
case-19 | pass→pass | 6,243 | 6,613 | +6% | 1 | 1 | 0% | 1,046 | 4,569 | +337% | 0 | 0 | — |
case-20 | pass→pass | 8,253 | 9,663 | +17% | 1 | 1 | 0% | 1,509 | 4,972 | +229% | 0 | 0 | — |
case-21 | pass→pass | 15,848 | 16,813 | +6% | 1 | 1 | 0% | 2,458 | 6,261 | +155% | 0 | 0 | — |
case-22 | pass→pass | 14,983 | 18,891 | +26% | 1 | 1 | 0% | 2,291 | 5,863 | +156% | 0 | 0 | — |
case-23 | fail→pass | 4,545 | 5,492 | +21% | 1 | 1 | 0% | 900 | 4,410 | +390% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +13 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.