Kotlin + Realmでシンプルなメモ帳アプリを作ってみた

Realmを使ってみたくて、CRUDを実装したシンプルなメモ帳アプリを作ってみた。
学んだことやハマったことをメモしておく。
https://realm.io/

# 作ったもの
ソースコードはこちらから↓
https://github.com/orimomo/realm-sample-app

![Jan-04-2020 23-19-15.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112818/494a5aca-843a-3edd-a230-036aef6e9d1a.gif)

* **Create** : fabボタン押下→ダイアログ入力で新規作成
* **Read** : 起動時・データ変更時に読み込み
* **Update** : Itemの長押し→ダイアログ入力で更新
* **Delete** : Itemの削除ボタン押下で削除

# Realmの導入
## Gradleの設定
以下を追加。

```groovy:プロジェクト/build.gradle
dependencies {
        classpath "io.realm:realm-gradle-plugin:6.0.2"
    }
```

```groovy:app/build.gradle
apply plugin: 'kotlin-kapt' // なければこれも追加
apply plugin: 'realm-android'
```

## Realmの初期化
カスタムアプリケーションクラスの中で以下を追加。

```kotlin:Application
 override fun onCreate() {
        super.onCreate()

        Realm.init(this)
        val config = RealmConfiguration.Builder()
             // .deleteRealmIfMigrationNeeded() 
            .build()
        Realm.setDefaultConfiguration(config)
}
```

これでRealmのデフォルト設定が完了し、`default.realm`というファイルにデータが保存されるようになる。

`.deleteRealmIfMigrationNeeded()`はモデルを変更した後など、必要に応じてDBを削除・再構築してくれるものなのだが（変更後にこのコードがないとアプリがクラッシュする）、
入っていると後述する`stetho-realm`が使えなかったので、一旦コメントアウトしておき、必要なときに コメントを外して有効にするようにする。

## Modelの作成
こんな感じ。

```kotlin
open class ListObject: RealmObject() {
    @PrimaryKey
    var id : Int? = null
    @Required
    var title = ""
}
```

`RealmObject()`を継承する点と、プライマリーキーが必要な点に注意。
`@PrimaryKey`は主キーで、 `@Required`はnullを許容しない必須項目のアノテーション。


## Fragmentから呼び出す
Fragment内でメンバ変数を作ってインスタンス化しておく。
また、Realmオブジェクトの削除処理も入れておく。

```kotlin
class ListFragment : Fragment() {
    // メンバ変数として持っておく
    private lateinit var realm: Realm

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // インスタンス化しておく
        realm = Realm.getDefaultInstance()
        return binding?.root
    }

    override fun onDestroy() {
        super.onDestroy()
        // Realmオブジェクトの削除
        realm.close()
    }
}
```


# RealmでCRUD
## Create
入力画面で「保存」ボタンを押したら、Realmにデータを書き込むようにする。

```kotlin
    private fun create(title: String) {
        // プライマリーキーを取得
        val savedId = sharedPreferences.getInt(ViewModel.KEY.REALM_ID.name, id)
        val id = savedId + 1

        // トランザクションして書き込む
        realm.executeTransaction { realm ->
            val obj = realm.createObject(ListObject::class.java, id)
            obj.title = title
        }

        // プライマリーキーを保存
        sharedPreferences.edit().putInt(ViewModel.KEY.REALM_ID.name, id).apply()
    }
```

プライマリーキーのインクリメントはとりあえず一番簡単な方法ですることとし、sharedPreferencesに前回書き込んだ番号を保存しておき、今回書き込むときに一つ番号を上げるようにした。

## Read
起動時と、データ変更時に、Realmからデータを読み込んで画面に表示するようにする。

```kotlin
    private fun read() {
        // 全件取ってきて、降順にソートする
        val all = realm.where(ListObject::class.java).findAll()
        val sortedAll = all.sort("id", Sort.DESCENDING)

        // RecyclerViewに表示する
        sortedAll.forEach { obj ->
            groupAdapter.add(ListItem(obj, viewModel))
        }
    }
```

データの変更は発生しないので、トランザクションは不要。

## Update
RecyclerViewのItemの長押し→入力画面で「保存」ボタンを押したら、データが更新されるようにする。

```kotlin
    private fun updateRealm(id: Int, newTitle: String) {
        // プライマリーキーをもとに該当のデータを取得
        val target = realm.where(ListObject::class.java)
            .equalTo("id",id)
            .findFirst()

        // トランザクションして更新をかける
        realm.executeTransaction {
            target?.title = newTitle
        }
    }
```

## Delete
RecyclerViewのItemにある「削除」ボタンを押したら、データが削除されるようにする。

```kotlin
    private fun deleteRealm(id: Int) {
        // プライマリーキーをもとに該当のデータを取得
        val target = realm.where(ListObject::class.java)
            .equalTo("id",id)
            .findAll()

        // トランザクションして削除
        realm.executeTransaction {
            target.deleteFromRealm(0)
        }
    }
```

# stetho-realmを使ったデータの確認
ちゃんとデータが保存されたのかを確認するために、stetho-realm を使ってみた。
stetho-realm についての概要は下の記事にまとめている。
[Realmのデータを確認するツール・ライブラリまとめ - Qiita](https://qiita.com/orimomo/items/79a91639c277fa6721d4#stetho-realm)



## stetho-realmの導入
下記が本家なのだが、READMEに書いてある通りに設定してもうまく行かなかった。
https://github.com/uPhyca/stetho-realm

理由はRealm3.7.1以上のバージョンに対応していないため。
そこでコミュニティが対応している下記リポジトリを使う必要があった。
https://github.com/wickedev/stetho-realm

まずはREADMEの通り、下記をGradleに追加する。

```groovy
repositories {
    maven {
        url 'https://github.com/uPhyca/stetho-realm/raw/master/maven-repo'
    }
}

dependencies {
    compile 'com.facebook.stetho:stetho:1.5.0'
    compile 'com.uphyca:stetho_realm:2.1.0'
}
```

カスタムアプリケーションのRealmの初期化の下に、stetho-realmを初期化するコードを追加。

```kotlin:Application
 override fun onCreate() {
        super.onCreate()
        // Realmの初期化（省略）

        // stetho-realmの初期化
        Stetho.initialize(
            Stetho.newInitializerBuilder(this)
                .enableDumpapp(Stetho.defaultDumperPluginsProvider(this))
                .enableWebKitInspector(RealmInspectorModulesProvider.builder(this).build())
                .build())
    }
}
```

## Chrome上でデータを確認する
アプリを起動して（実機でもエミュレータでも可）、`chrome://inspect/#devices`をChromeで検索する。
Developer Toolsが開くので`inspect`をクリック。
<img width="560" alt="スクリーンショット 2020-01-01 15.00.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112818/95ad3454-3360-a676-4664-2390e5a0ca41.png">

別ウィンドウが立ち上がって、Resources > Web SQL > defauld.realm からデータが確認できる。
<img width="1062" alt="スクリーンショット 2020-01-03 19.48.08.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112818/7f1d0d24-86f2-f967-30eb-a6f68b45a388.png">

# おわりに
Realmの操作は簡単で、特にハマったりもせず、サクサクと作ることができた。良い。
今回はRealmでCRUDをしてみることを目的にしていたので、それ以外の部分を雑に書いてしまっているのが反省点（ViewModelに持っていくべきコードを持っていってなかったり、ボタンを非活性にすべきところをしてなかったりする）。
RealmはSwiftでも同じようなコードで使えるとのことなので、今度はiOSでも使ってみたいと思う。


## 参考文献
* [Realm: Create reactive mobile apps in a fraction of the time](https://realm.io/docs/java/latest/#kotlin)
* [KotlinでRealmを使ってみた - Qiita](https://qiita.com/yusukeyama/items/ba0ebb80b66f286b3647#realmobject%E3%82%92%E7%B6%99%E6%89%BF%E3%81%97%E3%81%9F%E3%82%AF%E3%83%A9%E3%82%B9%E3%82%92%E4%BD%9C%E3%81%A3%E3%81%A6%E3%83%86%E3%83%BC%E3%83%96%E3%83%AB%E3%81%A8%E3%81%8B%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B)
* [Android KotlinでRealmを使う - BANBARA STUDIO](http://banbara-studio.hatenablog.com/entry/2018/03/22/003021)
* [Kotlin + Android でRealmをつかってみた。 - Qiita](https://qiita.com/Juju_62q/items/eaf55c7722b826156404#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E8%AA%AD%E3%81%BF%E5%8F%96%E3%82%8A%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)
* [realmのデータをStethoとstetho-realmでChromeで確認する - LOCAL-C BLOG](https://blog.local-c.com/archives/2496)