1. morayl

    Posted

    morayl
Changes in title
+個人的なRealmクラス設計のベストプラクティス(Android)
Changes in tags
Changes in body
Source | HTML | Preview
@@ -0,0 +1,120 @@
+# はじめに
+何回かRealmを利用するアプリを作成している上で、段々とこれがベストなのではないかという設計・実装を思いついたので、ここに記そうと思う。(異論・改善は大歓迎)
+
+# 前提条件
+RealmHelperというクラスを作成する。
+Realmにアクセスするために作成。
+1RealmObjectに対して1RealmHelperを作り、そのRealmHelperがそのオブジェクトへのアクセスを担う。
+これによって、一つのクラスの肥大化を防ぎ、各Helperの責任範囲が明確になる。
+
+# 各視点での最適解を考える
+設計に当たって、2視点から必要なことを洗い出してみた。
+
+### 必要になる使い方
+Realmの利用方法にどんなものがあるかを考えてみる。
+
+1. 単発でアクセス行いたい場合
+ - 画面で1回だけinsert処理を行いたい
+ - 一度値を読み込んで、画面に表示したいetc...
+ - **この場合、helperクラスはインスタンス化せず、staticメソッドで気軽に呼び出したい。Realmのopen、closeは内部でhelper側で自動的にやってほしい。**
+
+2. RealmからRealmObjectを取得して同期したい場合
+ - RealmAdapterを使いたい
+ - リアルタイムにRealmの値を修正したいetc...
+ - **この場合、realm自体は終了したいときまでopenにしておきたい。ユーザー(Fragmentなど)は、必要に応じてcloseメソッドを呼び出したい。**
+
+### クラスの実装しやすさ
+実装は早く終わった方が良いし、共通処理はなるべくまとめたい。
+
+# 考えた構成
+- 抽象クラスとしてAbstractRealmHelperというクラスを用意する。
+ - このクラスがどのhelperクラスでもやりたい共通処理を実装している。
+ - 各子helperクラスはこれを継承すれば実装が楽+処理も統一される
+- AbstractRealmHelper
+ - コンストラクタでは、Realmを生成し、フィールドに保持する。(小クラスはこのフィールドを使ってRealmにアクセスできる)
+ - トランザクション処理を2種類用意する
+ - staticなもの
+ - 子helperがstaticメソッドを実装しやすい時用
+ - transactionを引数で受取realmを生成し、closeする
+ - 非staticなもの
+ - 子helperがインスタンス化した状態で使いたいとき用
+ - transactionを引数にとり、フィールドに持つmRealmに対してtranscationを実行する(closeは行わない)
+ - フィールドのrealmをcloseする処理を定義する
+ - 作るhelperクラスによっては不要なもの
+ - RealmObjectを継承しているクラスをジェネリクスとして定義
+ - upsertメソッドとfindAllメソッドを作成
+ - (これらは私が作った時は必要だったため作成。upsert, findAll共に必ず行うわけではないので、不要かも)
+
+# ソースコード
+```java
+public abstract class AbstractRealmHelper<T extends RealmObject> {
+
+ protected final Realm mRealm;
+
+ public AbstractRealmHelper() {
+ mRealm = getRealm();
+ }
+
+ static Realm getRealm() {
+ return Realm.getDefaultInstance();
+ }
+
+ protected static void executeTransactionOneShot(Realm.Transaction transaction) {
+ Realm realm = getRealm();
+ realm.executeTransaction(transaction);
+ realm.close();
+ }
+
+ public abstract void upsert(T t);
+
+ public abstract RealmResults<T> findAll();
+
+ protected void executeTransaction(Realm.Transaction transaction) {
+ mRealm.executeTransaction(transaction);
+ }
+
+ public void destroy() {
+ mRealm.close();
+ }
+}
+```
+
+```java
+public class ItemRealmHelper extends AbstractRealmHelper<ItemRealmObject> {
+
+ public static void insertOneShot(ItemRealmObject itemRealmObject) {
+ executeTransactionOneShot(insertTransaction(itemRealmObject));
+ }
+
+ private static Realm.Transaction insertTransaction(final ItemRealmObject itemRealmObject) {
+ return new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ realm.insertOrUpdate(itemRealmObject);
+ }
+ };
+ }
+
+ @Override
+ public void upsert(final ItemRealmObject item) {
+ executeTransaction(new Realm.Transaction() {
+ @Override
+ public void execute(Realm realm) {
+ realm.copyToRealmOrUpdate(item);
+ }
+ });
+ }
+
+ @Override
+ public RealmResults<ItemRealmObject> findAll() {
+ return mRealm.where(ItemRealmObject.class).findAll();
+ }
+}
+```
+### 課題など
+- このままだと、一度closeすると再オープン出来ない(現状そういうものとして使っている)
+- そのため、closeをするメソッドをcloseとせずdestroyにしている(気休め?)
+- 各RealmObjectでconfigurationを分けたい場合に対応できない
+ - ただ、ファイル名を分けることの利点がよく分かっていないためdefaultInstanceで良い気がしている
+ - 複数ファイルを作っても一つのRealmObjectが修正されると結局どのRealmでもMigrationが必要になると思うので
+ - 本当はconfigurationも分けようと思っていたが、staticクラスのオーバーライドが出来ないため、子クラスに実装を持たせることが出来ず手詰まってしまった。(getRealmをstaticメソッドでも、インスタンスメソッドでも両方で呼びたかった。staticメソッドを諦めれば子クラスでgetRealmをオーバーライドすればよいか)