読者です 読者をやめる 読者になる 読者になる

うさがにっき

読書感想文とプログラムのこと書いてきます

realmでテーブル構造(モデルクラス)の更新、データ移行方法

realm Android

概要

realmで何も考えずにモデルクラスを更新してアプリをアップデートすると以下のエラーを吐いて落ちる

io.realm.exceptions.RealmMigrationNeededException: RealmMigration must be provided
  at io.realm.BaseRealm.migrateRealm(BaseRealm.java:568)
  at io.realm.Realm.migrateRealm(Realm.java:1064)
  at io.realm.Realm.migrateRealm(Realm.java:1053)
  at io.realm.Realm.create(Realm.java:253)
  at io.realm.Realm.getInstance(Realm.java:219)
  at io.realm.Realm.getInstance(Realm.java:182)

realmでのモデルクラスの更新方法をrealmの中の人に教えてもらったのでそのまとめ

詳細

エラーメッセージにあるようにRealmMigrationクラスを実装したMigrationクラス的なものを作成する必要がある
その中でversionで管理していく
サンプルソース
realm-java/Migration.java at v0.84.1 · realm/realm-java · GitHub

全体的な構成としてこんな感じになる

public class Migration implements RealmMigration {
    @Override
    public long execute(Realm realm, long version) {
        // Migrate from version 0 to version 1
        if (version == 0) {
           ・・・・・・・
            version++;
        }

        // Migrate from version 1 to version 2
        if (version == 1) {
            ・・・・・・
            version++;
        }

        // Migrate from version 2 to version 3
        if (version == 2) {
           ・・・・・・
            version++;
        }
        return version;
    }

カラムを追加、削除してデータ移行したいとき

PersonテーブルからfirstName,lastNameカラムを削除してfullNameカラムを追加し、fullNameにfirstNameとlastNameを組み合わせたものをデータ移行

            Table personTable = realm.getTable(Person.class);

            long fistNameIndex = getIndexForProperty(personTable, "firstName");
            long lastNameIndex = getIndexForProperty(personTable, "lastName");
            // カラム追加
            long fullNameIndex = personTable.addColumn(ColumnType.STRING, "fullName");
            // データ移行
            for (int i = 0; i < personTable.size(); i++) {
                personTable.setString(fullNameIndex, i, personTable.getString(fistNameIndex, i) + " " +
                        personTable.getString(lastNameIndex, i));
            }
            // カラム削除
            personTable.removeColumn(getIndexForProperty(personTable, "firstName"));
            personTable.removeColumn(getIndexForProperty(personTable, "lastName"));

    ・・・・・・

    private long getIndexForProperty(Table table, String name) {
        for (int i = 0; i < table.getColumnCount(); i++) {
            if (table.getColumnName(i).equals(name)) {
                return i;
            }
        }
        return -1;
    }

新しいモデルクラスPetの追加、PersonにRealmListを追加

            Table personTable = realm.getTable(Person.class);
            // pet作成
            Table petTable = realm.getTable(Pet.class);
            long nameColumnIndex = petTable.addColumn(ColumnType.STRING, "name");
            long typeColumnIndex = petTable.addColumn(ColumnType.STRING, "type");
            // petへの参照作成
            long petsIndex = personTable.addColumnLink(ColumnType.LINK_LIST, "pets", petTable);
            // personのfullnameが"JP McDonald"なら対応するpetを追加
            long fullNameIndex = getIndexForProperty(personTable, "fullName");
            for (int i = 0; i < personTable.size(); i++) {
                if (personTable.getString(fullNameIndex, i).equals("JP McDonald")) {
                    long rowIndex = petTable.addEmptyRow();
                    petTable.setString(nameColumnIndex, rowIndex, "Jimbo");
                    petTable.setString(typeColumnIndex, rowIndex, "dog");
                    personTable.getUncheckedRow(i).getLinkList(petsIndex).add(rowIndex);
                }
            }

petのtypeカラムの型変更、personのfullnameカラムをnull許可するようにする(デフォルトは@Required)

            Table personTable = realm.getTable(Person.class);
            long fullNameIndex = getIndexForProperty(personTable, "fullName");
            // fullNameをnullableに
            personTable.convertColumnToNullable(fullNameIndex);

            // 型を変更するためにintのtypeを追加し、データ移行し、古いカラム削除
            Table petTable = realm.getTable(Pet.class);
            long oldTypeIndex = getIndexForProperty(petTable, "type");
            // int型のtype追加
            long typeIndex = petTable.addColumn(ColumnType.INTEGER, "type");
            // データ移行
            for (int i = 0; i < petTable.size(); i++) {
                String type = petTable.getString(oldTypeIndex, i);
                if (type.equals("dog")) {
                    petTable.setLong(typeIndex, i, 1);
                }
                else if (type.equals("cat")) {
                    petTable.setLong(typeIndex, i, 2);
                }
                else if (type.equals("hamster")) {
                    petTable.setLong(typeIndex, i, 3);
                }
            }
            // 古いString型のtype削除
            petTable.removeColumn(oldTypeIndex);

追記
近いうちにmigration関係が刷新されるとのこと
新しくなったらまたまとめる

realm-java/Migration.java at cm-feature-dynamic-migration-api · realm/realm-java · GitHub
こんな感じになるらしい