The core:data:db-sqldelight module implements the data layer using SQLDelight - a multiplatform SQL database library that generates type-safe Kotlin APIs from SQL statements. This is the default database implementation for NoteDelight.
- Implement data access layer using SQLDelight
- Provide multiplatform SQLite database access
- Support database encryption via SQLCipher (Android, iOS, Desktop JVM)
- Implement
NoteDAOandDatabaseHolderinterfaces from domain layer - Manage database migrations and schema evolution
core:data:db-sqldelight (Data Layer - SQLDelight implementation)
├── src/
│ ├── commonMain/
│ │ ├── kotlin/
│ │ │ └── com/softartdev/notedelight/
│ │ │ ├── NoteDatabaseSQLDelightFactory.kt
│ │ │ ├── NoteDbHelper.kt
│ │ │ ├── NoteSQLDelightDAO.kt
│ │ │ ├── SafeRepoSQLDelight.kt
│ │ │ └── mapper/
│ │ │ └── LocalDateTimeMapper.kt
│ │ └── sqldelight/
│ │ └── com/softartdev/notedelight/db/
│ │ └── NoteDb.sq (SQL schema)
│ ├── androidMain/ # Android + SQLCipher
│ ├── iosMain/ # iOS + SQLCipher
│ ├── jvmMain/ # Desktop (unencrypted)
│ └── wasmJsMain/ # Web (sql.js)
└── build.gradle.kts
SQLDelight uses .sq files for SQL definitions:
CREATE TABLE IF NOT EXISTS Note (
id INTEGER PRIMARY KEY NOT NULL,
title TEXT NOT NULL,
text TEXT NOT NULL,
dateCreated TEXT NOT NULL,
dateModified TEXT NOT NULL
);
-- Queries
selectAll:
SELECT * FROM Note ORDER BY dateModified DESC;
insert:
INSERT INTO Note(id, title, text, dateCreated, dateModified)
VALUES (?, ?, ?, ?, ?);SQLDelight generates type-safe Kotlin code from these SQL statements at compile time.
NoteSQLDelightDAO: ImplementsNoteDAOinterface using generated SQLDelight APIs- Provides CRUD operations and pagination support
- Uses Flow for reactive data streams
NoteDatabaseSQLDelightFactory: Creates platform-specific database drivers- Handles encryption setup (SQLCipher) for Android, iOS, and Desktop JVM
- Manages database file paths
SafeRepoSQLDelight: Thread-safe repository implementation- Manages database lifecycle and encryption state
- Implements
SafeRepointerface from domain layer
LocalDateTimeMapper: Converts between SQLite TEXT and KotlinLocalDateTime- Ensures consistent date handling across platforms
- Driver:
AndroidSqliteDriver - Encryption: ✅ SQLCipher support via
SafeRoomlibrary - Storage: App-specific directory (
/data/data/.../databases/) - Dependencies:
sqlDelight.androidcommonsware.saferoom(SQLCipher wrapper)android.sqlcipher
- Driver:
NativeSqliteDriver - Encryption: ✅ SQLCipher via CocoaPods
- Storage: iOS Documents directory
- Dependencies:
sqlDelight.native- SQLCipher pod (specified in
cocoapods {}block) stately(for iOS memory model)
- Driver:
JdbcSqliteDriver(SQLite JDBC with SQLCipher support) - Encryption: ✅ SQLCipher support via
sqlite-jdbc-crypt(Willena's fork) - Storage: User home directory via
appdirslibrary - Dependencies:
sqlDelight.jvmappdirs(for platform-specific app directories)sqlite-jdbc-crypt(SQLCipher-enabled JDBC driver via SQLite3 Multiple Ciphers)
- Driver: Web Worker with
sql.js - Encryption: ❌ Not supported (browser environment)
- Storage: IndexedDB via browser APIs
- Dependencies:
sqlDelight.websql.jsNPM package- Web Worker setup via webpack
Encryption is supported on Android, iOS, and Desktop JVM using SQLCipher:
// Android
val driver = AndroidSqliteDriver(
schema = NoteDb.Schema,
context = context,
name = "notes.db",
factory = SafeRoomSupportFactory.fromUser(Passphrase(password))
)
// iOS
val driver = NativeSqliteDriver(
schema = NoteDb.Schema,
name = "notes.db",
key = password // SQLCipher key
)
// Desktop JVM
val driver = JdbcSqliteDriver(
url = "jdbc:sqlite:file:/path/to/notes.db?cipher=sqlcipher&legacy=4&key=${URLEncoder.encode(password, StandardCharsets.UTF_8)}",
properties = Properties()
)PlatformSQLiteState.DOES_NOT_EXIST- No database filePlatformSQLiteState.UNENCRYPTED- Database without encryptionPlatformSQLiteState.ENCRYPTED- Database with encryptionPlatformSQLiteState.UNDEFINED- Unknown state
Uses custom SQLDelight Paging3 extension from thirdparty:app:cash:sqldelight:paging3:
val pagingDataFlow: Flow<PagingData<Note>> = noteDAO.pagingDataFlowThis provides seamless integration with Jetpack/Compose Paging library.
SQLDelight handles migrations through schema versioning:
NoteDb.Schema.migrate(
driver = driver,
oldVersion = 1,
newVersion = 2
)Migration SQL is defined in .sqm files.
- ✅ Android - Full support with encryption
- ✅ iOS - Full support with encryption
- ✅ Desktop JVM - Full support with encryption
- ✅ Web - Full support (no encryption, sql.js)
core:domain- Domain interfaces and modelssqlDelight.runtime- SQLDelight runtimesqlDelight.coroutinesExt- Coroutine extensionsandroidx-paging-common- Paginationkotlinx-datetime- Date/time handlingkotlinx-coroutines- Async operationsstately-common- Thread-safe state management
See "Platform-Specific Implementations" section above for details.
- Mock database testing with in-memory SQLite
- DAO operation tests
- Repository tests
- Android instrumented tests (
androidDeviceTest/) - Platform-specific tests for each target
# All tests
./gradlew :core:data:db-sqldelight:test
# Platform-specific
./gradlew :core:data:db-sqldelight:jvmTest
./gradlew :core:data:db-sqldelight:iosSimulatorArm64Test
# Android instrumented tests (requires emulator)
./gradlew :core:data:db-sqldelight:connectedCheckTo switch from SQLDelight to Room, modify gradle.properties:
# Default: SQLDelight
CORE_DATA_DB_MODULE=:core:data:db-sqldelight
# Alternative: Room
CORE_DATA_DB_MODULE=:core:data:db-roomSQLDelight generates code at compile time:
# Generate database code
./gradlew :core:data:db-sqldelight:generateCommonMainNoteDbInterfaceGenerated files are in build/generated/sqldelight/.
See CONTRIBUTING.md for general guidelines.
- Write SQL in
.sqfiles, not Kotlin - Leverage SQLDelight's generated code
- Use
expect/actualfor platform-specific drivers - SQLCipher on Android/iOS/Desktop JVM
- Test with in-memory databases
- Use Flow for reactive queries
-- Named query in NoteDb.sq
selectById:
SELECT * FROM Note WHERE id = ?;
deleteById:
DELETE FROM Note WHERE id = ?;// SQLDelight generates type-safe APIs
val note: Note? = database.noteQueries.selectById(id = 1).executeAsOneOrNull()
database.noteQueries.deleteById(id = 1)// Observe query results as Flow
val notesFlow: Flow<List<Note>> = database.noteQueries
.selectAll()
.asFlow()
.mapToList(Dispatchers.IO)- Used by:
ui:shared,app:android,app:desktop,app:web,app:ios-kit - Depends on:
core:domain,thirdparty:app:cash:sqldelight:paging3 - Alternative:
core:data:db-room(Room implementation)