
自分の過去のツイートをカレンダーでふりかえることができるTwitCalというツイッタークライアントアプリを作っています。このアプリではデータのキャッシュ、ロードにRealmというモバイル向けDBを利用しています。
Realmは高速なので、Listviewで3000件のデータを表示する場合もほとんどディレイはありません。 ただし、格納されているデータが10000件くらいになった場合に、速度の保証はありませんし、Out of Memoryも心配です。そこで、必要な分だけデータをロードし、下までスクロールしたら追加でデータを取得するようなListViewの実装をこころみました。果たしてRealmでこの実装方法が適切なのかどうか不明なので、Qiitaではなくブログに投下します。
ツイートデータ取得の課題点
各ツイートにはidがふられています。このidはツイート順に増加するものですが、世の中の全てのツイートに与えられるものなので、ランダムに増加するidといえます。そのため、データを20件ずつ取得したいと考えたときには、指定したidより大きいidのうち上位20件(ただし20件未満もOK)を取り出す必要があります。
MySQLであれば式(1)の様な構文で取得できます。
Select * from tbl where id < (指定したid) order by id limit 0,20; //式(1)
limit句が使えない場合でも、サブクエリを指定して式(2)のように取得もできると考えられます。
Select * from (Select * from tbl where id <(指定したid) order by id) where rownum <=20; //式(2)
一方で、Realmにはそのようなメソッドはないです。しかしながら、結果(RealmResults)に対してクエリを実行できるので、式(2)のような処理を自前で作ることは簡単そうです。
実装の検討
検討のために以下のようなデータを用意し、名前を表示するためのリストビューを作りました。
- idと名前のjsonデータが500件
- idは0-9999の間でランダムに振られている
実装は以下のように行いました。
1.初回データ取得
final static int RESULT_NUM = 20;
public List<SimpleData> getInitSimpleData() {
RealmResults<SimpleData> partialResults;
mRealm = Realm.getInstance(mContext);
RealmQuery<SimpleData> realmQuery = mRealm.where(SimpleData.class);
//データの取得
RealmResults<SimpleData> realmResults = realmQuery.findAll();
realmResults.sort("id");
int firstId = realmResults.get(0).getId(); //(1)
int lastId = realmResults.get(realmResults.size() > RESULT_NUM ? RESULT_NUM : realmResults.size() - 1).getId(); //(2)
partialResults = realmResults.where().between("id", firstId, lastId).findAll();
partialResults.sort("id");
LocalStorage.putInt(Const.LAST_ID, lastId); //(3)
return partialResults;
}
(1)データインサート後の初回データ取得は、最初のidが不明なので、ソートしたデータの最初のid(firstId)を取得します。
(2)取得したいデータ範囲の最初のfirstIdから20件目のid(lastId)を取得(取得したいデータが20件未満の場合は最後のid)
(3)firstId,lastIdの範囲でデータを所得し、last_idはプリファレンスに保存しておきます。
2.初回以降のデータ取得
public List<SimpleData> getNextData() {
RealmResults<SimpleData> partialResults;
mRealm = Realm.getInstance(mContext);
int lastId = LocalStorage.getInt(Const.LAST_ID); //(4)
if (lastId < 0) return null;
RealmQuery<SimpleData> realmQuery = mRealm.where(SimpleData.class);
RealmResults<SimpleData> realmResults = realmQuery.greaterThan("id", lastId).findAll();//(5)
if (realmResults.size() == 0) {
Log.d(TAG, "¥¥no more data!");
return null;
}
realmResults.sort("id");
int nextLastId = realmResults.get(realmResults.size() > RESULT_NUM ? RESULT_NUM : realmResults.size() - 1).getId();//(6)
partialResults = realmResults.where().between("id", lastId, nextLastId).findAll();
partialResults.sort("id");
LocalStorage.putInt(Const.LAST_ID, nextLastId); // (7)
return partialResults;
}
(4)プリファレンスに保存しておいた前回のlastIdを取得します。
(5)lastIdよりも大きいidを取得し、ソートします。式(2)のサブクエリに当たる部分です。
(6)(2)と同様の処理で次のnextLastIdを取得します。
(7)lastIdからnextLastIdのデータを取得。lastIdを次の取得範囲のidとして保存
ソースコード
ソースコードはhttp://github.com/tomoima525/RealmEndlessViewになります。
EndlessViewはこちらのソースを参考に、データの最後までに来た場合の処理などを追加しています。
当初はデータをエンドレスにスクロールできるだけのサンプルだったのですが、MVP +ドメインモデルにチャレンジしたところ、結構大げさなサンプルになってしまいました。今回やりたかったことの実装はdata/repository/SimpleDataRepositoryImpl配下にあります。
当初のサンプルも一応OldArchitechure配下にあるので、違いをご覧になると良いかもです。
MVPって何さ、という方は、
kgmyshinさんのkgmyshin/Android-arch , これからの「設計」の話をしよう
konifarさんのAndroidではMVCよりMVPの方がいいかもしれない
をご覧になると、理解が深まると思います。平たく言うとビジネスロジックをActivityやFragmentから分離してpure javaにするためのアーキテクチャです。
Realm部分の実装についてのご意見だけでなく、DI周りで自分も不慣れな部分があるので、気づいた点があればPRいただけるととても嬉しいです!