Compose Multiplatform in Action: Implementing SqlDelight for Cross-Platform Database in CMP
Compose Multiplatform (CMP)
In a CMP
project
how can we implement cross-platform database operations?
SqlDelight
offers a powerful solution
This article will introduce how to perform database operations
using SqlDelight
in a cross-platform Android
& iOS
environment
目錄
- Compose Multiplatform 實戰:放輕鬆點,初探CMP
- Compose Multiplatform 實戰:初戰,安裝CMP環境吧
- Compose Multiplatform 實戰:續戰,用Wizard創建CMP專案
- Compose Multiplatform 實戰:在Android、iOS模擬器上跑CMP專案
- Compose Multiplatform 實戰:CMP的專案結構理解與編譯配置
- Compose Multiplatform 實戰:CMP中跨平台Android、iOS程式碼的進入點
- Compose Multiplatform 實戰:在CMP的Compose中用Material Design3 Theme
- Compose Multiplatform 實戰:CMP用Compose實作跨平台畫面
- Compose Multiplatform 實戰:使用 expect 和 actual 實現跨平台程式碼
- Compose Multiplatform 實戰:CMP中實作Compose Navigation頁面切換
- Compose Multiplatform 實戰:CMP中透過StateFlow來管理UI狀態
- Compose Multiplatform 實戰:CMP中實作NavigationBar底部欄
- Compose Multiplatform 實戰:CMP中使用koin來依賴注入Dependency Injection
- Compose Multiplatform 實戰:CMP實作跨平台資料庫SqlDelight
- Compose Multiplatform 實戰:CMP中使用ROOM開發跨平台資料庫 & 疑難雜症
To introduce SqlDelight
to your project
add the following to your libs.version.toml
file:
[versions]
sqldelight = "2.0.1"
[libraries]
sqldelight-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" }
sqldelight-native = { module = "app.cash.sqldelight:native-driver", version.ref = "sqldelight" }
sqldelight-coroutines-extensions = { module = "app.cash.sqldelight:coroutines-extensions", version.ref = "sqldelight" }
/** Choose one between extensions and runtime */
/** The main difference is that extensions provides commonly used flow and emit operations, while runtime doesn't */
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
[plugins]
sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
And add the plugin and dependencies in build.gradle.kts
This section requires implementations for both target platforms
CMP
again provides a Kotlin
-based solution
so you can directly add the corresponding dependencies
to androidMain
, commonMain
, and iosMain
androidMain.dependencies {
implementation(libs.sqldelight.android)
}
commonMain.dependencies {
implementation(libs.sqldelight.coroutines.extensions)
}
iosMain.dependencies {
implementation(libs.sqldelight.native)
}
Also, in the same build.gradle.kts
file
add the SqlDelight configuration
under the kotlin
section
This gradle configuration
can be understood as creating an operable AppDatabase class
in the test.your.package.db package
after compilation
kotlin {
sqldelight {
databases {
create("AppDatabase") {
packageName.set("test.your.package.db")
}
}
}
}
Create .sq
files in the commonMain/sqldelight/database
directory:
You need to add a
sqldelight folder
in the above path
for the build process to successfully generate operable classes
The .sq
files in SqlDelight
allow you to define tables using SQL commands
to create CRUD (Create, Read, Update, Delete) operations
Here’s an example .sq
file:
CREATE TABLE VocabularyEntity (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL
);
insert:
INSERT OR REPLACE INTO VocabularyEntity(id, name)
VALUES(?,?);
getAll:
SELECT * FROM VocabularyEntity;
updateName:
UPDATE VocabularyEntity
SET name = :name
WHERE id IS :id;
delete:
DELETE FROM VocabularyEntity
WHERE id IS :id;
As mentioned earlier
due to compatibility differences between Android and iOS platforms
table-related operation logic can be shared
but the DatabaseDriver for different targets may need separate implementations
Therefore
to create DatabaseDriver
for different platforms
you can write it like this:
// in ../commonMain
expect class DatabaseDriverFactory {
fun create(): SqlDriver
}
// in ../androidMain
actual class DatabaseDriverFactory(private val context: Context) : KoinComponent {
actual fun create(): SqlDriver {
return AndroidSqliteDriver(AppDatabase.Schema, context, "AppDataBase")
}
}
// in ../iosMain
actual class DatabaseDriverFactory {
actual fun create(): SqlDriver {
return NativeSqliteDriver(AppDatabase.Schema, "AppDataBase")
}
}
Key code explanation
:
AppDatabase.Schema
: This Schema is generated after configuring the aboveBuild.gradle.kts
and syncing.AndroidSqliteDriver
: The Android platform requires acontext
, so we include it in the actual classDatabaseDriverFactory
constructorNativeSqliteDriver
: The SqliteDriver for iOS
As mentioned in previous days
when dealing with cross-platform
content
you can create a platformModule
to implement cross-platform injection needs
(If you’ve forgotten, go back to Using Koin for Dependency Injection in CMP)
Here’s an example:
// in ../commonMain
expect val platformModule: Module
// in ../androidMain
actual val platformModule: Module = module {
single { DatabaseDriverFactory(get<Context>()) }
single { AppDatabase(get<DatabaseDriverFactory>().create()) }
}
// in ../iosMain
actual val platformModule: Module = module {
single { DatabaseDriverFactory() }
single { AppDatabase(get<DatabaseDriverFactory>().create()) }
}
Key code explanation
:
-
platformModule
: In commonMain, we use expect to inform target platforms they need to implement the corresponding variable
and inandroidMain
andiosMain
, we need to implement the actual variable respectively -
Therefore, in the target platforms
android
andiOS
, we can use
the previously completedAppDatabase
andDatabaseDriverFactory
along withkoin
for dependency injection
Here’s an example of obtaining AppDatabase
and performing operations
SqlDelight will convert it into object-oriented methods for you to use
class LearningDataStore (private val db: AppDatabase) {
private val queries = db.vocabularyEntityQueries
fun insert(id: Long?, name: String) {
queries.insert(id = id, name = name)
}
fun getAll() = queries.getAll().asFlow().mapToList(Dispatchers.IO)
fun update(id: Long, name: String) {
queries.updateName(id = id, name = name)
}
fun delete(id: Long) {
queries.delete(id = id)
}
}
This is an early solution provided by CMP
for a local database
to share cross-platform db logic
However, it’s not very intuitive to use currently
as you need to write entire SQL commands yourself
But if it allows writing logic for both platforms at once
it’s still a good approach
Don’t worry, though
We’ll introduce a new approach using Room
for cross-platform local databases later
which started being supported around May 2024
so you can check that out as well
In the next article
I’ll introduce another local database option, Room
After comparing both
you can decide which one to use
Compose Multiplatform in Action: Using ROOM for Cross-Platform Database Development & Troubleshooting