レイヤードアーキテクチャでRealmを使う

## はじめに
昨今のAndroid開発ではActivityやFragmentのマッチョ化を防止するために、CleanArchitectureやMVP、MVVMなどのレイヤードアーキテクチャを採用している人が多いと思います。

Realmは素晴らしいツールで大好きなのですが、ドキュメントの[ベストプラクティス](https://realm.io/docs/java/latest/#best-practices)に従おうとするとガッツリと画面のライフサイクルに依存しているため、設計に与える影響は大きいなぁと感じていました。

これからActivity/Fragmentが*Realmオブジェクトを保持することなく*使うための一例を紹介したいと思います。
全ての人にオススメできるTipsではないかもしれませんが、参考にしていただけたら幸いです。

## 3行まとめ
1. カスタムApplicationクラスでUIスレッド用のRealmオブジェクトを保持する
2. DB層のDaoパターンなどで①のRealmオブジェクトを渡す(Dagger2など)
3. ①のRealmオブジェクトは**closeしない**

## 詳細

断片的なコードで申し訳ないですが、カスタムApplicationクラスのRealmオブジェクトを、Daoオブジェクトで使用する流れです。

```java:MyApplication
public class MyApplication extends Application {
    private Realm realm;
    public Realm getRealm() {
        return realm;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        Realm.setDefaultConfiguration(new RealmConfiguration.Builder().name("sample.realm")).build());
        /*onCreate時に生成*/
        realm = Realm.getDefaultInstance();
    }
}
```

```java:AppModule
@Module
public class AppModule {
    private Application app;

    public AppModule(Application app) {
        this.app = app;
    }

    @Provides
    public Application provideApplication() {
        return app;
    }

    @Provides
    public Context provideContext() {
        return app.getApplicationContext();
    }
}
```

```java:InfraModule
@Module
public class InfraModule {
    @Singleton
    @Provides
    public Realm provideRealm(Application app) {
        return ((MyApplication) app).getRealm();
    }
}
```

```java:SampleDao
public class SampleDao {
    private final Realm realm;

    @Inject
    public SampleDao(Realm realm) {
        this.realm = realm;
    }

    // do something
}
```

これで完全にActivity/FragmentがRealmに関心を持たなくていいようになりました。

## おまけ
Dagger2を導入していない場合は単純にカスタムアプリケーションでstaticを使ってもいいと思います。


```java:MyApplication
public class MyApplication extends Application {
    private static Realm realm;
    public static Realm getRealm() {
        return realm;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        Realm.setDefaultConfiguration(new RealmConfiguration.Builder().name("sample.realm")).build());
        /*onCreate時に生成*/
        realm = Realm.getDefaultInstance();
    }
}
```

```java:SampleDao
public class SampleDao {
    private final Realm realm;

    /*UI thread*/
    public SampleDao() {
        this.realm = MyApplication.getRealm();
    }

    /*worker thread*/
    public SampleDao(Realm realm) {
        this.realm = realm;
    }

    // do something
}
```

## Applicationクラスで保持したRealmオブジェクトはcloseしなくていいのか
現在業務で公開している４つのアプリでは有難いことにRealmに関するエラーはほとんどありません。(nativeのエラーが4件ほど)

ただそれだけでは非常に不安だったので、*RealmのSlackチャンネル*で質問してみました。

**結論としては、問題ないとのことでした。**

*注意が必要なのは以下の２点のケース*  
1. Realm.compactRealm()などいくつかの特殊なメソッドは全Realmインスタンスがcloseされているときしか使えない  
2. Instant Runとの相性が良くない


Realmはトランザクションがコミットされた段階ですべてのデータをファイルに書き込んでいるため、アプリ終了時にcloseがなくても、データを失うことはないそうです。

close漏れが問題になるのには、Realm.compactRealm()などいくつかの特殊なメソッドが使えないことと、**Looperのないスレッドでは古いデータを見続けてしまう**ということでした。

@zaki50さんの説明が大変わかりやすかったので転記させていただきます。

```
まずRealmの基本として、データはトランザクションのcommitを単位としてバージョン管理されています。
どのバージョンのデータが見えるかはスレッドごとに管理されています。

Looperのあるスレッドの場合、そのスレッド用のRealmインスタンスがopenされている状態でも、
Looperのイベント処理の過程で自動的に最新バージョンのデータが見えるように更新されます。

Looperがないスレッドの場合、RealmクラスのwaitForChange()が呼ばれるまでRealmインスタンスが
オープンされた時点での最新バージョンを見続けます。

なので、Looperがないスレッドであっても適切にclose()していれば次にRealm.getInstance()したさいに
新たにRealmインスタンスが作られるのでその時点での最新バージョンが見えます。

close()していないと、Realmインスタンスはキャッシュされたままなので、getInstance()したときに古いバージョン
のデータが見える状態のRealmインスタンスが返ってくることになります。

古いバージョンのデータが見えるということ自体がアプリにとって問題になることがほとんどですし、古いバージョン
をみているRealmインスタンスがあると内部の処理としてその古いバージョンから最新バージョンまでのすべての
変更差分をデータベース内に保持する必要がでてきます。

このことがデータベースファイルの肥大化の原因になって、OOMなどによるクラッシュを発生させます。

通常は古いバージョンのデータを見ているRealmインスタンスはそれほど長い時間をまたずに最新バージョンのデータ
を見るようになるので、古いバージョンのために使われていた領域は新たなバージョンのデータによって再利用されます。
```

## 雑感
### Realmオブジェクトを無規則に散りばめない方がいい
`Realm.getDefaultInstance()`を呼び出す箇所を、カッチリ決めた方が良いと思いました。色々な層に無規則に散りばめられると管理が複雑になりclose漏れや、`IllegalStateException`を生む温床になります。
UIスレッド用では`カスタムApplicationで保持`か[ベストプラクティス](https://realm.io/docs/java/latest/#best-practices)で管理し、IntentSerivceやLooperがないThreadやRxJavaなどの**ワーカースレッドではtry-finallyで確実にcloseする**と楽だと思います。
[RealmUtil.dumpRealmCount()](https://gist.github.com/zaki50/9d8f0eacf6be05a66c372c0002164e3c)で漏れ数チェック`Realm Instance = total: xxx instance(s)`
#### realmfieldnameshelperを使う
カラム名を手入力で書いていている方はこちらの[ライブラリ](https://github.com/cmelchior/realmfieldnameshelper)がオススメです。

#### Fine-grained Collection Notification最高


サンプルもなく雑ですが、以上です