くま's Tech系Blog

基本的には技術で学んだことを書き留めようと思います。雑談もやるかもね!

Realmを使ってみよう(Android編)

今回はRealmをAndroidで使うやり方について書こうと思います

Realmの大まかな説明などは前回の記事をみてください

Realmの導入

まずはRealmを使えるようにします

build.gradle(app)で下記設定を追加します

plugin {
    id `realm-android`
}

そして、build.gradle(Project)で下記設定を追加します

buildscript {
    dependencies {
        classpath `io.realm:realm-gradle-plugin:10.9.0`
    }
}

設定を追加したらsyncを行うと使えるはずです。バージョンはGithubを確認してください

Modelの作成

プロジェクトで支えようになったら、まずはModelの作成からです

今回も前回と同じように仮でユーザーを表すクラスを作成します

open class User: RealmObject() {
    @PrimaryKey
    var id: Int = 0
    var name: String? = null
    var age: Int? = null
    @Ignore
    var height: Int? = null
}

RealmObjectを継承することでRealmで使えるModelになります

アノテーション@PrimaryKeyを指定しているプロパティはプライマリーキー扱いにでき、@Ignoreを指定しているプロパティはRealmには登録されないカラムとなります

Realmのデータベースはテーブルにアクセスするために中でModelクラスを継承する必要があります

しかし、KotlinのデフォルトではJavaでいうfinalクラスで定義され、継承ができなくなってしまうので、open修飾子をつけています

Realmの初期化(起動)

Modelを初期化した後にはRealmインスタンスを初期化してDBを起動します

var relalm: Realm? = null

Realm.init(this)
realm = Realm.getDefaultInstance()

上記の処理をActivityやRealmを管理するシングルトンのクラスなどに追加します

ちなみにRealmを削除する場合にはcloseメソッドで行えます

RealmのCRUD

次にRealmの登録・読み取り・更新・削除についてです

読み取り

まずは読み取りです

先ほど初期化したRealmを使って読み取りを行います

// ここでのinitRealmは初期化した際のRealm
initRealm.use { realm ->
    val result = relam.where<User>().findAll()
}

findAll()を使うことで、Realmに登録されているUserクラスのデータを全件取得できます

条件を指定する場合には下記のように条件を追加することで特定の条件のデータを取得できます

// 名前が「テスト」のデータのみ取得
val realmFilterNameData = relam.where<User>().equalTo("name", "テスト").findAll()

// 名前が登録されているデータのみ取得
val realmNameNotNullData = relam.where<User>().isNotNull("name").findAll()

上記の場合だとAND条件になるので、OR条件にする場合にはgroupを指定する必要があります

relam.where<User>()
    .beginGroup()
        .equalTo("name", "テスト1")
        .or()
        .equalTo("name", "テスト2")
    .endGroup()
    .findAll()

単純に取得したデータはRealmResult型のためListなどに変換する場合には下記のようにConvertする必要があります

initRealm.use { realm ->
    val result = relam.where<User>().findAll()
    val listResult = realm.copyFromRealm(result)
}
登録・更新

登録と更新は同じ処理でできますのでまとめます

val user = User()
user.id = 1
user.name = "テスト"
user.age = 2

initRealm.use { realm ->
    realm.executeTransaction {
        it.insertOrUpdate(user)
    }
}

読み取りのときと違って、realm.executeTransactionというトランザクションの中で行わないと登録されません

insertOrUpdateを使うことでprimaryKeyが一致しているデータがRealmに登録されている場合には更新処理が行われ、なければ登録処理が行われます

先ほどModelを作成したときにprimaryKeyを設定したのはここに理由があります。Realmに登録されているデータと一致しているかの判断基準で使われるのでprimaryKeyは設定しましょう!

また、読み取りと組み合わせて下記のように取得したデータのプロパティの更新も可能です

val results = relam.where<User>().findAll()

for result in results {
    realm.executeTransaction {
       result.name = "テスト1"
    }
}
削除

削除はrealm.deleteAllFromRealm()でできます

val user = User()
user.id = 1
user.name = "テスト"
user.age = 2

initRealm.use { realm ->
    realm.executeTransaction {
        it.deleteAllFromRealm(user)
    }
}

基本的な操作はこれだけです

Realmのデータの確認方法

実機もしくはエミュレーターで対象のアプリを起動します

AndroidStudioのView→Tool Windows→Device File Exploreを選択します。

f:id:kumaskun:20220206064208p:plain

すると上記のようにdata/data/{パッケージ名}/files配下にrealmファイルが存在するので右クリックで別の場所に保存してリネームすればRealmファイルになります

ただし本番環境ではファイルが確認ができないためデバッグ可能になるように下記設定をbuild.gradleに追加しないとファイルを参照できないので注意が必要です

buildTypes {
        release {
            debuggable true
        }
}

初期データを追加する

Realmファイルを追加して初期データとして扱うことができます

下記のように起動時にRealmファイルが存在しない場合には初期データをコピーしてrealmファイルとするようにできます

val congfig = RealmConfiguration.Builder()
    .assetFile("default.realm") // assetフォルダ配下に設定しているRealm初期データの名前指定
    .name("rename.realm") // 取得できるRealmファイルの名前を変えたい場合には指定
    .allowWritesOnUiThread(true)
    .allowQueriesOnUiThread(true)
    .schemeVersion(1L)
    .build()

Realm.getInstance(config)

realmを初期化する際に設定値を変更できるのでそのタイミングでassetフォルダに格納している初期データを見るように変更します

マイグレーションについて

テーブルを追加する場合には自動的にマイグレーションされますが、変数名を変更したり、カラムを削除したりする場合には不整合のエラーが発生します

ローカルで開発している場合にはアプリを削除して新規でアプリを入れ直せばいいのですが、リリースしているアプリではそうはいきません

リリース済みのアプリではマイグレーションが必要になります

マイグレーションの処理としては下記のようなマイグレーションのクラスを作成してRealm初期化の際の設定値に追加します

// RealmMigrationクラスを継承
class Migration: RealmMigration {
    override fun migrate(realm: DynamicRealm?, oldVersion: Long, newVersion: Long) {
        val scheme = realm?.schema ?: return

        if (olfVersion == 0L) {
            scheme.create("Prefecture")
                 // 第1引数がカラム名、第2引数がカラムの型、第3引数がぷりプライマリーキーであることの明示
                .addField("id", Int::class.java, FieldAttribute.PRIMARY_KEY)
                .addField("sortOrder", Int::class.java)
                // null許容であることの明示(型がIntでNullがあり得る場合には追加する必要がある)
                .setNullable("name", true)

            oldVersion++
        }
    }
}

iOSとは違い、テーブル追加は自動でマイグレーションしないため追加する必要があります

scheme.createがテーブル追加、addFieldがカラム追加です

上記Migrationクラスを設定に追加します

val congfig = RealmConfiguration.Builder()
    .assetFile("default.realm")
    .name("rename.realm")
    .migration(Migration()) // 追加
    .allowWritesOnUiThread(true)
    .allowQueriesOnUiThread(true)
    .schemeVersion(1L)
    .build()

Realm.getInstance(config)

今回はスキーマのバージョンを1にアップした状態で起動時にスキーマのバージョンが0の人はマイグレーションの処理が行われます(oldVersionがアプリ起動時のスキーマバージョンになります)

AndroidiOSと同様に学習コストが低いので使いやすいと思います。是非試してみてください!!

参照

docs.mongodb.com

qiita.com