Compose Multiplatform 実践:CMPでのROOMによるクロスプラットフォームデータベース開発とトラブルシューティング
Compose Multiplatform (略称CMP)
CMP
プロジェクトでは
クロスプラットフォームのデータベース操作をどのように実現するのでしょうか?
昨日はSqlDelight
について説明しました
これはCMP初期に提案され、ローカルデータベース
のクロスプラットフォームソリューションとして設計されたものです
最近の2024年5月
頃
Room
もCMPクロスプラットフォームのソリューションを提供し始めました
また、Android Developer
公式サイトにもKMPの使用に関する記事が掲載されています
この記事ではクロスプラットフォームAndroid
とiOS
環境で
Room
を使用してデータベース操作を行う方法を紹介します
目錄
- 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開發跨平台資料庫 & 疑難雜症
注意1
. Roomバージョン2.7.0-alpha01
以降がKMMをサポートしています。
注意2
.
CMP
またはKMP
のBuild.gradle.kts
でのRoom
の設定はksp
との組み合わせが必要な場合があります
kspを導入する際、kotlinのバージョンによって
kspバージョンが低すぎる
またはバージョンの更新が必要
というエラーが表示されてビルドできないことがあります
この場合、Kotlinと互換性のあるバージョンを公式GitHubで探すことができます
参考:ksp releases
注意3
. kotlinとkspを使用するとkspバージョンとkotlinの互換性がチェックされます
kotlin 2.0.0を使用している場合、gradle sync時にバージョンが低すぎるまたは互換性がないと表示されると
Cannot change attributes of configuration ':composeApp:debugFrameworkIosX64' after it has been locked for mutation
または[KSP2] Annotation value is missing in nested annotations
というエラーが発生します
最初に[KSP2] Annotation value is missing in nested annotations
というエラーに遭遇しました
オンラインで調査した結果
gradle.property
にksp.useKSP2=true
を追加することでこの問題を解決できることがわかりました
上記の問題を解決した後
gradle sync
はできるようになりましたが
ksp
でRoom
を設定する際に別の問題が発生しました
例えばksp(libs.androidx.room.compiler)
を設定した後
[ksp] [MissingType]: xxxxx 'data.xxx.xxxDao' references a type that is not present
というエラーが表示されます
その後判明したことですが
問題の原因は公式ドキュメントの設定が主にKotlin 1.9.0
向けであることでした
Kotlinが2.0.0
にアップグレードされた後
KSP
との連携方法が調整されました
同様の問題に遭遇している他のユーザーもいました
以下は関連するIssueの報告です:
Issue 1
Issue 2
Issue 3
問題を解決するためにKotlinのバージョンをKSPと同じバージョンに下げることを提案する人もいました
しかし、現在公式のWizard
で生成されたCMP
のデフォルトはすでにKotlin 2.0.0
なので
私は「新しいものを使う」という原則を守ることにしました XD
Kotlin 2.0.0
でRoom
を正常に構築したい場合
一時的な解決策が必要かもしれません
公式がこの問題を修正するまでの間
以下の設定方法を参考にして
Room
をKotlin 2.0.0
で正常に動作させることができます
以下では、Kotlin 2.0.0でRoomを使用する方法について共有します
- ステップ1. プロジェクトに
Room
を導入する
libs.version.toml
ファイルに以下を追加します:
[versions]
kotlin = "2.0.0"
ksp = "2.0.0-1.0.21"
sqlite = "2.5.0-SNAPSHOT"
androidx-room = "2.7.0-alpha01"
[libraries]
androidx-room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "androidx-room" }
androidx-room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "androidx-room" }
sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" }
[plugins]
ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }
room = { id = "androidx.room", version.ref = "androidx-room" }
そして、gradleのcommonMainに以下を追加します
commonMain.dependencies {
implementation(libs.androidx.room.runtime)
implementation(libs.sqlite.bundled)
}
- ステップ2.
build.gradle.kts
を調整します。主に以下の点がありますbuild/generated/ksp/metadata
をsourceSets.commonMain
に追加する- add方法を使用してkspをインポートする:
add("kspCommonMainMetadata", libs.androidx.room.compiler)
- 最外層にtasks.withTypeを追加する
- room schemas設定を追加する
- plugins設定を追加する
plugins {
alias(libs.plugins.ksp)
alias(libs.plugins.room)
}
kotlin {
sourceSets.commonMain {
kotlin.srcDir("build/generated/ksp/metadata")
}
...
}
room {
schemaDirectory("$projectDir/schemas")
}
dependencies {
add("kspCommonMainMetadata", libs.androidx.room.compiler)
}
tasks.withType<org.jetbrains.kotlin.gradle.dsl.KotlinCompile<*>>().configureEach {
if (name != "kspCommonMainKotlinMetadata" ) {
dependsOn("kspCommonMainKotlinMetadata")
}
}
- ステップ3. workaroundを使用してRoomDatabaseを実装する
これは現段階でのworkaroundです
kotlin 2.0.0バージョンとRoomを組み合わせて使用したい場合は、まずこれを行う必要があります
現在、kspとの互換性
は公式の修正を待つ必要があります
主にRoomを構築する際にRoomのRoomDatabase
を継承します
通常、コンパイル後にAppDataBaseの実装が生成されますが
現在のバージョンではclearAllTables
が不足しています
そのため、ここで手動で追加して
一時的な解決策とします
// in ~/commonMain/db/AppDataBase.kt
@Database(entities = [VocabularyEntity::class], version = 1)
abstract class AppDatabase : RoomDatabase(), DB {
abstract fun vocabularyDao(): VocabularyDao
override fun clearAllTables() {
super.clearAllTables()
}
}
// FIXME: Added a hack to resolve below issue:
// Class 'AppDatabase_Impl' is not abstract and does not implement abstract base class member 'clearAllTables'.
interface DB {
fun clearAllTables() {}
}
- 前回と同様に、すべてのターゲットプラットフォームの
RoomDatabase.Builder
を作成する必要があります
// in ~/androidMain
fun getAppDatabase(context: Context): RoomDatabase.Builder<AppDatabase> {
val dbFile = context.getDatabasePath("app.db")
return Room.databaseBuilder<AppDatabase>(
context = context.applicationContext,
name = dbFile.absolutePath
)
.fallbackToDestructiveMigrationOnDowngrade(true)
.setDriver(BundledSQLiteDriver())
.setQueryCoroutineContext(Dispatchers.IO)
}
// in ~/iOSMain
fun getAppDatabase(): RoomDatabase.Builder<AppDatabase> {
val dbFile = NSHomeDirectory() + "/app.db"
return Room.databaseBuilder<AppDatabase>(
name = dbFile,
factory = { AppDatabase::class.instantiateImpl() }
)
.fallbackToDestructiveMigrationOnDowngrade(true)
.setDriver(BundledSQLiteDriver()) // Very important
.setQueryCoroutineContext(Dispatchers.IO)
}
//in ~/androidMain
actual val platformModule: Module = module {
single<RoomDatabase.Builder<AppDatabase>> {
getAppDatabase(get())
}
}
//in ~/iosMain
actual val platformModule: Module = module {
single { getAppDatabase() }
}
Roomの核心概念はオブジェクト指向の方法でローカルデータベースを操作することです
RoomDatabase
、DAO
(データアクセスオブジェクト)、Entity
(エンティティクラス)を実装することで
データベースを簡単に操作できます
AppDatabase
:Roomのアノテーション@Database
を持つRoomDatabase
を実装したクラスです
その中でentityの宣言やバージョン移行などを行えます
dao
:インターフェースを作成し、いくつかのSQL cmdと組み合わせてDBの操作をオブジェクト指向の方法で行えるようにします
entity
:基本的にテーブルの作成をオブジェクト指向の方法で行います
ここでは@Entity
アノテーションを使用してRoomのentityであることを宣言し
RoomDatabase()に追加してコンパイルすると
DBに対応するテーブルが生成されます
AppDatabase
の実装