単語帳を作りたいのでデータ永続化のためにデータベースを探していました。
なんやかんやあってRealm[1]を採用することになりました。
採用理由は
kotlin android database おすすめ
で調べて最初に出てきた記事[2]でおすすめされていたライブラリだからです。
英語で
kotlin multiplatform database recommend
と調べても、一番上の質問板[3]ではRealmがおすすめされています。だいぶおすすめっぽいです。
Android公式のRoom[4]も使ってみたかったのですが、ページトップに書いてあるとおりAndroidJetpackの一部とのことで、
めちゃめちゃAndroidのなのでkmpで利用するには適さないと考えました。
自分用のテンプレリポジトリがあるのでこいつに実装します。
動作もわかりやすいので、適当にボタンをおいてカウンタを実装しました。
ボタンをタップしたらボタン内部の数字が1大きくなります。
該当差分はこちらです。Android公式のMVVMに従って実装したつもりです。
koinを導入してあるので使ってもよいのですが、例としてわかりにくくなるので今回はなしです。
動作はこんな感じ。
これはまだ永続化していないので、一度アプリを落とすとカウントがリセットされてしまいます。これを永続化するためにRealmを導入します。
とりあえず依存関係の追加をおこないました。PRはこちらです。
gradle/libs.versions.toml
[versions]
~~ 省略 ~~
realm="1.16.0"
[libraries]
~~ 省略 ~~
realm-library-base={module="io.realm.kotlin:library-base" ,version.ref="realm"}
[plugins]
~~ 省略 ~~
realm = {id = "io.realm.kotlin",version.ref ="realm"}
プラグインとライブラリの依存関係を追加します。
バージョンは『Kotlin SDK のインストール』[5]の数字を参考にしました。
build.gradle.kts
alias(libs.plugins.realm) apply false
composeApp/build.gradle.kts
plugins {
~~ 省略 ~~
alias(libs.plugins.realm) apply false
}
kotlin{
~~ 省略 ~~
sourceSet{
~~ 省略 ~~
commonMain.dependencies {
~~ 省略 ~~
implementation(libs.realm.library.base)
~~ 省略 ~~
}
~~ 省略 ~~
}
~~ 省略 ~~
}
プロジェクト配下とApp配下のgradleに追加したライブラリ・プラグインを追加します。
作ったリポジトリの内部で永続化を行います。差分はこちらです。
ここからはクイックスタート[6]を参考に実装を行います
composeApp/src/commonMain/kotlin/domain/Count.kt
package domain
import io.realm.kotlin.types.RealmObject
import io.realm.kotlin.types.annotations.PrimaryKey
import org.mongodb.kbson.ObjectId
class Counter : RealmObject {
@PrimaryKey
var _id: ObjectId = ObjectId()
var count: Int = 0
}
『オブジェクトモデルを定義する』[7]を参考に保存したいオブジェクトを定義しました。
保存したいのは数値だけなので、PrimaryKeyとcountだけ用意しました。
repositoryの各行の説明に入ります。
private val config: RealmConfiguration = RealmConfiguration.create(schema = setOf(Counter::class))
private val realm: Realm = Realm.open(config)
『Realmを開く』[8]を参考にCounterクラスを読み込みます。
今回利用したいものはCounterなので、schemaにはCounterだけ追加します。
private val counter: Counter = realm.query<Counter>().first().find() ?:
realm.writeBlocking {
copyToRealm(
Counter()
)
}
『オブジェクトの作成、読み取り、更新、削除』[9]を参考に読み書きをします。
エルビス演算子(?:)の前で、queryを利用してCounter
のデータを読み出します。1つしか保存していないので検索ワードはなしで、そのまま取り出します。
エルビス演算子以降は書き込みを参考にデータの新規作成を行います。
初回起動時はデータが保存されておらず、queryの結果がnullになるので
書き込み処理を入れてあげます。
findLatest(counter)?.apply {
count = value
}
}
値を更新した際に、同時にDBも更新しています。
読み出しているcounterインスタンスの値を更新します。
参考にしたのは引き続き、『オブジェクトの作成、読み取り、更新、削除』[9]です。
init{
count = counter.count
}
大したことはしてなく、初回表示を0ではなくしただけです。
以上で完成です。
DBに保存して永続化しているので、アプリを終了しても数値を継続利用できます。
全体の差分はこちらです
公式ページ[10]や公式のgithub[11]を見ていると、Atlas Device Sync +
Realm SDKsの組み合わせが2024/09にdeprecatedになったようです。この作業は10月に入ってから始めたのでほんとに直前に発表されています。タイムリーです。
警告に従うならRealm Kotlinの3.0.0以上のバージョンを利用するか、syncの機能の利用を避けなくてはなりません。
Atlas Device Syncについて調べると、デバイス間で同期をとる機能のよう[12]です。今回の実装だとあまり関係ないですね。
でもせっかくなのでバージョンアップしたい気持ちがあります。1.16.0から3.0.0にアップデートするのは大変です。一気にメジャーバージョンが2個も上がることあるんですね……。
現状のコードではバージョンを3.0.0にすると以下のエラーが発生してしまいます。
kmpTemplate/composeApp/src/commonMain/kotlin/domain/Count.kt:-1:-1
Details: Internal error in file lowering : io.realm.kotlin.compiler.RealmModelLowering: Found interface org.jetbrains.kotlin.ir.declarations.IrFactory, but class was expected
(2024/10/10現在、)このエラーコードで検索をかけても8件しかヒットしませんでした。
調べても沼りそうなので今回は引き返すことにします。どこかで対策したいです。
と、まぁ今後の目標もたったところで今回はこの辺で終わります。