その他
    ホーム 技術発信 DoRuby RealmでKotlinのモデルクラスを扱うときの注意点

    RealmでKotlinのモデルクラスを扱うときの注意点

    この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。


    概要

    RealmというDBがやたら早いとかでモバイルでは流行りみたいです。
    ここ数年はSQLiteからRealmに移行してる人が多いとか。
    ということでRealm触ってみました。

    サンプルコードの概要

    冒頭のgif画像が動いている様子です。
    右のボタンを押すと現在時刻をDBに突っ込んで表示します。
    左のボタンを押すとDBのレコードを全て削除します。
    fab使ってみたかったのでこんな感じにしました。
    dataListを直接触らなくてもRealmを使うと簡単にRecyclerViewに反映されます。

    以下コード

    Activity

    class MainActivity : AppCompatActivity() {
        lateinit var mRealm: Realm
        lateinit var mRecyclerView: RecyclerView
        lateinit var mAdapter: RecyclerViewAdapter
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.layout_recycler_view)
    
            // Realmのセットアップ
            val realmConfig = RealmConfiguration.Builder(baseContext)
                    .deleteRealmIfMigrationNeeded()
                    .build()
            mRealm = Realm.getInstance(realmConfig)
    
            // Realmを読み込み
            val dateList: RealmResults<CurrentTimeModel> = mRealm.where(CurrentTimeModel::class.java).findAll()
    
            // RecyclerViewのセットアップ
            mRecyclerView = findViewById(R.id.recycler_view) as RecyclerView
            mAdapter = RecyclerViewAdapter(dateList)
            val layoutManager = LinearLayoutManager(applicationContext)
            mRecyclerView.layoutManager = layoutManager
            mRecyclerView.itemAnimator = DefaultItemAnimator()
            mRecyclerView.adapter = mAdapter
            mRecyclerView.addItemDecoration(DividerItemDecoration(this))
    
            // ボタンのセットアップ
            val fabAddCurrentDateTime = findViewById(R.id.fab_add_current_date_time)
            fabAddCurrentDateTime.setOnClickListener { addCurrentDateTime() }
    
            val fabDeleteAllRecords = findViewById(R.id.fab_delete_all_records)
            fabDeleteAllRecords.setOnClickListener { deleteAllRecords() }
        }
    
        override fun onDestroy() {
            super.onDestroy()
    
            mRealm.close()
        }
    
        fun addCurrentDateTime() {
            mRealm.executeTransaction {
                val currentDateTime = mRealm.createObject(CurrentTimeModel::class.java)
                // LocalDateTime.now()がMIN_APIで使えないので、KotlinMomentを使用
                currentDateTime.currentTime = Moment().toString()
                mRealm.copyToRealm(currentDateTime)
            }
    
            mAdapter.notifyDataSetChanged()
        }
    
        fun deleteAllRecords() {
            mRealm.executeTransaction {
                mRealm.where(CurrentTimeModel::class.java)
                        .findAll()
                        .deleteAllFromRealm()
            }
    
            mAdapter.notifyDataSetChanged()
        }
    }
    

    Model

    open class CurrentTimeModel(
            open var currentTime: String = ""
    ): RealmObject() {}
    
    

    エラーが出る

    Realmを使っていて、エラーが出て詰まる場面が2つありました。
    1. 新しくモデルクラスを追加したときに、class com.list_sample.realmkotlinsample.FooModel is not part of the schema for this Realm. というエラーが出る
    2. 既に使っているモデルクラスをリネームしたり、パッケージ移動したりすると `Error:Execution failed for task ‘:app:transformClassesWithRealmTransformerForDebug’.

    javassist.NotFoundException: com.list_sample.realmkotlinsample.Model.` というエラーが出る

    エラー対策

    JavaとKotlinで書いてみて、言語によって起こるエラーとそれ以外を切り分けます。

    1. 新しくモデルクラスを作成した場合のエラー

    まずはJavaで簡単なサンプルを動かす

    #MainActivity
    package com.list_sample.realmjavasample;
    
    import android.support.v7.app.AppCompatActivity;
    import android.os.Bundle;
    import android.util.Log;
    
    import io.realm.Realm;
    import io.realm.RealmConfiguration;
    import io.realm.RealmResults;
    
    public class MainActivity extends AppCompatActivity {
        private Realm mrealm;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //Realmのセットアップ
            RealmConfiguration realmConfig = new RealmConfiguration.Builder(this)
                    .deleteRealmIfMigrationNeeded()
                    .build();
            mrealm = Realm.getInstance(realmConfig);
    
            writeRealm();
    
            RealmResults<HogeModel> mrealmResult = mrealm.where(HogeModel.class).findAll();
            Log.d("Hoge", "Realm result is " + mrealmResult.toString());
        }
    
        private void writeRealm() {
            mrealm.executeTransaction(new Realm.Transaction() {
    
                @Override
                public void execute(Realm realm) {
                    HogeModel hoge = mrealm.createObject(HogeModel.class);
                    hoge.setHoge("hoge");
    
                }
            });
        }
    }
    
    #HogeModel
    public class HogeModel extends RealmObject{
        private String hoge;
    
        public String getHoge() {
            return hoge;
        }
    
        public void setHoge(String hoge) {
            this.hoge = hoge;
        }
    }
    

    当然正常に動きます。
    これに新しくモデルクラスとして、FooModelを追加します。

    # FooModel
    public class FooModel extends RealmObject {
        public String getFoo() {
            return foo;
        }
    
        public void setFoo(String foo) {
            this.foo = foo;
        }
    
        private String foo;
    
    }
    
    
    # MainActivity
    public class MainActivity extends AppCompatActivity {
        private Realm mrealm;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            //Realmのセットアップ
            RealmConfiguration realmConfig = new RealmConfiguration.Builder(this)
                    .deleteRealmIfMigrationNeeded()
                    .build();
            mrealm = Realm.getInstance(realmConfig);
    
            writeRealm();
    
            RealmResults<FooModel> mrealmResult = mrealm.where(FooModel.class).findAll();
            Log.d("Foo", "Realm result is " + mrealmResult.toString());
        }
    
        private void writeRealm() {
            mrealm.executeTransaction(new Realm.Transaction() {
    
                @Override
                public void execute(Realm realm) {
                    FooModel foo = mrealm.createObject(FooModel.class);
                    foo.setFoo("foo");
                }
            });
        }
    }
    

    Javaではエラーが出ません。
    これをKotlinで書くと、

    # FooModel
    open class FooModel(
            open var foo: String = ""
    ): RealmObject(){}
    
    # MainActivity
    class MainActivity : AppCompatActivity() {
        lateinit var mrealm: Realm
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            // Realmのセットアップ
            val realmConfig = RealmConfiguration.Builder(this)
                    .deleteRealmIfMigrationNeeded()
                    .build()
            mrealm = Realm.getInstance(realmConfig)
    
            writeRealm()
    
            val mRealmResults: RealmResults<FooModel> = mrealm.where(FooModel::class.java).findAll()
            Log.d("Foo", "Realm results is $mRealmResults")
    
        }
    
        fun writeRealm() {
            mrealm.executeTransaction {
                val foo = mrealm.createObject(FooModel::class.java)
                foo.foo = "foo"
            }
        }
    }
    

    落ちます。
    class com.list_sample.realmkotlinsample.FooModel is not part of the schema for this Realm.

    原因と対策

    これの原因はRealmのライブラリのバージョンが古かったことです。
    今のRealmライブラリの最新バージョンが3.7.1で使っていたライブラリが1.1.0でした。

    2. 既に使っているモデルクラスをリネームしたり、パッケージ移動したりすると `Error:Execution failed for task ‘:app:transformClassesWithRealmTransformerForDebug’.

    javassist.NotFoundException: com.list_sample.realmkotlinsample.Model` というエラーが出る
    ここでもJavaとKotlinを使って切り分けます。
    不要になったHogeModelというクラスを削除してみます。

    Javaの場合

    削除しても正常に動きます。

    Kotlinの場合
    Error:Execution failed for task ':app:transformClassesWithRealmTransformerForDebug'.
    > javassist.NotFoundException: com.list_sample.realmkotlinsample.HogeModel
    

    落ちます。

    原因

    Realmはモデルクラスを作ってビルドするときに、Proxyクラスを生成します。
    HogeModelの場合はHogeModelRealmProxyというファイルが生成されます。
    これはJavaの場合もKotlinの場合も同じなんですが、Kotlinの場合RealmProxy クラスとモデルの名前やディレクトリが異なるとエラーが出ます。

    対策

    Rebuildすると直ります。一番手っ取り早いです。
    あとはRealmProxyクラスを削除しても直ります。 -> android studio でcleanするとbuild以下のファイルは全て消えるため、手動で消すのは無駄だと指摘をうけました。
    Javaでモデルクラスを作成した場合はそもそもエラーが出ないです。
    Rebuildが一番良さそうな解決方法です。

    感想

    速度が求められるモバイル開発では今後もRealmを使う場面が増えてくると思います。
    今回はざっくりした実装とエラー対策ではありましたが、これからも使い方を勉強してうまく使えるようになりたいですね。
    あと、今回の記事が間違ってるよ!って方は教えていただけると助かります。
    fabだったりRealmだったり、adb使った動画キャプチャのとり方だったり、画像の圧縮方式だったり、個人的には学びが多かったのでよかったです。
    あとkotlinはやっぱりいいです。楽だし書きやすい。