【Android】Realmを0.87.4から1.0.0にUpgradeしたときの備忘録


Google様から[古いOpenSSL使ってるアプリは2016/07/11以降、Playに公開／更新させないよ](https://support.google.com/faqs/answer/6376725)というお達しもあり、Realmでは0.91.0未満では古いOpenSSLを使っているので、これを期に最新（1.0.0）にUpgradeしたときの備忘録です。
（古いOpenSSLでは脆弱性がありましたが、Realm内では脆弱性を含む機能を使用していないため、最近まで更新していなかったらしい）

[本家のGetting Started](https://realm.io/docs/java/latest/#getting-started)を参考に、0.87.4 => 1.0.0にUpgradeする際の手順です。（部分的にしか使ってなかったのでたぶん変更箇所は少ない方だと思います）
[Change Log](https://github.com/realm/realm-java/blob/master/CHANGELOG.md)をみると結構たくさんBreaking changesがあるけど、頑張ろう！



# 手順
- gradleで参照しているRealmのバージョンを更新
- ビルドエラーを潰す
- 実際に動かして、想定外の動きをする箇所を潰す

※基本的にはエラーや想定外の動作が出た箇所の実装を[Change Log](https://github.com/realm/realm-java/blob/master/CHANGELOG.md)と[本家ドキュメント](https://realm.io/docs/java/latest/)を見ながら変更／修正していくことの繰り返しです。


# build.gradleの変更
projectレベルのbuild.gradleにdependenciesを追加([0.88.0/Breaking change](https://github.com/realm/realm-java/blob/master/CHANGELOG.md#breaking-changes-3))


```build.gradle
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:1.0.0"
    }
}

```

appレベルのbuild.gradleから0.87.4の記述を削除して1.0.0の記述を追記

```app/build.gradle
apply plugin: 'realm-android'

dependencies {
  // compile 'io.realm:realm-android:0.87.4'
}
```

# ビルドエラーの解消

## リストAPI削除：```RealmResults.removeLast()```
- メソッドがなくなったので```RealmResults.deleteLastFromRealm()```に変更([0.91.0/Breaking change](https://github.com/realm/realm-java/blob/master/CHANGELOG.md#0910))

## メソッドのthrow追加：```Realm.migrateRealm(RealmConfiguration configuration, RealmMigration migration)```

- ```FileNotFoundException```をthrowするようになった([0.88.0/Breaking change](https://github.com/realm/realm-java/blob/master/CHANGELOG.md#breaking-changes-3))
	- そもそも```RealmConfiguration```生成時にmigrationを設定する方法に変更して[ベストプラクティス](https://realm.io/jp/docs/java/latest/#realm-8)に従って```Realm.getDefaultInstance()```を使うよう変更

【変更前】

```RealmUtil.java
public static Realm getInstance(Context context) {
    Realm realm;

    RealmConfiguration config = new RealmConfiguration.Builder(context)
            .schemaVersion(JmtyMigration.REALM_SCHEMA_VERSION)
            .build();
    try {
        realm = Realm.getInstance(config);
    } catch (RealmMigrationNeededException ex) {
        Realm.migrateRealm(config, new JmtyMigration());
        realm = Realm.getInstance(config);
    }

    return realm;
}
```


【変更後】


```MyApplication.java
@Override
public void onCreate() {
    super.onCreate();
    sContext = getApplicationContext();

    // Realmセットアップ
    Realm.setDefaultConfiguration(RealmUtil.createRealmConfiguration(sContext));
}
```


```RealmUtil.java
/**
 * RealmのConfigurationを生成する
 *
 * @param context
 * @return RealmConfiguration
 */
public static RealmConfiguration createRealmConfiguration(Context context){
    return new RealmConfiguration.Builder(context)
            .schemaVersion(JmtyMigration.REALM_SCHEMA_VERSION)
            .migration(new JmtyMigration())
            .build();
}
```


# 実際に動かして、想定外の動きをする箇所を潰す

## ソート変更：```RealmResults.sort()```
- エラーは出ず、ソートが効かなくなる
- ```RealmResults```そのものへの操作ではなく、ソートしたオブジェクトを返却するようになった（[0.89.0/Breaking changes](https://github.com/realm/realm-java/blob/master/CHANGELOG.md#breaking-changes-2)）

【変更前】

```ArticleListFragment.java
Realm realm = Realm.getDefaultInstance();
RealmQuery<Article> query = realm.where(Article.class);
results = query.findAll();
results.sort("updatedAt", Sort.DESCENDING);
```

【変更後】

```ArticleListFragment.java
Realm realm = Realm.getDefaultInstance();
RealmQuery<Article> query = realm.where(Article);
results = query.findAll().sort("updatedAt", Sort.DESCENDING);
```

## String型のprimary keyを持ったRealmファイルが開けない
- 以前のversionで作られたRealmファイルを参照しようとしたときにエラー
- Primary Keyのフィールドがnullを許容する型（String, Byte, Short, Integer, and Long）だと```RealmObjectSchema.setNullable()```か```@Required```のアノテーションを使わないとエラーになるようになった。
	- Schema versionを上げて```RealmObjectSchema.isRequired(String)```を実施（[0.89.0/Breaking changes](https://github.com/realm/realm-java/blob/master/CHANGELOG.md#breaking-changes-2)）
		- nullを許容するなら```RealmObjectSchema.isNullable(String)```を実施する（praimary keyでnull許容なんてケースあるのか？）

```logcat：エラーログ抜粋
FATAL EXCEPTION: main
Process: com.snowmonkey.sample, PID: 8761
io.realm.exceptions.RealmMigrationNeededException: @PrimaryKey field '{フィールド名}' does not support null values in the existing Realm file. Migrate using RealmObjectSchema.setNullable(), or mark the field as @Required.
以下略
```

【変更前】

```data/realm/Article.java
public class Article extends RealmObject {

    @PrimaryKey
    private String key;
    
    // 以下略
}
```

【変更後】

```realm/data/Article.java
public class Article extends RealmObject {

    @PrimaryKey @Required
    private String key;
    
    // 以下略
}
```

```realm/MyMigration.java
public class MyMigration implements RealmMigration {
    /** Current Schema Version. */
    public static final long REALM_SCHEMA_VERSION = 1;

    @Override
    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
        Log.d(getClass().getSimpleName(), "Migrate Realm Schema old[" + oldVersion + "] to new[" + newVersion + "]");
        if (oldVersion == 0) {
            // Migrate from v0 to v1
            migrate0To1(realm);
            oldVersion++;
        }

        if (oldVersion < newVersion) {
            throw new IllegalStateException(String.format("Migration missing from v%d to v%d", oldVersion, newVersion));
        }
    }

    /**
     * Migrate from version 0 to version 1.
     * At version 1, add @Required annotation for Primary Key(historyForShow) at SearchHistory Table.
     *
     * @param realm
     */
    private void migrate0To1(DynamicRealm realm) {
        RealmObjectSchema searchHistorySchema = realm.getSchema().get(SearchHistory.class.getSimpleName());
        searchHistorySchema.isRequired("key");
    }
}
```

# 所感的な何か
うちはまだそれほどがっつりRealm使っていなかったのでさくっと半人日もかからずUpgrade出来てよかったよかった。
というかちゃんと本家の更新を追ってないと差分が多くなって大変そうなので今後はチラチラ追っていこう。。。
（とはいえ「安定版の1.0.0になったから今後はBreaking changeは控える」って中の人が言ってたから大丈夫かな…？）

早く[```RealmConfiguration.Builder.assetFile()```](https://realm.io/docs/java/1.0.0/api/io/realm/RealmConfiguration.Builder.html#assetFile-android.content.Context-java.lang.String-)使ってアプリ初回起動時にやってたマスタデータ取得の時間を短縮したいぞおおおおおおお！！！

[![LGTM](http://lgtm.in/p/KDeWgZq4q)](http://lgtm.in/i/KDeWgZq4q)