
Room Patterns
- 19 installs
- 60 repo stars
- Updated June 14, 2026
- ahmed3elshaer/everything-claude-code-mobile
room-patterns is a Claude Code skill that provides Room database patterns for Android including entities, DAO interfaces, the Database class, migrations, TypeConverters, and Flow integration.
About
room-patterns is a Claude Code skill that documents Room database patterns for Android. It covers entity definitions with indices, DAO interfaces with Flow and suspend queries, the Database class, TypeConverters, embedded objects, relations, and schema migrations. Developers use it when persisting structured data locally in an Android app. Examples use KSP and Room 2.6.
- Room entity, DAO, and Database class definitions with KSP
- TypeConverters, embedded objects, and @Relation queries
- Migration strategy with versioned Migration objects and Flow integration
Room Patterns by the numbers
- 19 all-time installs (skills.sh)
- Ranked #561 of 911 Databases skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
room-patterns capabilities & compatibility
- Capabilities
- local database · orm · schema migration
- Use cases
- database
What room-patterns says it does
# Room Database Patterns
@Query("SELECT * FROM users ORDER BY created_at DESC")
npx skills add https://github.com/ahmed3elshaer/everything-claude-code-mobile --skill room-patternsAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 60 |
| Last updated | June 14, 2026 |
| Repository | ahmed3elshaer/everything-claude-code-mobile ↗ |
What it does
Persist local structured data in Android with Room entities, DAOs, TypeConverters, relations, and versioned migrations.
Who is it for?
Defining a local Room database schema, DAOs, and migrations in an Android app
Skip if: Server-side databases or non-Room local storage
When should I use this skill?
Adding local persistence, DAO queries, or schema migrations to an Android app
What you get
The app has a versioned Room schema with typed DAOs, converters, relations, and reactive Flow queries
By the numbers
- Example database at version 2 with a MIGRATION_1_2
- Covers entities, DAOs, converters, relations, and migrations
Files
Room Database Patterns
Dependencies (build.gradle.kts)
plugins {
id("com.google.devtools.ksp") version "2.0.21-1.0.27"
}
dependencies {
val roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
ksp("androidx.room:room-compiler:$roomVersion")
testImplementation("androidx.room:room-testing:$roomVersion")
}Entity Definitions
@Entity(
tableName = "users",
indices = [
Index(value = ["email"], unique = true),
Index(value = ["created_at"])
]
)
data class UserEntity(
@PrimaryKey(autoGenerate = true)
val id: Long = 0,
@ColumnInfo(name = "display_name")
val displayName: String,
@ColumnInfo(name = "email")
val email: String,
@ColumnInfo(name = "avatar_url")
val avatarUrl: String? = null,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis(),
@Ignore
val isOnline: Boolean = false
)Embedded Objects
data class Address(
val street: String,
val city: String,
@ColumnInfo(name = "zip_code") val zipCode: String
)
@Entity(tableName = "profiles")
data class ProfileEntity(
@PrimaryKey val userId: Long,
@Embedded(prefix = "address_") val address: Address
)Relations
data class UserWithPosts(
@Embedded val user: UserEntity,
@Relation(
parentColumn = "id",
entityColumn = "author_id"
)
val posts: List<PostEntity>
)DAO Interfaces
@Dao
interface UserDao {
@Query("SELECT * FROM users ORDER BY created_at DESC")
fun observeAll(): Flow<List<UserEntity>>
@Query("SELECT * FROM users WHERE id = :userId")
fun observeById(userId: Long): Flow<UserEntity?>
@Query("SELECT * FROM users WHERE email = :email LIMIT 1")
suspend fun findByEmail(email: String): UserEntity?
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsert(user: UserEntity): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
suspend fun insertIfNotExists(users: List<UserEntity>): List<Long>
@Update
suspend fun update(user: UserEntity)
@Delete
suspend fun delete(user: UserEntity)
@Query("DELETE FROM users WHERE id = :userId")
suspend fun deleteById(userId: Long)
@Transaction
@Query("SELECT * FROM users WHERE id = :userId")
fun observeUserWithPosts(userId: Long): Flow<UserWithPosts?>
@Transaction
suspend fun replaceAll(users: List<UserEntity>) {
deleteAll()
insertIfNotExists(users)
}
@Query("DELETE FROM users")
suspend fun deleteAll()
}TypeConverters
class Converters {
@TypeConverter
fun fromDate(date: Date?): Long? = date?.time
@TypeConverter
fun toDate(timestamp: Long?): Date? = timestamp?.let { Date(it) }
@TypeConverter
fun fromStatus(status: UserStatus): String = status.name
@TypeConverter
fun toStatus(value: String): UserStatus = UserStatus.valueOf(value)
@TypeConverter
fun fromStringList(list: List<String>): String =
Json.encodeToString(list)
@TypeConverter
fun toStringList(value: String): List<String> =
Json.decodeFromString(value)
}Database Class
@Database(
entities = [
UserEntity::class,
PostEntity::class,
ProfileEntity::class
],
version = 2,
exportSchema = true
)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
abstract fun postDao(): PostDao
}Migration Strategy
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE users ADD COLUMN avatar_url TEXT")
db.execSQL(
"CREATE INDEX IF NOT EXISTS index_users_created_at ON users(created_at)"
)
}
}
// Destructive fallback for development
val database = Room.databaseBuilder(context, AppDatabase::class.java, "app.db")
.addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration() // only in debug builds
.build()Room + Koin Injection
val databaseModule = module {
single {
Room.databaseBuilder(
androidContext(),
AppDatabase::class.java,
"app.db"
)
.addMigrations(MIGRATION_1_2)
.build()
}
single { get<AppDatabase>().userDao() }
single { get<AppDatabase>().postDao() }
}Room + Kotlin Flow Integration
class UserRepository(private val userDao: UserDao) {
val allUsers: Flow<List<UserEntity>> = userDao.observeAll()
fun observeUser(id: Long): Flow<UserEntity?> = userDao.observeById(id)
suspend fun addUser(user: UserEntity): Long = userDao.upsert(user)
suspend fun removeUser(userId: Long) = userDao.deleteById(userId)
}
// In ViewModel
class UserListViewModel(
private val repository: UserRepository
) : ViewModel() {
val users: StateFlow<List<UserEntity>> = repository.allUsers
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
}Testing with In-Memory Database
@RunWith(AndroidJUnit4::class)
class UserDaoTest {
private lateinit var database: AppDatabase
private lateinit var userDao: UserDao
@Before
fun setup() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
AppDatabase::class.java
)
.allowMainThreadQueries()
.build()
userDao = database.userDao()
}
@After
fun tearDown() {
database.close()
}
@Test
fun insertAndRetrieveUser() = runTest {
val user = UserEntity(displayName = "Alice", email = "alice@test.com")
val id = userDao.upsert(user)
val result = userDao.findByEmail("alice@test.com")
assertNotNull(result)
assertEquals("Alice", result?.displayName)
}
@Test
fun observeUsersEmitsUpdates() = runTest {
val emissions = mutableListOf<List<UserEntity>>()
val job = launch(UnconfinedTestDispatcher()) {
userDao.observeAll().toList(emissions)
}
userDao.upsert(UserEntity(displayName = "Bob", email = "bob@test.com"))
advanceUntilIdle()
assertTrue(emissions.last().any { it.displayName == "Bob" })
job.cancel()
}
}Best Practices
- Always use
Flowreturn types for reactive queries; usesuspendfor one-shot operations. - Prefer
OnConflictStrategy.REPLACEfor upsert patterns,IGNOREfor insert-if-absent. - Export schemas (
exportSchema = true) to track migration history in version control. - Use
@Transactionfor queries returning relations or when performing multi-step writes. - Keep entity classes as pure data holders; map to domain models in the repository layer.
- Use KSP instead of KAPT for Room annotation processing (faster build times).
- Test migrations with
MigrationTestHelperfromroom-testing.
Related skills
FAQ
How are reactive queries exposed?
DAO methods return Flow, for example observeAll() returns Flow<List<UserEntity>> so the UI updates on data changes.
How are migrations defined?
As Migration(1, 2) objects that run SQL like ALTER TABLE and CREATE INDEX, added to the database builder.