ホーム 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はやっぱりいいです。楽だし書きやすい。

記事を共有

最近人気な記事