Realmの使い方 🔰初心者向け Kotlin

#最初に
この度初めて記事を投稿させていただきます。分りにくい箇所がありましたら気軽に質問してください。
今回は軽量かつ高速なデータベースRealmを紹介していきたいと思います。
バージョンは7.0.0と6.0.2を扱っていきたいと思います。
大した違いはないのでお好きなほうをどうぞ^^

■環境
・Android Studio 4.1.1
・Kotlin         1.4.10
・Realm          6.0.2 & 7.0.0

■公式サイト
[Realm](https://realm.io/docs/kotlin/latest)

# インストール

```gradle:build.gradle(アプリ名)【6.0.2】
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:6.0.2"
    }
}
```
```gradle:build.gradle(アプリ名)【7.0.0】
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:7.0.0"
    }
}
```

```gradle:build.grable(app)
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
apply plugin: 'realm-android'
```

```gradle:build.grable(app)
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    id 'kotlin-kapt'
    id 'realm-android'
}
```

<font color="red">※注意 pluginの順番は上記のようにしてください。</font>
pluginの記述はどちらの方法でも構いません。

### Android Studio 4.1.2 以降の```jcenter()```廃止について
[JCenter サービスの更新](https://developer.android.com/studio/build/jcenter-migration?hl=ja)
2021年3月31日をもって```jcenter()```が読み取り専用になったことにより、```build.gradle(アプリ名)```の```jcenter()```が```mavenCentral()```へと変更されました。
しかし、Realmは```jcenter()```にのみ対応しているため```build.gradle(アプリ名)```に手を加える必要があります。

```gradle:build.gradle(app)
buildscript {
    repositories {
        google()
        mavenCentral()
        jcenter()
    }
}
```

```gradle:setting.gradle(アプリ名)
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        google()
        mavenCentral()
        jcenter() // Warning: this repository is going to shut down soon
    }
}
rootProject.name = "アプリ名"
include ':app'
```

上記のように`mavenCentral()`の下に`jcenter()`を加えてください。
<font color="red">※注意 `jcenter()`は非推奨なのでいつ使えなくなるかわからないためおすすめはしません。</font>
　また、`setting.gradle`の2行目にある`repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)`を
`repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)`に変更してください。
# Realmの初期化と設定
新しくCustomApplication.kt(ファイル名は自由)をMainActivityと同じフォルダの中に作ります。

```kotlin:CustomApplication.kt

class CustomApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        //初期化
        Realm.init(this)
        //設定
        val config = RealmConfiguration.Builder()
            .deleteRealmIfMigrationNeeded()
         // .readOnly() 
            .build()
        Realm.setDefaultConfiguration(config)
    }
}
```
`.deleteRealmIfMigrationNeeded()`はデータベース(DB)モデルを変更したときに必要に応じて、DBを削除・再構築してくれます。(DBを変更した後にこのコードがないとアプリがクラッシュします。)
   `.readOnly()`は導入していませんが、このコードを入れることでDBを読み込みのみにしてくれます。最初のうちは必要ないので、記述しなくても大丈夫です。

###Manifestファイルに登録
Realmを初期化して設定を読み込ませるためには、manifests/AndroidManifest.xmlに新たにコードを書いていきます。

```xml:AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.realmsampleqiita">

    <application
        android:name=".CustomApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        <!--省略-->

    </application>

</manifest>
```
`<application>`の中に`android:name=".CustomApplication"`を記述します。

#Modelの作成
続いてDBモデルを作っていきます。

```kotlin:Data(任意の名前).kt
open class Data : RealmObject() {
    @PrimaryKey
    var id: Int = 0
    @Required
    var text: String = ""
}
```
<font color="red">※`open`を忘れないでください</font>
@PrimaryKeyは主キーでデータを一意に識別するために使われる項目、
@Requiredは入力が必須である(null×)項目です。

#実際にDBを操作する
MainActivityに変更を加えていきます。

```kotlin:MainActivity.kt
class MainActivity() : AppCompatActivity() {
    private lateinit var realm: Realm

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        realm = Realm.getDefaultInstance()
     }

     override fun onDestroy() {
        super.onDestroy()
        realm.close()
    }
}
```
`private lateinit var realm: Realm`Realmを初期化して`Realm.getDefaultInstance()`で
先ほど作ったCustomApplication.ktから初期設定を読み込んでいます。
 最後の`onDestory()`でRealmを閉じています。これがないとクラッシュします。

#データの挿入(登録)

```kotlin:MainActivity.kt
class MainActivity() : AppCompatActivity() {

        //省略

        realm = Realm.getDefaultInstance()

        insertData()

    }

    private fun insertData() {
        realm.executeTransaction {
            val id = realm.where<Data>().max("id")
            val nextId1 = (id?.toLong() ?: 0) + 1
            val realmObject1 = realm.createObject<Data>(nextId1)
                    realmObject1.text = "sampleText1"

            val nextId2 = (id?.toLong() ?: 0) + 2
            val realmObject2 = realm.createObject<Data>(nextId2)
                    realmObject2.text = "sampleText2"

            val nextId3 = (id?.toLong() ?: 0) + 3
            val realmObject3 = realm.createObject<Data>(nextId3)
                    realmObject3.text = "sampleText3"
        }
        Log.e("RealmInsert", "insertResult:${realm.where<Data>().findAll()}")
    }

    //省略
    
}
```
```kotlin:Logcast
E/RealmInsert: insertResult:[Data = proxy[{id:1},{text:sampleText1}], 
                             Data = proxy[{id:2},{text:sampleText2}], 
                             Data = proxy[{id:3},{text:sampleText3}]]
```

`realm.executeTransaction { }`はデータを書き込み・更新するときに必須になります。このコードを書かないと書き込み・更新ができません。
nextIdは登録ずみのidを検索してそのidに+1してidが被らないようにしています。
Logcast(Error)で登録したデータを確認できます。

>Logcastは下のタブにあります

#データの検索

```kotlin:MainActivity.kt
class MainActivity() : AppCompatActivity() {

        //省略

        realm = Realm.getDefaultInstance()

        insertData()

        selectData()

    }

    private fun selectData() {
        val realmResult1 = realm.where<Data>().findAll()
        val realmResult2 = realm.where<Data>().findFirst()
        val realmResult3 = realm.where<Data>()
                .equalTo("id", 1.toInt())
                .findAll()
        val realmResult4 = realm.where<Data>()
                .equalTo("text", "sampleText2")
                .findAll()
        val realmResult5 = realm.where<Data>()
                .greaterThan("id", 1)
                .findAll()
        val realmResult6 = realmResult1[1]?.text

        Log.e("RealmSelect", """
                |realmResult1:$realmResult1
                |realmResult2:$realmResult2
                |realmResult3:$realmResult3
                |realmResult4:$realmResult4
                |realmResult5:$realmResult5
                |realmResult6:$realmResult6
                |""".trimMargin())
    }

    //省略
    
}
```

```kotlin:Logcast
E/RealmSelect: realmResult1:[Data = proxy[{id:1},{text:sampleText1}],
                             Data = proxy[{id:2},{text:sampleText2}],
                             Data = proxy[{id:3},{text:sampleText3}]]
               realmResult2:Data = proxy[{id:1},{text:sampleText1}]
               realmResult3:[Data = proxy[{id:1},{text:sampleText1}]]
               realmResult4:[Data = proxy[{id:2},{text:sampleText2}]]
               realmResult5:[Data = proxy[{id:2},{text:sampleText2}], 
                             Data = proxy[{id:3},{text:sampleText3}]]
               realmResult6:sampleText2
```
&ensp;`realm.where<Data>()`でデータを探し、そこから条件などを付けて取り出したいデータを検索します。データをすべて出したいときは`.findAll()`を記述すればデータを取り出してくれます。`.findFirst()`は条件下で一番最初のデータを取り出してくれます。
>`.eqoulTo()`のときだけ`.toInt()`がついているのはコンパイラエラーを防ぐものです。
>調べた限り修正方法は見当たらなかったので、公式が修正するまではこのままでしょう。

####条件コード一覧
&ensp;`.eqoulTo("フィールド名", 値)`
:フィールドから値が同じデータを抽出する
&ensp;`.notEqoulTo("フィールド名", 値)`
:フィールドから値が違うデータを抽出する
&ensp;`.greaterThan("フィールド名", 値)`
:フィールドから指定した値より大きいデータを抽出する
&ensp;`.greaterThanOrEqualTo("フィールド名", 値)`
:フィールドから指定した値以上のデータを抽出する
&ensp;`.lessThan("フィールド名", 値)`
:フィールドから指定した値より小さいデータを抽出する
&ensp;`.lessThanOrEqualTo("フィールド名", 値)`
:フィールドから指定した値以下のデータを抽出する
&ensp;`.beginsWith("フィールド名", "値")`
:指定した値から始まる文字列を含むデータを抽出する
&ensp;`.endsWith("フィールド名", "値")`
:指定した値で終わる文字列を含むデータを抽出する
&ensp;`.contains("フィールド名", "値")`
:指定した値を含む文字列を含むデータを抽出する

# データの更新
```kotlin:MainActivity.kt
class MainActivity() : AppCompatActivity() {

        //省略

        realm = Realm.getDefaultInstance()

        insertData()

        selectData()

        updateData()

    }

    private fun updateData() {
        val data = realm.where<Data>()
                .lessThanOrEqualTo("id",2)
                .contains("text", "1")
                .findFirst()
        realm.executeTransaction {
            data?.text = "サンプルテキスト１"
        }
        Log.e("RealmUpdate", "updateRealm:${realm.where<Data>().equalTo("id", 1.toInt()).findFirst()}")
    }

    //省略
    
}
```
```kotlin:Logcast
E/RealmUpdate: updateRealm:Data = proxy[{id:1},{text:サンプルテキスト１}]
```
データの検索と手順は最初は同じです。検索したデータに`?.フィールド名`または`!!.フィールド名`を後ろにつけて更新したいデータを代入します。ただし、上記のようにさいごのfindの部分を`.findFirst()`で記述するときはとくに心配しなくても大丈夫ですが、`.findAll()`を使うときは、下記にように記述しないといけません。

```kotlin:MainActivity.kt
private fun updateData() {
    val data = realm.where<Data>()
            .lessThanOrEqualTo("id",2)
            .contains("text", "1")
            .findAll()
    realm.executeTransaction {
        data[0]?.text = "サンプルテキスト１"
    }
    Log.e("RealmUpdate", "updateRealm:${realm.where<Data>().equalTo("id", 1.toInt()).findFirst()}")
}
```
# データの削除

```kotlin:MainActivity.kt
class MainActivity() : AppCompatActivity() {

        //省略

        realm = Realm.getDefaultInstance()

        insertData()

        selectData()

        updateData()

        deleteData()

    }

    private fun deleteData() {
        realm.beginTransaction()
        val target = realm.where<Data>().findAll()
        target.deleteFromRealm(1)
        Log.e("RealmDelete", "deletePartRealm:${realm.where<Data>().findAll()}")
        target.deleteAllFromRealm()
        Log.e("RealmDelete", "deleteAllRealm:${realm.where<Data>().findAll()}")
        realm.commitTransaction()
    }

    //省略
    
}
```

```kotlin:Logcast
E/RealmDelete: deletePartRealm:[Data = proxy[{id:1},{text:サンプルテキスト1}], 
                                Data = proxy[{id:3},{text:sampleText3}]]
E/RealmDelete: deleteAllRealm:[]
```
今回は`realm.executeTransaction { }`の代わりに`realm.beginTransaction()`と`realm.commitTransaction()`を使いました。使い方は、データの書き込み・更新をするコードをbeginとcommitで挟むだけです。役割は前者と変わりません。

#最後に
今回の記事はどうでしたでしょうか？伝わりにくい場所があったかもしれませんが、RealmはSQLとは違ってKotlinで記述できるので簡単に導入できるのではないでしょうか。質問等ありましたら気軽にコメントください。

##参考文献
[Realmの基本的な使い方まとめ in Kotlin](https://qiita.com/KIRIN3qiita/items/2cd7bd4bc7c12e59e223)
[Kotlin + Realmでシンプルなメモ帳アプリを作ってみた](https://qiita.com/orimomo/items/bc1f2065f44104176f7f)
[Kotlin + Android でRealmをつかってみた。](https://qiita.com/Juju_62q/items/eaf55c7722b826156404)