Realmの簡単な使い方メモ

# Realmメモ
全くのRealm素人ですが、
Realmハンズオン (Android) #2に参加したので、覚えたことを書いていきます。
http://realm.connpass.com/event/28919/

このドキュメントがめちゃくちゃ詳しいです。
https://realm.io/jp/docs/java/latest/

サンプルとステップはこちらにあるようです。
https://github.com/zaki50/Realm-Hands-On2_android

# 準備

/build.gradle

```groovy
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
        classpath 'io.realm:realm-gradle-plugin:0.88.3'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}
```
app/build.gradle

```groovy
apply plugin: 'com.android.application'
// androidの後である必要がある
apply plugin: 'realm-android'
```

# Applicationクラスで利用準備する

## 初期化

カスタムのApplication#onCreate内

```java
    @Override
    public void onCreate() {
        Realm.setDefaultConfiguration(new RealmConfiguration.Builder(this).build());
    }
```

## マイグレーション(フィールドの追加などのバージョンアップ)

DynamicRealmという特殊なオブジェクトを利用して、フィールドを追加する
ここに詳しいものがあるみたい
https://realm.io/docs/java/latest/#migrations
        
```java
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.setDefaultConfiguration(buildRealmConfiguration());
    }

    private RealmConfiguration buildRealmConfiguration() {
        return new RealmConfiguration.Builder(this)
                .schemaVersion(1L)
                .migration(new RealmMigration() {
                    @Override
                    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
                        if (oldVersion == 0L) {
                            final RealmObjectSchema tweetSchema = realm.getSchema().get("Tweet");
                            tweetSchema.addField("favorited", boolean.class);
                            //noinspection UnusedAssignment
                            oldVersion++;
                        }
                    }
                })
                .build();
    }
```

# エンティティの用意
RealmObjectを継承する。
プライマリーキーがある場合は@PrimaryKeyをつける。
StatusはTwitter4jのオブジェクトなので、各自入れたいものをコンストラクタで渡せば良い。

```java
package io.realm.handson2.twitter.entity;
import java.util.Date;
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
import twitter4j.Status;
public class Tweet extends RealmObject {
    @PrimaryKey
    private long id;
    private Date createdAt;
    private String screenName;
    private String text;
    private String iconUrl;
    private boolean favorited;
    public Tweet() {
    }
    public Tweet(Status status) {
//https://dev.twitter.com/overview/api/twitter-ids-json-and-snowflake
        setId(status.getId());
        setCreatedAt(status.getCreatedAt());
        setScreenName(status.getUser().getScreenName());
        setText(status.getText());
        setIconUrl(status.getUser().getProfileImageURLHttps());
        setFavorited(status.isFavorited());
    }
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
    public Date getCreatedAt() {
        return createdAt;
    }
    public void setCreatedAt(Date createdAt) {
        this.createdAt = createdAt;
    }
    public String getScreenName() {
        return screenName;
    }
    public void setScreenName(String screenName) {
        this.screenName = screenName;
    }
    public String getText() {
        return text;
    }
    public void setText(String text) {
        this.text = text;
    }
    public String getIconUrl() {
        return iconUrl;
    }
    public void setIconUrl(String iconUrl) {
        this.iconUrl = iconUrl;
    }
    public boolean isFavorited() {
        return favorited;
    }
    public void setFavorited(boolean favorited) {
        this.favorited = favorited;
    }
}
```

# データを入れる
`Realm.getDefaultInstance()`した分だけ、`realm.close();`する必要がある。
`executeTransaction()`を使うことで楽にトランザクションできる。
`copyToRealmOrUpdate()`を使うと追加するかprimaryKeyによってアップデートするかうまくしてくれる。
`copyToRealm()`も存在し、これの場合は同じprimaryKeyを入れようとするとExceptionが投げられます。

```java
        final Realm realm = Realm.getDefaultInstance();
        try {
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    for (Status status : homeTimeline) {
                        final Tweet tweet = new Tweet(status);
                        // プライマリーキーが同じならアップデート
                        realm.copyToRealmOrUpdate(tweet);
                    }
                }
            });
        } finally {
            // getしたらcloseする
            realm.close();
        }
```

ちなみにハンズオンではデータ取得とデータを入れるのにIntentServiceを利用していました。


# データを取り出して表示する

`realm.allObjectsSorted(Tweet.class, "createdAt", Sort.DESCENDING);`というように使うことでソートしたリストが取得できる。

```java
realm = Realm.getDefaultInstance();
final RealmResults<Tweet> tweets = realm.allObjectsSorted(Tweet.class, "createdAt", Sort.DESCENDING);
```

### データ変更時に自動的に更新する

`RealmBaseAdapter`を使うとRealmのデータが変更された時に自動的に`notifyDataSetChanged()`が呼ばれて、いい感じにしてくれる。(RecyclerView用のものはこれから追加されるらしい。今はプルリクの状態:https://github.com/realm/realm-java/pull/2548 )
終了時には`realm.close();`する。(Androidのライフサイクルに合わせる)

```java
public class TimelineFragment extends ListFragment {
    private Realm realm;

    @Override
    public void onViewCreated(View view, Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        realm = Realm.getDefaultInstance();
        final RealmResults<Tweet> tweets = buildTweetList(realm);
        final RealmBaseAdapter<Tweet> adapter = new RealmBaseAdapter<Tweet>(getContext(), tweets, true) {
            @Override
            public View getView(int position, View convertView, ViewGroup parent) {
                final Tweet tweet = getItem(position);
                if (convertView == null) {
                    convertView = inflater.inflate(R.layout.listitem_tweet, parent, false);
                }
　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　// TODO ViewHolderパターンを適用
                ((TextView) convertView.findViewById(R.id.screen_name)).setText(tweet.getScreenName());
                ((TextView) convertView.findViewById(R.id.text)).setText(tweet.getText());
                Glide.with(TimelineFragment.this).load(tweet.getIconUrl()).into((ImageView) convertView.findViewById(R.id.image));
                return convertView;
            }
        };
        setListAdapter(adapter);

    }

    @NonNull
    protected RealmResults<Tweet> buildTweetList(Realm realm) {
        return realm.allObjectsSorted(Tweet.class, "createdAt", Sort.DESCENDING);
    }


// 閉じる必要あり
    @Override
    public void onDestroyView() {
        super.onDestroyView();
        // Fragmentのバグ？でDestroyされた後もViewがrealmをいじってしまう場合があるので、realmの結果を使わないように変更
        ((RealmBaseAdapter<?>) getListAdapter()).updateRealmResults(null);
        realm.close();
        realm = null;
    }
```

# データを絞り込む
equalTo()を使うことで絞込ができるみたい

```java
    @NonNull
    protected RealmResults<Tweet> buildTweetList(Realm realm) {
        return realm.where(Tweet.class)
                .equalTo("favorited", true)
                .findAllSorted("createdAt", Sort.DESCENDING);
    }
```

# RxJavaと使う
(ハンズオンの内容にはありませんでしたが、、)
依存関係の追加

```groovy
dependencies {
    compile 'io.reactivex:rxjava:1.1.3'
}
```

こんな感じでObservableが使えるみたい

```java
realm.allObjectsSorted(Tweet.class, "createdAt", Sort.DESCENDING).asObservable();
```

# まとめ
普通に使いやすそうなので、これに速さなどの利点があって、Rxに対応していたりして、とても良いのではないかと思いました。