Compose Multiplatform 実践:CMPでSqlDelightを使用したクロスプラットフォームデータベースの実装
Compose Multiplatform (略称CMP)
CMP
プロジェクトでは
クロスプラットフォームのデータベース操作をどのように実現するのでしょうか?
SqlDelight
はそのための強力なソリューションを提供しています
本記事ではクロスプラットフォームAndroid
およびiOS
環境で
SqlDelight
を使用してデータベース操作を行う方法を紹介します
目錄
- 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開發跨平台資料庫 & 疑難雜症
プロジェクトにSqlDelight
を導入するには
libs.version.toml
ファイルに以下を追加します:
[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" }
/** extensions と runtime はどちらか一方を選択可能 * /
/** 主な違いは、extensionsがよく使われるflow、emit関連の操作を提供するのに対し、runtimeは提供しない点 * /
sqldelight-runtime = { module = "app.cash.sqldelight:runtime", version.ref = "sqldelight" }
[plugins]
sqlDelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" }
そしてbuild.gradle.kts
にプラグインと依存関係を追加します
今回は2つの主要ターゲットプラットフォームの実装が必要です
CMP
はKotlin
ベースのソリューションを提供しているので
androidMain
、commonMain
、iosMain
に
それぞれ対応する依存関係を追加できます
androidMain.dependencies {
implementation(libs.sqldelight.android)
}
commonMain.dependencies {
implementation(libs.sqldelight.coroutines.extensions)
}
iosMain.dependencies {
implementation(libs.sqldelight.native)
}
同様に、build.gradle.kts
の
kotlin
セクションの下にsqlDelightの設定
を追加します
このgradle設定
は
test.your.package.dbパッケージ内で
コンパイル後
にAppDatabaseという操作可能なクラスが生成されると理解できます
kotlin {
sqldelight {
databases {
create("AppDatabase") {
packageName.set("test.your.package.db")
}
}
}
}
commonMain/sqldelight/database
ディレクトリに.sq
ファイルを作成します:
上記のパスに
sqldelightフォルダ
を追加する必要があります
そうすることでプロジェクトをビルドする際に操作可能なクラスが正常に生成されます
sqlDelight
の.sq
ファイルは
SQLコマンドを通じて
追加・削除・更新・検索を定義してテーブルを生成するソリューションを提供します
.sq
の例は以下の通りです:
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;
前述したように
AndroidとiOSプラットフォームの互換性の違いにより
テーブル関連の操作ロジックは共通化できますが
異なるターゲットのDatabaseDriverは個別に実装する必要があるかもしれません
したがって
異なるプラットフォーム用のDatabaseDriver
を作成するには
次のように実装できます:
// 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")
}
}
重要なコードの説明
:
AppDatabase.Schema
:このSchemaは上記のBuild.gradle.kts
を設定した後
同期すると自動生成されます。AndroidSqliteDriver
:Androidプラットフォームではcontext
の入力が必要なので、actual classDatabaseDriverFactory
のコンストラクタに含めますNativeSqliteDriver
:iOSのSqliteDriver
以前の記事で触れたように
クロスプラットフォーム
のコンテンツがある場合
platformModule
を作成して、クロスプラットフォームで注入する必要があるコンテンツを実装できます
(忘れた場合はCMPでKoinを使用した依存性注入を参照してください)
ここでは例を直接示します:
// 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()) }
}
重要なコードの説明
:
-
platformModule
:commonMainでexpectを使用して、ターゲットプラットフォームが対応する変数を実装する必要があることを知らせます
そしてandroidMain
、iosMain
ではそれぞれactualの変数を実装する必要があります -
したがって、ターゲットプラットフォーム
android
とiOS
では
前に完成したAppDatabase
とDatabaseDriverFactory
を使用して
koin
で依存性注入を行うことができます
以下はAppDatabase
を取得して操作を行う例です
SqlDelightはオブジェクト指向の操作可能なメソッドに変換して使用できるようにします
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)
}
}
これは初期のCMP
が提供するローカルデータベースのソリューションの一つです
クロスプラットフォームのDB論理を共有するために使用されます
しかし現在の使用感ではあまり直感的ではありません
SQLコマンド全体を自分で書く必要があるためです
しかし一度に両プラットフォームのロジックを書くことができれば
これも良い方法と言えるでしょう
しかし心配する必要はありません
後でRoom
を使ったクロスプラットフォームローカルDBの新しいソリューションを紹介します
これは最近の2024/05
頃にようやくサポートされ始めたものです
ですので、その時にもぜひ参考にしてみてください
次の記事では
別のローカルデータベースRoom
について紹介します
参考にした後
どちらを使用するかを決めることができます
Compose Multiplatform 実践:CMPでROOMを使用したクロスプラットフォームデータベースの開発と問題解決