The core:data:db-room module implements the data layer using Room - Android's persistence library. This is an alternative database implementation to SQLDelight, primarily for comparison and Android-focused development.
- Provide Room-based data layer implementation
- Support database encryption via SQLCipher
- Implement
NoteDAOandDatabaseHolderinterfaces from domain layer - Demonstrate multiplatform Room usage (Android, iOS, Desktop)
- Offer an alternative to SQLDelight for teams preferring Room
core:data:db-room (Data Layer - Room implementation)
├── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/softartdev/notedelight/
│ │ ├── NoteRoomDAO.kt
│ │ ├── NoteRoomDb.kt
│ │ ├── NoteRoomFactory.kt
│ │ ├── SafeRepoRoom.kt
│ │ └── converter/
│ │ └── LocalDateTimeConverter.kt
│ ├── androidMain/ # Android + Room + SQLCipher
│ ├── iosMain/ # iOS + Room
│ ├── jvmMain/ # Desktop + Room
│ └── commonTest/ # Shared tests
└── build.gradle.kts
@Database(
entities = [NoteEntity::class],
version = 1,
exportSchema = true
)
@TypeConverters(LocalDateTimeConverter::class)
abstract class NoteRoomDb : RoomDatabase() {
abstract fun noteDao(): NoteRoomDAO
}@Dao
interface NoteRoomDAO : NoteDAO {
@Query("SELECT * FROM Note ORDER BY dateModified DESC")
override val pagingDataFlow: Flow<PagingData<Note>>
@Insert(onConflict = OnConflictStrategy.REPLACE)
override suspend fun insert(note: Note)
@Delete
override suspend fun delete(note: Note)
// ... more operations
}LocalDateTimeConverter: ConvertsLocalDateTimeto/from Long (timestamp)- Required for Room to handle custom types
SafeRepoRoom: Thread-safe repository using Room- Manages database encryption state
- Implements domain layer interfaces
NoteRoomFactory: Creates Room databases with platform-specific builders- Handles encryption setup
- Manages database file paths
- Database: Room with native Android support
- Encryption: ✅ SQLCipher via
SafeRoomlibrary - Storage: App-specific directory
- KSP: Kotlin Symbol Processing for code generation
- Dependencies:
androidx.room.runtimeandroidx.room.ktxcommonsware.saferoomandroid.sqlcipher
- Database: Room via KMP support
- Encryption: ✅ SQLCipher support
- Storage: iOS Documents directory
- KSP: Code generation for iOS target
- Dependencies:
androidx.room.runtime- SQLCipher pod
- Database: Room with JDBC driver
- Encryption: ❌ Not implemented
- Storage: User home directory
- Dependencies:
androidx.room.runtime- JDBC SQLite driver
Room with SQLCipher works similarly to SQLDelight:
// Android with encryption
val factory = SafeRoomSupportFactory.fromUser(Passphrase(password))
val db = Room.databaseBuilder(context, NoteRoomDb::class.java, "notes.db")
.openHelperFactory(factory)
.build()Room uses Kotlin Symbol Processing (KSP) for compile-time code generation:
// In build.gradle.kts
plugins {
id("com.google.devtools.ksp")
}
dependencies {
ksp("androidx.room:room-compiler:$roomVersion")
}Generated code is in build/generated/ksp/.
Room has built-in Paging3 support:
@Query("SELECT * FROM Note ORDER BY dateModified DESC")
fun pagingSource(): PagingSource<Int, Note>
// In DAO implementation
override val pagingDataFlow: Flow<PagingData<Note>> = Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { noteDao().pagingSource() }
).flowRoom migrations are defined programmatically:
val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("ALTER TABLE Note ADD COLUMN favorite INTEGER DEFAULT 0 NOT NULL")
}
}
Room.databaseBuilder()
.addMigrations(MIGRATION_1_2)
.build()- ✅ Android - Full support with encryption
- ✅ iOS - Experimental multiplatform Room support
- ✅ Desktop JVM - Full support (no encryption)
- ❌ Web - Not supported (Room doesn't support Web)
core:domain- Domain interfaces and modelsandroidx.room.runtime- Room runtimeandroidx.room.ktx- Kotlin extensionsandroidx.paging.common- Paginationkotlinx-datetime- Date/time handlingkotlinx-coroutines- Async operations
androidx.room.compiler(KSP processor)
- Android: SQLCipher + SafeRoom
- iOS: SQLCipher pod
- JVM: JDBC SQLite driver
Room provides in-memory database for testing:
@RunWith(AndroidJUnit4::class)
class NoteRoomDAOTest {
private lateinit var database: NoteRoomDb
@Before
fun setup() {
database = Room.inMemoryDatabaseBuilder(
context = ApplicationProvider.getApplicationContext(),
klass = NoteRoomDb::class.java
).build()
}
@After
fun teardown() {
database.close()
}
}# All tests
./gradlew :core:data:db-room:test
# Platform-specific
./gradlew :core:data:db-room:androidHostTest
./gradlew :core:data:db-room:iosSimulatorArm64TestTo use Room instead of SQLDelight, modify gradle.properties:
# Switch to Room
CORE_DATA_DB_MODULE=:core:data:db-roomThen rebuild the project:
./gradlew clean build- Android-native: Deep Android integration
- Compile-time verification: SQL queries validated at compile time
- Built-in migrations: Migration framework included
- Paging support: Native Paging3 integration
- Type converters: Automatic type conversion
- Annotation-based: Familiar Android development patterns
- No Web support: Room doesn't work in browsers
- Limited multiplatform: Primarily Android-focused
- Larger binary: Room adds more code than SQLDelight
- KSP requirement: Requires KSP for code generation
| Feature | Room | SQLDelight |
|---|---|---|
| SQL-first | ❌ Annotation-based | ✅ Pure SQL |
| Web support | ❌ | ✅ |
| Multiplatform | ✅ Native | |
| Code gen | KSP | Gradle plugin |
| Paging | Built-in | Custom extension |
| Learning curve | Android-familiar | SQL-familiar |
| Binary size | Larger | Smaller |
See CONTRIBUTING.md for general guidelines.
- Use
@Entity,@Dao,@Databasecorrectly - Ensure KSP is configured for all platforms
- Register all custom type converters
- Handle iOS/JVM differences
- Use in-memory databases for tests
- Use
suspendfor all async operations
@Entity(tableName = "Note")
data class NoteEntity(
@PrimaryKey
val id: Long,
val title: String,
val text: String,
val dateCreated: LocalDateTime,
val dateModified: LocalDateTime
)@Dao
interface NoteRoomDAO {
@Query("SELECT * FROM Note WHERE id = :id")
suspend fun getById(id: Long): Note?
@Query("DELETE FROM Note WHERE id = :id")
suspend fun deleteById(id: Long)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(notes: List<Note>)
}val db = Room.databaseBuilder(
context = context,
klass = NoteRoomDb::class.java,
name = "notes.db"
)
.addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration() // Use with caution!
.build()- Used by:
ui:shared,app:android(when selected) - Depends on:
core:domain - Alternative:
core:data:db-sqldelight(default)