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


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

作ったもの

ソースコードはこちらから↓
https://github.com/orimomo/realm-sample-app

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

Realmの導入

Gradleの設定

以下を追加。

プロジェクト/build.gradle
dependencies {
        classpath "io.realm:realm-gradle-plugin:6.0.2"
    }
app/build.gradle
apply plugin: 'kotlin-kapt' // なければこれも追加
apply plugin: 'realm-android'

Realmの初期化

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

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の作成

こんな感じ。

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

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

Fragmentから呼び出す

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

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にデータを書き込むようにする。

    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からデータを読み込んで画面に表示するようにする。

    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の長押し→入力画面で「保存」ボタンを押したら、データが更新されるようにする。

    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にある「削除」ボタンを押したら、データが削除されるようにする。

    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

stetho-realmの導入

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

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

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

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を初期化するコードを追加。

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をクリック。

別ウィンドウが立ち上がって、Resources > Web SQL > defauld.realm からデータが確認できる。

おわりに

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

参考文献