古いバージョンのドキュメントを表示しています。
最新版のドキュメントはこちらからご覧になれます

Realm Javaはアプリケーションのモデルレイヤーの永続化処理を安全に素早く記述することを可能にします。コードは以下のようになります。

// RealmObjectを継承することでモデルクラスを定義
public class Dog extends RealmObject {
    private String name;
    private int age;

    // getterとsetterを生成
}
public class Person extends RealmObject {
    @PrimaryKey
    private long id;
    private String name;
    private RealmList<Dog> dogs; // 1対多の関連をもつ

    // 生成されたgetterとsetter
}

// 通常のJavaオブジェクトのようにモデルクラスを使用
Dog dog = new Dog();
dog.setName("Rex");
dog.setAge(1);

// Realm全体の初期化
Realm.init(context);

// このスレッドのためのRealmインスタンスを取得
Realm realm = Realm.getDefaultInstance();

// 年齢が2未満のすべてのDogオブジェクトを取得する問い合わせを発行
final RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size(); // => まだRealmへは一つもオブジェクトが保存されていないので0を返します

// トランザクションの中でデータを永続化します
realm.beginTransaction();
final Dog managedDog = realm.copyToRealm(dog); // unmanagedなオブジェクトを永続化
Person person = realm.createObject(Person.class); // managedなオブジェクトを直接作成
person.getDogs().add(managedDog);
realm.commitTransaction();

// データが更新されると、リスナーが呼びだされて更新を受け取ることができます。
puppies.addChangeListener(new OrderedRealmCollectionChangeListener<RealmResults<Dog>>() {
    @Override
    public void onChange(RealmResults<Dog> results, OrderedCollectionChangeSet changeSet) {
        // Query results are updated in real time with fine grained notifications.
        changeSet.getInsertions(); // => [0] is added.
    }
});

// 別のスレッドから非同期に更新を実行することもできます
realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
        // トランザクションの開始とコミットは自動的に行われます
        Dog dog = bgRealm.where(Dog.class).equalTo("age", 1).findFirst();
        dog.setAge(3);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // 既存のクエリやRealmObjectは自動的に最新の情報に更新されます
        puppies.size(); // => 0("age"が2未満の犬は存在しなくなったので)
        managedDog.getAge();   // => 3(既存オブジェクトは最新の状態に更新されるため)
    }
});

はじめに

Download Realm for Android

ソースコードはGitHubにて公開しています。

必要条件

  • バージョン1.5.1以上のAndroid Studio
  • JDK 7.0以降
  • 最新のAndroid SDK
  • API Level 9(Android 2.3以上)

注意: Realmは現在はAndroid以外のJavaでは使用できません。開発環境としてEclipseはサポートしません。Android Studioに移行してください。

インストール

ステップ 1: プロジェクトのトップレベルにあるbuild.gradleファイルに以下のclasspath設定を追加します。

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

プロジェクトのトップレベルのbuild.gradleは以下の場所にあります。

プロジェクトトップレベルのbuild.gradleファイル

ステップ 2: realm-androidプラグインをアプリケーションレベルのbuild.gradleファイルで適用します。

apply plugin: 'realm-android'

アプリケーションレベルのbuild.gradleは以下の場所にあります。

アプリケーションレベルのbuild.gradleファイル

これらの設定を行ってからgradleプロジェクトのリフレッシュを行ってください。もし0.88以前のバージョンからアップグレードした場合は古いファイルの削除のためにプロジェクトのclean(./gradlew clean)を行う必要があるかもしれません。

以下の場所に、それぞれのbuild.gradleのサンプルがあります。

Gradle以外のビルドシステム

MavenやAntでのビルドはサポートされません。もしこれらのビルドシステムのサポートの必要性を表明したい場合は、以下のissueへコメントをお願いします。

ProGuard設定はRealmライブラリの一部として提供されるようになりました。そのため、Realm自体に関するProGuard設定を追加する必要はありません。

Realmの初期化

Realmを使用する前には必ず初期化しなければなりません。初期化は次のようにします。

Realm.init(context);

引数にAndroidのcontextを渡す必要があります。初期化のタイミングはApplicationサブクラスのonCreate()が適しています。

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    Realm.init(this);
  }
}

下記のように、MyApplicationクラスをアプリケーションレベルのAndroidManifest.xmlに登録する必要があります。

<application
  android:name=".MyApplication"
  ...
/>

Realm Browser

.realmデータベースを閲覧、編集するためのRealm BrowserというMacアプリケーションを提供しています。

Realm Browser

また、Tools > Generate demo database と選択すると、サンプルデータを含むテスト用のRealmデータベースを作ることができます。

開発中のアプリのRealmファイルがどの場所に格納されているかは、このStackOverflowの回答が参考になります。

Realm BrowserはMac App StoreRealm BrowserのGitHubページからダウンロードすることができます。

現在のRealm BrowserはWindowsやLinuxでは動作しません。これらのプラットフォームを使用してアプリ開発を行っている方は、Stetho-Realmの使用をご検討ください。StethoはFacebook社によって開発されたツールで、Google Chromeブラウザのデバッグ機能を使ってAndroidアプリをデバッグできるようになります。

APIリファレンス

Realmで使用できるすべてのクラスとメソッドに関しては、APIリファレンスをご覧ください。

サンプルコード

Realmが実際に動くアプリケーションの中でどのように利用されるかの例がexamplesにあります。Android Studioで、Import Projectし、runで実行してみてください。

introExampleは、Realm APIの簡単な使用例について学べます。

gridViewExampleは、GridViewのデータソースとしてRealmを利用する方法について学べます。また、GSONを用いてJSONデータをRealm保存する方法や、ABI splitsを使用してAPKサイズを小さくする方法も含まれています。

threadExampleは、マルチスレッド環境でRealmを利用する方法について学べます。

adapterExampleは、RealmBaseAdapterRealmRecyclerViewAdapterを使って、Realm上のデータをListViewRecyclerViewに表示する方法について学べます。

jsonExampleは、RealmのJSON関連のメソッドについて学べます。

encryptionExampleは、Realmを暗号化する方法について学べます。

rxJavaExamplesは、RealmをRxJavaと組み合わせて使用方法について学べます。

unitTestExampleは、Realmを使ったアプリケーションをテストする方法について学べます。

ヘルプ

  • 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
  • さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。
  • バグ報告や機能リクエストについては GitHubのIssuesにご報告ください。
  • 次のバージョンのリリース内容を前もって把握しておきたいですか? Changelogファイルを見ましょう。次のバージョンでリリースされる予定の新機能や変更が記述されていきます。また、過去の変更内容も書かれています。

モデル

Realmでは、RealmObjectのサブクラスを作ることでモデルを定義します。

public class User extends RealmObject {

    private String          name;
    private int             age;

    @Ignore
    private int             sessionId;

    // Standard getters & setters generated by your IDE…
    public String getName() { return name; }
    public void   setName(String name) { this.name = name; }
    public int    getAge() { return age; }
    public void   setAge(int age) { this.age = age; }
    public int    getSessionId() { return sessionId; }
    public void   setSessionId(int sessionId) { this.sessionId = sessionId; }
}

モデル定義では、publicprotectedprivateフィールドや、任意のメソッドの使用も可能です。

public class User extends RealmObject {

    public String name;

    public boolean hasLongName() {
      return name.length() > 7;
    }

    @Override
    public boolean equals(Object o) {
      // カスタムのequals()メソッド
    }
}

対応しているデータ型

Realmでは、次に示すデータ型をサポートしています: booleanbyteshortintlongfloatdoubleStringDateおよびbyte[]です。 Realmの内部では、整数型であるbyteshortintlong型はすべて同じ型(long型)として扱われます。

また、関連をサポートするためにはRealmObjectのサブクラスとRealmList<? extends RealmObject>が使用されます。

データ型として、プリミティブラッパークラスの BooleanByteShortIntegerLongFloatDouble型を使用することもできます。これらの型をもつフィールドに対しては値としてnullをセットすることができます。

必須フィールドとnull値

nullが値として適切ではない場合もあります。その場合、@Requiredアノテーションを使うことでnull禁止を明示することができます。BooleanByteShortIntegerLongFloatDoubleStringbyte[]Date型にのみ@Requiredアノテーションを使用することができ、これら以外の型に対して@Requiredアノテーションを使用した場合はコンパイルエラーになります。

プリミティブ型やRealmList型のフィールドは、暗黙的に@Requiredとして扱われます。また、RealmObjectを継承した型のフィールドは常にnull可として扱われます。

保存しないプロパティ

@Ignoreアノテーションを付けて宣言したフィールドは、ディスクに保存されないことを表します。モデルによって使用されない入力データが多くあるが、それらのデータを扱いたくないときに役に立ちます。

staticまたはtransientなフィールドは@Ignoreアノテーションを付加しなくても保存しないプロパティとして扱われます。

オブジェクトの自動更新

RealmObjectは”live”なオブジェクトであり、対応するデータの更新を自動的に反映します。つまり、明示的なリフレッシュ操作は必要ありません。

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Dog myDog = realm.createObject(Dog.class);
        myDog.setName("Fido");
        myDog.setAge(1);
    }
});
Dog myDog = realm.where(Dog.class).equalTo("age", 1).findFirst();

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Dog myPuppy = realm.where(Dog.class).equalTo("age", 1).findFirst();
        myPuppy.setAge(2);
    }
});

myDog.getAge(); // => 2

RealmObject‘やRealmResultsが備えるこの性質により、Realm自体が高速で効率的であるだけでなくRealmを利用するコードがシンプルでよりリアクティブなものになります。例えば、ActivityやFragmentがRealmObjectRealmResultsを利用している場合、UIの更新の際にこれらのオブジェクトを更新したり取得しなおしたりする必要はなくなります。

また、通知機能を利用することでRealm内のデータが更新されたことを知ることができます。この機能により、常にUIに最新の情報を表示することができます。

インデックス付きプロパティ

@Indexアノテーションを付けて宣言したフィールドには、検索インデックスが作成されます。

インデックスを作成すると、データの追加が少し遅くなり、データファイルは大きくなりますが、検索の実行速度が向上します。そのため検索を速く実行する必要があるところでのみ使うことを推奨しています。

現在のバージョンでは、StringbyteshortintlongbooleanおよびDate型のフィールドに対して、インデックスを追加することができます。

プライマリキー

プライマリキーを指定するには、@PrimaryKeyアノテーションを使います。指定できるフィールドは、文字列型(String)か整数型(byteshortintまたはlong)か、それらのラッパー型(ByteShortIntegerまたはLong)である必要があります。

複数のフィールドをプライマリキーとして指定すること(複合プライマリキー)はできません。

String型のフィールドをプライマリキーにすると、暗黙的にそのフィールドには検索インデックスが追加されます。(@PrimaryKeyアノテーションを指定することで、自動的に@Indexが付きます。)

プライマリキーを指定したモデルについては、copyToRealmOrUpdate()を使ってオブジェクトを作成または更新をすることができます。 このメソッドは、プライマリキーが一致するデータがすでに存在すれば、データを更新し、無ければ新しくオブジェクトを作成します。 copyToRealmOrUpdate()をプライマリキーを持たないクラスに対して使用した場合は例外がスローされます。

プライマリキーを使うことによってパフォーマンスに影響します。オブジェクトの作成と更新は、少し遅くなりますが、検索は速くなります。パフォーマンスへの影響は、データセットの大きさによって異なります。

Realm.createObject()は新しくRealmオブジェクトを作成し、すべてのフィールドにデフォルト値を設定します。 すでに存在するデータのプライマリキーとデフォルト値が衝突するのを避けるために、オブジェクトを作成した後は、フィールドに値を設定し、その後でcopyToRealm()でRealmにオブジェクトを追加してください。

final MyObject obj = new MyObject();
obj.setId(42);
obj.setName("Fish");
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        // 次のコメント行のコードでは、単に新たなオブジェクトの作成を試みます
        // realm.copyToRealm(obj);
        // 同じIDのオブジェクトが存在すれば更新を行い、存在しなければ新たに作成を行います
        realm.copyToRealmOrUpdate(obj);
    }
});

プライマリキー指定されたフィールドの型が文字列型(String)やラッパー型(ByteShortIntegerLong)の場合、値としてnullを持つことも可能です。ただし、同時に@Requiredを指定することでnullの使用を禁止することもできます。

モデルクラスのカスタマイズ

RealmObjectを継承したモデルクラスは、ほぼ通常のPOJOsとして扱うことができます。RealmObjectを継承しさえすれば、フィールドをpublicにして直接代入することもできます。いかに例を示します。

public class Dog extends RealmObject {
    public String name;
    public int age;
}

Dogは通常のクラスと同じように使用することができます。Realm内にDogオブジェクトを作成するためには、createObject()またはcopyToRealm()メソッドを使用します。

realm.executeTransaction(new Realm.Transaction() {
    @Overrride
    public void execute(Realm realm) {
        Dog dog = realm.createObject(Dog.class);
        dog.name = "Fido";
        dog.age  = 5;
    }
};

ゲッターやセッターを定義し、その中に独自のロジックを組み込むことも可能です。たとえばRealmに保存する前に値のチェックを行う場合などにはとても役に立ちます。また、モデルクラスに独自のメソッドを定義することもできます。

RealmModelインタフェース

RealmObjectを継承する以外の選択肢として、RealmModelインタフェースと@RealmClassアノテーションを用いる方法があります。その場合は、RealmModelを実装するとともに、クラスに@RealmClassアノテーションを付与してください。

@RealmClass
public class User implements RealmModel {
}

RealmObjectがインスタンスメソッドとして提供していたメソッドは、staticメソッドとしても提供されます。

// RealmObjectを継承する場合
user.isValid();
user.addChangeListener(listener);

// RealmModelを実装する場合
RealmObject.isValid(user);
RealmObject.addChangeListener(user, listener);

リレーションシップ(関連)

RealmObjectは、互いを関連させることができます。

public class Email extends RealmObject {
    private String address;
    private boolean active;
    // settersとgettersは省略しています
}

public class Contact extends RealmObject {
    private String name;
    private Email email;
    // settersとgettersは省略しています
}

Realmの関連は速度の観点からは非常に低コストで利用できます。 さらに関連を表現するための内部の仕組みは、メモリ消費の点においても非常に効率良く作られています。

多対1のリレーションシップ

モデル間で多対1の関連を持たせるには、別のRealmObjectのプロパティを下記のように定義します:

public class Contact extends RealmObject {
    private Email email;
    // Other fields…
}

それぞれのContactインスタンスは0個または1つのEmailインスタンスを関連として持ちます。 複数のContactインスタンスが同じ1つのEmailインスタンスを関連として持つこともできます。 上記のモデルで多対1の関連を持つこともできますが、多くの場合1対1の関連が使われます。

RealmObject型のフィールドにnullをセットすることは関連を削除することを意味し、参照先のモデルオブジェクト自体は削除されません。

多対多のリレーションシップ

1つのオブジェクトから複数(0個以上)の関連を持つにはRealmList<T>フィールドを定義します。たとえば複数のメールアドレスを保持するアドレス帳の例を考えてみます。

public class Contact extends RealmObject {
    public String name;
    public RealmList<Email> emails;
}

public class Email extends RealmObject {
    public String address;
    public boolean active;
}

RealmListクラスは、基本的にRealmObjectのコンテナで、 その振る舞いは、JavaのListクラスと非常によく似ています。 1つのオブジェクトが、複数の異なるRealmListをもつことができます。 1対多の関連としても、多対多の関連を表す場合にも使うことができます。

以下のようにContactEmailクラスを定義したとします。

それぞれのオブジェクトを作成した後、RealmList.add()メソッドを用いてEmailオブジェクトをContactと関連付けることができます。

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Contact contact = realm.createObject(Contact.class);
        contact.name = "John Doe";

        Email email1 = realm.createObject(Email.class);
        email1.address = "john@example.com";
        email1.active = true;
        contact.emails.add(email1);

        Email email2 = realm.createObject(Email.class);
        email2.address = "jd@example.com";
        email2.active = false;
        contact.emails.add(email2);
    }
});

再帰的な関連を定義することもできます。

public class Person extends RealmObject {
    public String name;
    public RealmList<Person> friends;
    // Other fields…
}

RealmList型のフィールドにnullをセットすることはリストをクリアすることを意味します。つまり、リストが参照していたモデルオブジェクトの削除はおこなわれず、単にリストの長さをゼロにするだけです。RealmList型のフィールドのgetterがnullを返すことはありません。返されるオブジェクトの保持する要素数が0になります。

関連に対するクエリ

関連のオブジェクトに対してクエリを実行する方法について以下のモデルで説明します。

public class Person extends RealmObject {
  private String id;
  private String name;
  private RealmList<Dog> dogs;
  // getters and setters
}

public class Dog extends RealmObject {
  private String id;
  private String name;
  private String color;
  // getters and setters
}

それぞれのPersonオブジェクトは以下の図のように複数のDogへの関連を持っています。

Table Diagram

リンククエリを用いてユーザーを検索する例を見ていきましょう。

// persons => [U1,U2]
RealmResults<Person> persons = realm.where(Person.class)
                                .equalTo("dogs.color", "Brown")
                                .findAll();

まず初めに、equalTo条件を関連に適用するには(ピリオドで区切られた)関連を示すパスを使うことがわかります。

上記のクエリは次のように解釈されます。

“色が’Brown’である犬を少なくとも1つは関連に保持しているすべての’Person’を取得してください。”

結果に含まれるユーザーが関連として持っている犬には、条件を満たさないものが含まれている場合もあるということに注意してください。

persons.get(0).getDogs(); // => [A,B]
persons.get(1).getDogs(); // => [B,C,D]

以下の2つのクエリの例を詳しく見ていきましょう。

// r1 => [U1,U2]
RealmResults<Person> r1 = realm.where(Person.class)
                             .equalTo("dogs.name", "Fluffy")
                             .findAll();

// r2 => [U1,U2]
RealmResults<Person> r2 = r1.where()
                          .equalTo("dogs.color", "Brown")
                          .findAll();

1つめのクエリがどのように両方のPersonにマッチするかについて見ていきましょう。

結果に含まれるそれぞれのPersonは条件にマッチしたDogだけでなく、その他のDogも含んだリストを保持しています。(名前と色に関する)特定の条件を満たす犬を関連として保持している人を探しているのであって、犬自体を探しているわけではないことに注意してください。2つめのクエリは1つめのクエリの結果(r1)に含まれるPersonとそのPersonが保持するすべてのDogに対して評価されます。そのため、2つめのクエリも両方のPersonにマッチすることになります。

この考え方をより深く理解するため、以下の例を見ていきます。

// r1 => [U1,U2]
RealmResults<Person> r1 = realm.where(Person.class)
                             .equalTo("dogs.name", "Fluffy")
                             .equalTo("dogs.color", "Brown")
                             .findAll();

// r2 => [U2]
RealmResults<Person> r2 = realm.where(Person.class)
                             .equalTo("dogs.name", "Fluffy")
                             .findAll()
                             .where()
                             .equalTo("dogs.color", "Brown")
                             .findAll();
                             .where()
                             .equalTo("dogs.color", "Yellow")
                             .findAll();

1つめのクエリは以下のように読みます。

“名前(name)が’Fluffy’である犬を持つ全てのPersonと、色(color)が’Brown’である犬を持つ全てのPersonとの両方に含まれるPersonを取得してください。”

2つめのクエリは以下のように読みます。

“名前(name)が’Fluffy’である犬を持つ全てのPersonを取得してください。その中から、色(color)が’Brown’である犬を持つ全てのPersonを取得してください。さらにその中から、色(color)が’Yellow’である犬を持つ全てのPersonを取得してください。”

r1を取得するためのクエリで何が起こっているか詳しく見ていきましょう。条件はそれぞれequalTo("dogs.name", "Fluffy")equalTo("dogs.color", "Brown")です。U1U2ともに1つめの条件を満たします(これをC1と呼ぶことにします)。 U1U2は2つめの条件も満たします(こちらをC2と呼ぶことにします)。条件の論理積は、これらC1C2の積集合と同じです。C1C2の積集合はU1U2を要素に持つので、r1U1U2の両方ということになります。

r2ではr1とは違うことが起こっています。クエリを分解してみましょう。クエリの一つめの部分はRealmResults<Person> r2a = realm.where(Person.class).equalTo("dogs.name", "Fluffy").findAll();です。U1U2ともにこの条件を満たします。

つぎのクエリは、r2b = r2a.where().equalTo("dogs.color", "Brown").findAll();です。これらについてもU1U2ともに条件を満たします。

最後のクエリは、r2 = r2b.where().equalTo("dogs.color", "Yellow").findAll();です。この条件をみたすのはU2のみです。

逆方向の関連

Realmの関連は一方通行です。PersonクラスとDogクラスを例にして説明します。Personオブジェクトから関連のDogオブジェクトをたどることはできますが、DogオブジェクトからPerson`オブジェクトをたどることはできません。

LinkingObjectsアノテーションを付加したフィールドを使用することで、関連元のオブジェクトを特定のプロパティを使って取得することができます。例えば、Dogクラスにownersという自分自身を関連として保持しているPersonオブジェクトをすべて取得するというフィールドを持たせることができます。その方法はownersフィールドに@LinkingObjectsアノテーションを付加し、Personクラスの関連であると指定します。

public class Person extends RealmObject {
  private String id;
  private String name;
  private RealmList<Dog> dogs;
  // getters and setters
}

public class Dog extends RealmObject {
  private String id;
  private String name;
  private String color;
  @LinkingObjects("dogs")
  private final RealmResults<Person> owners;
  // getters and setters
}

逆方向の関連を示すフィールドはfinalかつRealmResults<T>型のフィールドとして定義します。このとき、型パラメータのTは関連元のオブジェクトのクラスを指定します。関連には1対1の関連と1対多の関連があります。逆方向の関連は0個以上のオブジェクトが返ることになります。通常のRealmResultsと同様に、逆方向の関連のオブジェクトに対してクエリを実行してさらに検索や並べ替えが可能です。

逆方向の関連を使ったクエリ

逆方向の関連を条件にした、さらに柔軟なクエリを実行できます。 同じようにPersonクラスとDogクラスを例にして考えてみましょう。

前に示した例の中で、色(color)が’Brown’で名前(name)が’Fluffy’である犬を関連に持つPersonを取得するクエリがありました。ここでは同じ条件ですが、まず検索条件に当てはまるDogオブジェクトを取得し、そこから逆方向の関連をたどってPersonオブジェクトを取得するように書いてみます。

RealmResults<Dog> brownFluffies = realm.where(Dog.class).equalTo("color", "Brown").equalTo("name", "Fluffy").findAll();
for (Dog brownFluffy : brownFluffies) {
    RealmResults<Person> owners = brownFluffy.getOwners();
    // ...
}

ここで、逆方向の関連であるPersonクラスのフィールドについて、関連に対するクエリのと同じように検索条件に書くことができます。例えば、’Jane’という名のPersonオブジェクトを逆方向の関連に持つDogオブジェクトを検索するクエリは、下記のようになります。

RealmResults<Dog> dogs = realm.where(Dog.class).equalTo("owner.name", "Jane").findAll();
// dogs => [A, B]

書き込み

読み出しの操作は、どのタイミングでも実行可能ですが、書き込み(追加、変更、削除)処理は、必ずトランザクションの中で行わなければなりません。

Writeトランザクションは、コミットかキャンセルができます。

コミットしたときは、トランザクション中のすべての変更がディスクに書き込まれ、すべてのデータが保存されるとコミット処理が成功となります。トランザクションをキャンセルした場合、トランザクション中のすべての変更は破棄されます。

データの一貫性を保つためにトランザクションを使用します。

また、トランザクションはスレッドセーフを保証するためにも使用されます。

// Obtain a Realm instance
Realm realm = Realm.getDefaultInstance();

realm.beginTransaction();

//... オブジェクトの追加や更新 ...

realm.commitTransaction();

トランザクション中で行ったRealmオブジェクトの変更は、反映するかキャンセルするかを選ぶことができます。 変更をコミットするのではなく破棄する場合、下記のように簡単にキャンセルすることができます。

realm.beginTransaction();
User user = realm.createObject(User.class);

//  ...

realm.cancelTransaction();

同時に発生した書き込み処理は、互いの書き込み処理をブロックします。

UIスレッドとバックグラウンドスレッドで同時にトランザクションを実行することは、UIスレッドをブロックしANRエラーの原因になります。

これを避けるために、UIスレッド上でトランザクションを開始する際は、非同期トランザクションを使用してください。

Realmは不意のクラッシュに対しても安全です。トランザクション中にExceptionが発生した場合でもRealmファイルが壊れることはありません。トランザクション中のデータが失われるだけです。Exceptionを捕捉し、アプリケーションが続行する場合は、トランザクションをキャンセルしなければなりません。executeTransaction()メソッドを利用しているなら自動的にトランザクションはキャンセルされます。

RealmはMVCCアーキテクチャーを採用しているので、トランザクション実行中でも読み込み処理がブロックされることはありません。 同時に複数のスレッドからトランザクションを実行するのでなければ、細かいトランザクションを使うよりも大きな単位でトランザクションを使いましょう。

同じRealmに保存されているオブジェクトに変更があった場合は、トランザクションがコミットされた時点ですべての変更が自動的に適用されます

Realmのトランザクションは、ACIDに準拠しています。

オブジェクトの作成

RealmObjectはRealmと強く結び付けられているので、以下のようにRealmインスタンスをつかって作成する必要があります。

realm.beginTransaction();
User user = realm.createObject(User.class); // 新たなオブジェクトを作成
user.setName("John");
user.setEmail("john@corporation.com");
realm.commitTransaction();

別の方法として、始めに一般のオブジェクトと同様にインスタンスを作成し、realm.copyToRealm() を使用して、後からRealmに保存することもできます。

Realmのモデルでは自由にコンストラクタを定義することができますが、少なくともその中に引数を取らないパブリックコンストラクタが含まれていなければなりません。

User user = new User("John");
user.setEmail("john@corporation.com");

// Realmへオブジェクトをコピーします。これ以降の変更は、返り値のオブジェクトに対して行う必要があります
realm.beginTransaction();
User realmUser = realm.copyToRealm(user);
realm.commitTransaction();

realm.copyToRealm()を使う際に注意することは、戻り値として返されたオブジェクトのみがRealmによって管理されているということです。以降、最初に作られたオブジェクトに変更を加えても、何も反映されません。

トランザクションブロック

realm.beginTransaction(), realm.commitTransaction(), realm.cancelTransaction() の代わりに、realm.executeTransaction()メソッドを使って、ブロック形式でトランザクションを記述することができます。

このメソッドを使用した場合、メソッドを抜けた時点で自動的にトランザクションはコミットされます。もし、エラーが発生した場合は自動的にキャンセルされます。

realm.executeTransaction(new Realm.Transaction() {
	@Override
	public void execute(Realm realm) {
		User user = realm.createObject(User.class);
		user.setName("John");
		user.setEmail("john@corporation.com");
	}
});

非同期トランザクション

トランザクションを開始しようとした際、他のスレッドがトランザクションを実行中だと実行中のトランザクションが終了するまで待たされてしまいます。 UIスレッドが待たされてしまうことを避けるために、バックグラウンドスレッドで全ての書き込み処理を行うことには大きな利点があります。

非同期トランザクションの機能を使うことで、Realmに対してバックグラウンドスレッドでのトランザクション実行と完了後の結果通知を行わせることができます。

realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm bgRealm) {
                User user = bgRealm.createObject(User.class);
                user.setName("John");
                user.setEmail("john@corporation.com");
            }
        }, new Realm.Transaction.OnSuccess() {
            @Override
            public void onSuccess() {
                // トランザクションは成功
            }
        }, new Realm.Transaction.OnError() {
            @Override
            public void onError(Throwable error) {
                // トランザクションは失敗。自動的にキャンセルされます
            }
        });

OnSuccessOnErrorのコールバックは必須ではありませんが、渡された場合はトランザクション完了時に、成功/失敗に応じて呼び出されます。コールバックはLooperを用いて制御されているので、Looperスレッドでのみ使用することができます。

RealmAsyncTask transaction = realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm bgRealm) {
                User user = bgRealm.createObject(User.class);
                user.setName("John");
                user.setEmail("john@corporation.com");
            }
        }, null);

非同期トランザクションはRealmAsyncTaskオブジェクトとして表現されます。 このオブジェクトを通して、トランザクションのキャンセルを行うことができます。

トランザクション完了前にActivityやFragmentを終了する場合は忘れずにキャンセルを行ってください。キャンセルせずにコールバックがUI更新を行ってしまうとアプリケーションがクラッシュしてしまいます。

public void onStop () {
    if (transaction != null && !transaction.isCancelled()) {
        transaction.cancel();
    }
}

バイト配列の更新

Realmはフィールドが持つ値全体をひとまとまりで扱います。そのため、バイト配列の要素を個別に更新することはサポートしていません。たとえば、バイト配列の5番目の要素のみを更新したい場合、Realmでは以下のようにコーディングする必要があります。

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        bytes[] bytes = realmObject.binary;
        bytes[4] = 'a';
        realmObject.binary = bytes; // 代入またはsetterの呼び出しが必要
    }
});

これはRealmのMVCCアーキテクチャによる制限で、変更を行ってからcommitが完了するまでの間、他のスレッドが更新中のデータにアクセスしてしまうことを防ぐために必要です。

クエリ

Realmにおいてすべての読み込み(クエリを含む)は遅延実行されます。また、読み込み時にデータのコピーは一切発生しません。

RealmのクエリAPIは、複数の条件を組み立てる方法として流れるようなインターフェースを採用しています(メソッドチェーンと入力補完を利用して、複雑なクエリでも簡単に組み立てることができます)。

以下のようなUserクラスを定義したとします。

public class User extends RealmObject {

    @PrimaryKey
    private String          name;
    private int             age;

    @Ignore
    private int             sessionId;

    // Standard getters & setters generated by your IDE…
    public String getName() { return name; }
    public void   setName(String name) { this.name = name; }
    public int    getAge() { return age; }
    public void   setAge(int age) { this.age = age; }
    public int    getSessionId() { return sessionId; }
    public void   setSessionId(int sessionId) { this.sessionId = sessionId; }
}

nameフィールドの値がJohnまたはPeterであるすべてのオブジェクトを取得するには次のように書きます。

// Build the query looking at all users:
RealmQuery<User> query = realm.where(User.class);

// Add query conditions:
query.equalTo("name", "John");
query.or().equalTo("name", "Peter");

// Execute the query:
RealmResults<User> result1 = query.findAll();

// Or alternatively do the same all at once (the "Fluent interface"):
RealmResults<User> result2 = realm.where(User.class)
                                  .equalTo("name", "John")
                                  .or()
                                  .equalTo("name", "Peter")
                                  .findAll();

この例では、nameの値がJohnまたはPeterであるデータがRealmResultsとして返ってきます。クエリはfindAll()メソッドが呼ばれた時点で実行されます。findAllメソッドと同じ種類のメソッド、findAllSorted()はソートされた結果を返します。findAllAsync()はクエリを非同期にバックグラウンドスレッドで実行します。詳しくはAPIリファレンスをご覧ください。

オブジェクトがコピーされることはありません。マッチしたオブジェクトの参照がリストとして返され、元のオブジェクトと同じように直接操作することができます。RealmResultは、AbstractListクラスを継承しており、使い方はよく似ています。例えば、RealmResultsは、順序を持ち、添え字を指定してアクセスすることができます。

実行されたクエリに該当するオブジェクトが無かった場合、戻り値としてnullが返されることはなく、空のRealmResultsオブジェクトが返されます。返されるオブジェクトのsize()メソッドの値は0です。

RealmResultに含まれるオブジェクトに対して変更を加えたり、削除する場合、それらの操作はトランザクションの中で行わなければなりません。

関連に対するクエリを発行することもできます。

検索する

where()メソッドはRealmQueryオブジェクトとしてのクエリを生成します。RealmQueryオブジェクトを得たあとは、さまざまな検索条件のメソッドを使い、必要なデータを得るためのクエリを構築します。ほとんどの検索条件のメソッドはわかりやすい名前を持っています。

検索条件を表すメソッドの最初の引数はフィールド名です。もし検索条件が対応してない型のフィールドを指定した場合は例外が発生します。より詳しくはRealmQueryのAPIリファレンスをご覧ください。

すべてのデータ型につき、次のメソッドが使えます。

  • equalTo()
  • notEqualTo()
  • in()

複数の値に対して一致するものを選ぶ場合はin()を利用します。例えば、”Jill”、”William”または”Trillian”を検索したい場合は、in("name", new String[]{"Jill", "William", "Trillian"})のようにします。in()メソッドは文字列、バイナリデータ、数値型(日付を含む)に対して利用できます。

数値型およびDate型のフィールドにはさらに次のメソッドが使用できます。

  • between() (両端を含む)
  • greaterThan()
  • lessThan()
  • greaterThanOrEqualTo()
  • lessThanOrEqualTo()

String型には下記のメソッドが使用できます。

  • contains()
  • beginsWith()
  • endsWith()
  • like()

上記の4つのString型に対するメソッドには大文字小文字を区別するかどうかを示す3番目の引数があります。必須ではありません。Case.INSENSITIVEをセットすると大文字小文字の区別を無視して検索します。Case.SENSITIVEをセットすると大文字小文字を区別します。デフォルト値はCase.SENSITIVEです。

like()はワイルドカードが利用できます。ワイルドカードの書式は下記の通りです。

  • *は0個または複数のユニコード文字列にマッチします。
  • ?は1つのユニコード文字にマッチします。

例えば、nameというフィールドがあるオブジェクトについて、それぞれWilliam、Bill、Jill、Trillianと設定されているとします。そのとき、like("name", "?ill*")という条件には最初の3つの名前がマッチします。like("name", "*ia?")という条件には最初と最後の名前がマッチします。

バイナリデータと文字列、およびRealmObjectのリスト(RealmList)は空の値も格納できます。空の値にマッチさせるには次のメソッドを利用します。

  • isEmpty()
  • isNotEmpty()

フィールドが必須ではない場合、nullが保存されている可能性があります。(また、1対1の関連を示すRealmObjectのフィールドは必須にすることはできないので、nullの可能性があります。)nullにマッチさせるには次のメソッドを使用します。

  • isNull()
  • isNotNull()

論理演算子

すべての条件式は暗黙的に論理積(AND)になります。論理和(OR)を使う場合は明示的にor()を使用する必要があります。

public class User extends RealmObject {

    @PrimaryKey
    private String          name;
    private int             age;

    @Ignore
    private int             sessionId;

    // 以下はIDEによって生成された一般的なgetter/setter
    public String getName() { return name; }
    public void   setName(String name) { this.name = name; }
    public int    getAge() { return age; }
    public void   setAge(int age) { this.age = age; }
    public int    getSessionId() { return sessionId; }
    public void   setSessionId(int sessionId) { this.sessionId = sessionId; }
}

それぞれの条件の評価順を指定するためにbeginGroup()endGroup()を用いて条件をグループ化できます。

RealmResults<User> r = realm.where(User.class)
                            .greaterThan("age", 10)  // implicit AND
                            .beginGroup()
                                .equalTo("name", "Peter")
                                .or()
                                .contains("name", "Jo")
                            .endGroup()
                            .findAll();

さらに、not()を使うと否定になります。not()は、beginGroup()endGroup()に対してのみ使用することができます。名前が”Peter”または”Jo.”ではない条件で検索するとすると次のように書きます。

RealmResults<User> r = realm.where(User.class)
                           .not()
                           .beginGroup()
                                .equalTo("name", "Peter")
                                .or()
                                .contains("name", "Jo")
                            .endGroup()
                            .findAll();

もちろん、このクエリはin()を使うともっと簡単に書けます。

RealmResults<User> r = realm.where(User.class)
                           .not()
                           .in("name", new String[]{"Peter", "Jo"})
                           .findAll();

並べ替え

クエリの実行後は、以下のように結果を並べ替えることができます。

RealmResults<User> result = realm.where(User.class).findAll();
result = result.sort("age"); // 昇順にソート
result = result.sort("age", Sort.DESCENDING);// 降順にソート

デフォルトは昇順です。変更するには2番目の引数にSort.DESCENDINGを指定します。複数のフィールドを同時に並べ替えの条件にすることができます。

Unique values

distinct()を使うと、重複を取り除くことができます。例えば、異なる名前がどれだけ存在するかを調べるには次のように書きます。

RealmResults<Person> unique = realm.where(Person.class).distinct("name");

数値型と文字列型に対してのみ利用可能です。他の型に対して使用すると、例外が発生します。並べ替えと同様に複数のフィールドを指定できます。

クエリの連鎖

すべてのクエリは、遅延実行されることに加えて決してデータをコピーしないので、非常に効率的に連鎖して実行することができ、次々とデータをフィルタリングしていくことができます。

RealmResults<Person> teenagers = realm.where(Person.class).between("age", 13, 20).findAll();
Person firstJohn = teenagers.where().equalTo("name", "John").findFirst();

クエリの連鎖は関連オブジェクトに対しても適用することができます。以下のようにPersonDogをリストとして保持している例で説明します。

public class Dog extends RealmObject {
    private int age;
    // getters & setters ...
}

public class Person extends RealmObject {
    private int age;
    private RealmList<Dog> dogs;
    // getters & setters ...
}

以下のようにすることで、ageが13から20の間のPersonで、ageが1である犬を少なくとも1つは関連として保持しているものを取得することができます。

RealmResults<Person> teensWithPups = realm.where(Person.class).between("age", 13, 20).equalTo("dogs.age", 1).findAll();

クエリの連鎖はRealmQueryではなくRealmResultsに対してさらにクエリを発行するものを指します。RealmQueryに対して条件を追加するものはクエリの連鎖とは呼ばず、単に既存のクエリが更新されまます。詳しくは、link queriesを参照してください。

クエリの自動更新

RealmResultsは”live”なオブジェクトであり、対応するデータの更新を自動的に反映します。つまり、明示的なリフレッシュ操作は必要ありません。クエリに影響するような操作をオブジェクトに対して行った場合、RealmResultsへの反映は即座に行われます。

final RealmResults<Dog> puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size(); // => 0

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Dog dog = realm.createObject(Dog.class);
        dog.setName("Fido");
        dog.setAge(1);
    }
});

puppies.addChangeListener(new RealmChangeListener() {
    @Override
    public void onChange(RealmResults<Dog> results) {
      // resultsとpuppiesは両方共最新のデータに更新されています
      results.size(); // => 1
      puppies.size(); // => 1
    }
});

RealmResultsが備えるこの性質により、Realm自体が高速で効率的であるだけでなくRealmを利用するコードがシンプルでよりリアクティブなものになります。例えば、ActivityやFragmentがRealmObjectRealmResultsを利用している場合、UIの更新の際にこれらのオブジェクトを更新したり取得しなおしたりする必要はなくなります。

また、通知機能を利用することでRealm内のデータが更新されたことを知ることができます。この機能により、情報の再取得を意識することなく常に最新の情報をUIに表示することができます。

クエリの結果は自動更新されるので、インデックスやサイズが不変であるという前提のコードを記述しないことはとても重要です。

型を使ったオブジェクトの取得

Realmからオブジェクトを取得するときのもっとも基本的なメソッドは、realm.where(Foo.class).findAll()です。このメソッドは、指定したモデルクラスのすべてのオブジェクトを含むRealmResults<Foo>として返します。

また、findAll()に似たメソッドで realm.where(Foo.class).findAllSorted()もあります。このメソッドは、オブジェクトを取得すると同時に、フィールドを指定して並べ替えをすることができます。 詳しくは、realm.where(Foo.class).findAllSorted()をご覧ください。

集計

RealmResultsにはさまざまな集計のためのメソッドがあります。

RealmResults<User> results = realm.where(User.class).findAll();
long   sum     = results.sum("age").longValue();
long   min     = results.min("age").longValue();
long   max     = results.max("age").longValue();
double average = results.average("age");

long   matches = results.size();

繰り返し処理とスナップショット

すべてのRealmコレクションは[自動更新(#auto-updating-results)]されます。つまり、常に最新の状態が反映されているということです。ほとんどの場合、この挙動は便利なものです。しかし、要素を変更しながらコレクションをループするような場合はどうでしょうか?例えば次のような場合です。

RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll();
realm.beginTransaction();
for (int i = 0; guests.size(); i++) {
    guests.get(i).setInvited(true);
}
realm.commitTransaction();

普通はこのコードで全てのゲストが招待済みになると期待することでしょう。しかしRealmResultsは即座に更新されるので、実際には半分のゲストした招待済みになりません!招待済みに変更されたゲストは直ちにコレクションから取り除かれるので、それによって残りの要素が1つずれます。そして変数iが増加すると、要素が飛ばされてしまいます。

この問題を避けるためには、コレクションの スナップショット を取得して利用します。スナップショットでは要素の順番が変更されたり、要素が削除、変更されることはないことが保証されます。

Realmコレクションから取得されたIteratorは自動的にスナップショットを利用します。自分でスナップショットを作成するにはRealmResultsRealmListcreateSnapshot()メソッドを使用します。

RealmResults<Person> guests = realm.where(Person.class).equalTo("invited", false).findAll();

// Use an iterator to invite all guests
realm.beginTransaction();
for (Person guest : guests) {
    guest.setInvited(true);
}
realm.commitTransaction();

// Use a snapshot to invite all guests
realm.beginTransaction();
OrderedRealmCollectionSnapshot<Person> guestsSnapshot = guests.createSnapshot();
for (int i = 0; guestsSnapshot.size(); i++) {
    guestsSnapshot.get(i).setInvited(true);
}
realm.commitTransaction();

削除

クエリの結果を使ってRealmからデータを削除することができます。

// クエリを発行し結果を取得
final RealmResults<Dog> results = realm.where(Dog.class).findAll();

// 変更操作はトランザクションの中で実行する必要あり
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        // マッチしたオブジェクトから1つを削除
        results.deleteFromRealm(0);
        results.deleteLastFromRealm();

        // 特定のオブジェクトのみを削除
        Dog dog = results.get(5);
        dog.deleteFromRealm();

        // すべてのオブジェクトを削除
        results.deleteFromRealm();
    }
});

非同期クエリ

クエリもバックグラウンドスレッドで実行させることができます。

ほとんどのクエリはたとえUIスレッド上であっても同期実行させて問題ないほどに高速です。 しかし、とても複雑なクエリやとても大きなデータに対するクエリについてはバックグラウンドスレッドで実行する価値があります。

"name""John"または"Peter"であるユーザーを検索する例を見ていきましょう。

クエリの作成

RealmResults<User> result = realm.where(User.class)
                              .equalTo("name", "John")
                              .or()
                              .equalTo("name", "Peter")
                              .findAllAsync();

このクエリはスレッドをブロックせず、即座にRealmResults<User>オブジェクトを返します。 このオブジェクトは プロミス で、標準のJavaにおけるFutureと似た概念を表現しています。 クエリはバックグラウンドスレッドで実行され、完了時に先ほど返却されたRealmResultsインスタンスが更新されます。

クエリの完了後RealmResultsが更新される際に通知を受け取りたい場合は、RealmChangeListenerを登録することができます。 このリスナは、Realmに反映された最新の内容を受け取るためにRealmResultsが更新されるたびに呼びだされます。

コールバックの登録

private OrderedRealmCollectionChangeListener<RealmResults<User> callback = new OrderedRealmCollectionChangeListener<>() {
    @Override
    public void onChange(RealmResults<User> results, OrderedCollectionChangeSet changeSet) {
        if (changeSet == null) {
            // The first time async returns with an null changeSet.
        } else {
            // Called on every update.
        }
    }
};

public void onStart() {
    RealmResults<User> result = realm.where(User.class).findAllAsync();
    result.addChangeListener(callback);
}

メモリリークを避けるため、ActivityやFragment終了の際は忘れずにリスナの登録を解除してください。

public void onStop () {
    result.removeChangeListener(callback); // 特定のリスナの登録を解除
    // または
    result.removeAllChangeListeners(); // 登録されている全てのリスナの登録を解除
}

クエリの完了チェック

RealmResults<User> result = realm.where(User.class).findAllAsync();
if (result.isLoaded()) {
  // クエリの結果を利用した処理
}

同期のクエリで取得したRealmResultsに対する isLoaded() の呼び出しは、常にtrueを返します。

非同期クエリの強制ロード

非同期クエリの完了を待つこともできます。 同期クエリに戻すことになるので、クエリが完了するまで呼び出しスレッドがブロックされます(Future.get()と同様の考え方です)。

RealmResults<User> result = realm.where(User.class).findAllAsync();
result.load() // 注意。結果が返るまで呼び出したスレッドをブロックします。

非Looperスレッド

非同期クエリはLooperを持つスレッドからのみ使用することができます。 というのも、非同期クエリはクエリの結果を呼び出し元のスレッドに伝えるために、Realmが内部に持っている Handlerを使用するからです。

Looperを持たないスレッド上のRealmに対する非同期クエリの呼び出しはIllegalStateException がスローされます

Realmについて

Realm はRealm Mobile Databaseのコンテナを表すインスタンスです。Realmとは ローカルRealm同期されたRealm があります。同期されたRealmは透過的にRealm Object Serverと別のデバイス間でデータを同期します。アプリケーションからは同期されたRealmをローカルファイルのように扱いますが、そのデータは別のデバイスから更新される可能性があります。どちらのRealmでも同じように操作できますが、同期されたRealmを開くには認証済みのユーザーオブジェクト、およびアクセス権が必要なことが異なります。

Realmについてさらに詳しくはRealm Data Modelのセクションをご覧ください。

Opening Realms

Realmを開くには、Realmオブジェクトをインスタンス化するだけです。これまで見てきたように、次のようにします。

// Initialize Realm
Realm.init(context);

// Get a Realm instance for this thread
Realm realm = Realm.getDefaultInstance();

getDefaultInstance()メソッドは、デフォルトとして設定されているRealmConfigurationオブジェクトを用いてRealmインスタンスが生成されます。

Realmの設定をカスタマイズする

RealmConfigurationオブジェクトを用いてどのようなRealmを構築するかを制御できます。最小の設定は以下のコードで作成できます。

RealmConfiguration config = new RealmConfiguration.Builder().build();

上記の設定では、デフォルトの名前であるdefault.realmという名前のファイルをContext.getFilesDir()の場所に作成します。 異なる設定のRealmを使用するには下記のように新しくRealmConfigurationのインスタンスを作成します。

// ビルダーパターンでRealmConfigurationを作成します。
// "myrealm.realm"という名前でContext.getFilesDir()の場所にファイルを作成します。
RealmConfiguration config = new RealmConfiguration.Builder()
  .name("myrealm.realm")
  .encryptionKey(getKey())
  .schemaVersion(42)
  .modules(new MySchemaModule())
  .migration(new MyMigration())
  .build();
// 設定に従ったRealmを取得します
Realm realm = Realm.getInstance(config);

複数のRealmConfigurationを使用することもできます。このようにすることで、異なるバージョン、異なるスキーマ、異なるファイルのRealmを同時に扱うことができます。

RealmConfiguration myConfig = new RealmConfiguration.Builder()
  .name("myrealm.realm")
  .schemaVersion(2)
  .modules(new MyCustomSchema())
  .build();

RealmConfiguration otherConfig = new RealmConfiguration.Builder()
  .name("otherrealm.realm")
  .schemaVersion(5)
  .modules(new MyOtherSchema())
  .build();

Realm myRealm = Realm.getInstance(myConfig);
Realm otherRealm = Realm.getInstance(otherConfig);

Realm.getPath()メソッドで、Realmファイルの絶対パスを取得することができます。

同一のRealmConfigurationに対するRealmインスタンスはスレッドごとのシングルトンオブジェクトであるため、Realm.getInstance()などのstaticメソッドはスレッドごとに同じインスタンスを返すことに注意してください。

デフォルトRealm

RealmConfigurationオブジェクトをデフォルト設定として保持しておくことができます。

カスタムApplicationクラスでデフォルト設定を行うことで、アプリケーション全体で同じ設定のRealmを使用することができます。

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    // The Realm file will be located in Context.getFilesDir() with name "default.realm"
    Realm.init(this);
    RealmConfiguration config = new RealmConfiguration.Builder().build();
    Realm.setDefaultConfiguration(config);
  }
}

public class MyActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Realm realm = Realm.getDefaultInstance();
    try {
      // ... Realmを使用した処理 ...
    } finally {
      realm.close();
    }
  }
}

同期されたRealmを開く

ローカルRealmと同期されたRealmの違いは、RealmConfigurationではなくSyncConfigurationを使うことです。

SyncCredentials myCredentials = SyncCredentials.usernamePassword("bob", "greatpassword", true);
SyncUser user = SyncUser.login(myCredentials, serverUrl());
SyncConfiguration config = new SyncConfiguration.Builder(user, realmUrl())
    .schemaVersion(SCHEMA_VERSION)
    .build();
// Use the config
Realm realm = Realm.getInstance(config);

SyncConfigurationを作るには最低限Realm.Sync.Userオブジェクトと、Realm Object ServerのURLが必要です。詳しくはユーザーセクションをご覧ください。

非同期にRealmを開く

Realm.getInstanceAsync(RealmConfiguration, Realm.Callback)はLooperスレッドから呼び出さなければなりません。

Realmファイルを開くことはマイグレーションやアセットからファイルをコピーする必要があるなど、時間がかかる場合があります。また同期されたRealmで初回同期を待つ場合にもダウンロードのための時間がかかります。そのような場合にRealm.getInstanceAsync()メソッドを利用すると、自動的にバックグラウンドスレッドで非同期に時間のかかる処理を実行し、Realmが利用可能になった時点でコールバックを呼び出します。

RealmConfiguration config = new RealmConfiguration.Builder()
    .schema(42)
    .migration(new MyMigration()) // Potentially lengthy migration
    .build();

RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() {
    @Override
    public void onSuccess(Realm realm) {
        // Realm is opened and ready on the caller thread.
    }
});

初回ダウンロード

場合によっては最初にリモートのデータをすべてダウンロード完了してからRealmを使用したいということがあります。例えば、最初に郵便番号リストのマスターデータをユーザーに表示する場合などです。そのようにするにはgetInstanceAsyncAPIをwaitForRemoteInitialData()と組み合わせて使用します。そうすると、バックグラウンドスレッドでリモートデータをすべてダウンロードし、完了したらコールバックが呼ばれます。

SyncUser user = getUser();
String url = getUrl();
SyncConfiguration config = new SyncConfiguration.Builder(user, url)
    .waitForRemoteInitialData();
    .build();

RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() {
    @Override
    public void onSuccess(Realm realm) {
        // Realm is now downloaded and ready. New changes to Realm will continue
        // to be synchronized in the background as normal.
    }
});

読み取り専用のRealm

readOnly()は実行中のプロセスでのみ強制されます。他のプロセスやデバイスからは書き込みが可能です。また、読み取り専用のRealmに対して書き込みトランザクションを実行するとIllegalStateExceptionが発生します。スキーマを作成することも、この書き込みトランザクションに含まれます。必然的に、読み取り専用のRealmは他のデバイスやアプリケーションで作成しなければなりません。

作成済みのデータをアプリに組み込んでリリースしたい場合があります。ユーザーが作成するデータの他に、あらかじめ決まったマスターデータを共有したいなどの場合です。このような場合、ほとんどのケースではマスターデータを意図せず変更してしまうことは避けたいはずです。つまりRealmを読み取り専用として扱いたいことがあります。下記のような設定でRealmファイルを作成すると読み取り専用としてRealmファイルを構成できます。

RealmConfiguration config = new RealmConfiguration.Builder()
    .assetFile("my.realm")
    .readOnly()
    // It is optional, but recommended to create a module that describes the classes
    // found in your bundled file. Otherwise if your app contains other classes
    // than those found in the file, it will crash when opening the Realm as the
    // schema cannot be updated in read-only mode.
    .modules(new BundledRealmModule())
    .build();

同期されたRealmを読み取り専用として扱うには次のようにします。

SyncUser user = getUser();
String url = getUrl();
SyncConfiguration config = new SyncConfiguration.Builder(user, url)
    // Similar to `assetFile` for local Realms, but it will fetch all remote
    // data before from the server prior to opening the Realm.
    .waitForRemoteInitialData();
    .readOnly()
    .modules(new BundledRealmModule())
    .build();

RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() {
    @Override
    public void onSuccess(Realm realm) {
        // Realm is now downloaded and ready. It is readonly locally but will
        // still see new changes coming from the server.
    }
});

In-Memory Realm

データをメモリ上のみに保持する(ディスクに保存しない)Realmオブジェクトを作ることができます。

RealmConfiguration myConfig = new RealmConfiguration.Builder()
    .name("myrealm.realm")
    .inMemory()
    .build();

この設定は通常のディスクに保存するRealmインスタンスの代わりに、In-memory Realmインスタンスを生成します。

In-memory Realmであっても、使用メモリが不足したときなどにはディスクを使用することがあります。ですがIn-memory Realmによって作成されたファイルはRealmを閉じるときにすべて削除されます。

In-memory Realmと同じ名前の、通常の(ディスクに保存する)Realmインスタンスを作成することはできないので気をつけてください。

スコープを外れて、In-Memory Realmインスタンスへの参照がすべて無くなると、そのRealm内に保存されている、すべてのデータは解放されます。アプリケーションの起動中は、In-Memory Realmインスタンスへの参照を保持しておようにしてください。

Dynamic Realms

従来のRealmでは、モデルクラスはRealmObjectのサブクラスを用いて定義されます。これは型安全という点においてとても大きな利点がありますが、マイグレーションやCSVのように文字列ベースのデータを扱うような場合のように使用する型がコンパイル時には決められない場合もあります。

DynamicRealmは従来のRealmの派生版で、RealmObjectのサブクラスによって定義された型なしでデータを扱うことができます。データに対するアクセスはクラスベースではなく、文字列によって行われます。

DynamicRealmは従来のRealmと同じRealmConfigurationを使ってオープンすることができますが、スキーマ、マイグレーション、スキーマバージョンについては無視されます。

RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();
DynamicRealm realm = DynamicRealm.getInstance(realmConfig);

// DynamicRealmでは、すべてのオブジェクトはDynamicRealmObjectです
DynamicRealmObject person = realm.createObject("Person");

// すべてのフィールドは名前の文字列でアクセスします
String name = person.getString("name");
int age = person.getInt("age");

// データベースのスキーマは依然として有効なので、スキーマに違反する操作には例外がスローされます
// will throw an exception
person.getString("I don't exist");

// クエリは従来通り使用可能です
RealmResults<DynamicRealmObject> persons = realm.where("Person")
    .equalTo("name", "John")
    .findAll();

DynamicRealmは柔軟性のために型安全とパフォーマンスを犠牲にしています。DynamicRealmが提供する柔軟性がどうしても必要な場合でのみ使うようにしてください。

Realmインスタンスを閉じる

ネイティブメモリとファイルディスクリプタを解放する必要があるため、RealmCloseableインターフェースを実装しています。

Realmインスタンスが必要なくなったら、そのRealmを閉じることを忘れないようにしてください。

Realmインスタンスはリファレンスカウント方式で管理されています。もし同じスレッドで2回getInstance()を呼んだ場合は、close()も同じように2回呼ばなければなりません。

この仕組みにより、Runnableインターフェースを実装するときに、どのスレッドで実行されるのかを特に意識する必要はありません。 単にgetInstance()で始めて、最後にclose()してあげればいいのです。

UIスレッドにおいてRealmインスタンスを閉じるもっとも簡単な方法は、onDestroy()メソッドでrealm.close()を呼ぶようにすることです。

独自にLooperスレッドを作成する必要がある場合は、 以下のようにクラスを作成してください。

public class MyThread extends Thread {

    private Realm realm;

    @Override
    public void run() {
        Looper.prepare();
        realm = Realm.getDefaultInstance();
        try {
            // ... ここで実際の処理を行うハンドラーを準備する ...
            Lopper.loop();
        } finally {
            realm.close();
        }
    }
}

AsyncTaskで使用する場合は、以下のように(よくあるCloseableなオブジェクトの使い方と同じやりかたで)使うのは良い書き方です。

protected Void doInBackground(Void... params) {
    Realm realm = Realm.getDefaultInstance();
    try {
        // ... Realmインスタンスを使用するコード ...
    } finally {
        realm.close();
    }

    return null;
}

非同期にタスクを実行するためにThreadRunnableを用いて寿命の短いタスクを生成する場合は以下のようにコーディングしてください。

// Run a non-Looper thread with a Realm instance.
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        Realm realm = null;
        try {
            realm = Realm.getDefaultInstance();
            //... Realmインスタンスを使用する ...
        } finally {
            if (realm != null) {
                realm.close();
            }
        }
    }
});

thread.start();

アプリケーションのAPIレベルがminSdkVersion >= 19かつJava7以降であればtry-with-resources 構文が使えるので、下記のように書くことができます。

try (Realm realm = Realm.getDefaultInstance()) {
    // Realmインスタンスを明示的に閉じる必要はありません。
}

オートリフレッシュ

Looperスレッド(UIスレッドはデフォルトでLooperが存在します)で生成されたRealmインスタンスはオートリフレッシュ機能を持ちます。

Looperで作られたRealmインスタンスは、イベントループが回るたびに常に最新のデータに更新されるということです。この機能は非常に便利で、少ない手間でUIを常に最新の状態に更新し続けることができます。

Looperが存在しないスレッドで作られたRealmインスタンスは、自動的に更新されることはなく、最新のデータに更新するにはwaitForChange()を呼ぶ必要があります。

ここで注意すべきことは、最新でない古い履歴のデータを保持し続けることは、メモリやディスクの消費の観点から、コストの高いことだという点です。 そして、履歴が進むにつれてそのコストも増加します。 これが各スレッドにおいてRealmインスタンスを使い終わったら、すぐに閉じることが重要だという理由です。

オートリフレッシュ機能が有効になっているかどうかはisAutoRefresh()メソッドを使って確認することができます。

Realmファイルの探し方

Realmファイルの特定の仕方は、この StackOverflow の回答をご覧ください。

スレッド

Realmをマルチスレッドで使う際に覚えておかなければいけないこと、実践しなければいけないことというのは実際にはそれほど多くはありません。Realmを正しく用いることでマルチスレッドにおける一貫性やパフォーマンスの問題から開放されます。というのも、オブジェクトの自動更新クエリの自動更新が必要なことは全て行ってくれるからです。

他のスレッドが同じオブジェクトにどのよな操作を行っているかを気にすることなく、それぞれのスレッドにおいて取得したライブなオブジェクトに対して読み込みや変更の操作を行うことができます。

データの変更が必要な場合はドランザクションを使用します。他のスレッド上のオブジェクトは、ほぼリアルタイムにその変更を受け取ります。ほぼリアルタイムと書いたのは、なにか処理中であった場合は次のイベントループまで更新が遅延されるからです。

唯一の制約は、Realmのオブジェクトをスレッド間で受け渡してはいけないということです。同じデータを他のスレッドで利用する必要がある場合、それぞれのスレッドでクエリを発行してオブジェクトを取得してください。Realmの提供する仕組みを用いて変更を監視することもできます。すべての更新はスレッドを超えて同期され、通知の仕組みにより受け取ることができます。

次のサンプルをご確認ください。

Realmをマルチスレッドで使う場合のサンプル

顧客の一覧を表示するアプリケーションがあるとします。バックグラウンドスレッド(Android IntentService)で、新たな顧客の追加をポーリングしてRealmに保存します。バックグラウンドスレッドが新たな顧客を追加すると、UIスレッド上のデータも自動的に更新されます。UIスレッドはRealmChangeListenerを通して通知を受け取り、UIを更新します。クエリを発行し直す必要はありません。Realmではクエリは自動的に更新されます

// FragmentやActivity
@Override
public void onActivityCreated(Bundle savedInstanceState) {
    // ... Realmに関係しないコードは省略しています
    realm = Realm.getDefaultInstance();
    // すべての顧客を取得するクエリを構築します
    RealmResults<Customer> customers = realm.where(Customer.class).findAllAsync();
    // ... 実際にはここで、ListViewやRecyclerViewなどのアダプターを構築します

    // Realmのリスナーをセットします
    changeListener = new RealmChangeListener() {
        @Override
        public void onChange(RealmResults<Customer> results) {
            // データベースに変更が行われるたびに通知されます。
            // リスナーはLooperスレッドでのみ利用可能であることに注意してください。
            // 非Looperスレッドでは、Realm.waitForChange()を呼ぶ必要があります。
            listAdapter.notifyDataSetChanged(); // UIの更新処理
        }
    };
    // 顧客リストに変更(追加、削除、更新等)があった場合に通知するようにリスナーを登録。
    customers.addChangeListener(changeListener);
}

// 別スレッドで動くバックグラウンドサービス
public class PollingService extends IntentService {
    @Override
    public void onHandleIntent(Intent intent) {
        Realm realm = Realm.getDefaultInstance();
        try {
            // ネットワーク通信をするなどして得た値を'json'変数へ保持
            String json = customerApi.getCustomers();
            realm.beginTransaction();
            realm.createObjectFromJson(Customer.class, json); // 新たな顧客オブジェクトを保存
            realm.commitTransaction();
            // この時点で、UIスレッド上のデータは自動的に更新されます
            // ...
        } finally {
            realm.close();
        }
    }
    // ...
}

バックグラウンドスレッドが新たな顧客を追加すると、UIスレッド側のcustomersリストが自動的に更新されます。

同じことはそれぞれのオブジェクトでも行われます

バックグラウンドスレッドでは1つのオブジェクトのみを扱い、それを更新するだけでUIスレッドは自動的に最新の情報を入手することができます。変更があった際に何か処理を実行する必要がある場合は、上記のコードのようにリスナーを登録して通知を受け取ってください。

たったこれだけです。

複数スレッド間でRealmを使う

スレッド間での使用方法として注意することは、 Realm、RealmObject、RealmResultsインスタンスはスレッド間での受け渡しを行ってはいけない ということだけです。 代わりに、非同期クエリ非同期トランザクションで処理をバックグラウンドスレッドにまかせ、結果を元のスレッドで受け取ることができます。

複数のスレッドで同じデータにアクセスしたい場合は、それぞれのスレッドで別々のRealmインスタンスを(Realm.getInstance(RealmConfiguration)類似のメソッドを使って)取得する必要があります。取得したRealmインスタンスに対してクエリを発行することでオブジェクトを取得することができます。

それぞれのスレッドで取得したオブジェクトから、ディスク上の同じデータを読み書きすることが可能です。

スキーマ

デフォルトスキーマとはプロジェクトで定義されたすべてのモデルクラスのことになります。 この挙動は変更することができます。

(例)特定のRealmには一部のモデルクラスのみ含めたい場合など

カスタムRealmModuleを作ることで、このような挙動を実現できます。

// カスタムモジュールを作成します
@RealmModule(classes = { Person.class, Dog.class })
public class MyModule {
}

// RealmConfigurationにモジュールに定義したモデルクラスのみを使用するように設定します
RealmConfiguration config = new RealmConfiguration.Builder()
  .modules(new MyModule())
  .build();

// 一つのスキーマで複数のモジュールを使用することができます
RealmConfiguration config = new RealmConfiguration.Builder()
  .modules(new MyModule(), new MyOtherModule())
  .build();

スキーマの共有

ライブラリ開発者の方へ: 内部でRealmを利用するライブラリは、RealmModuleを使ってスキーマを公開する必要があります。

ライブラリプロジェクトではデフォルトRealmModuleが作られないようにする必要があります。 というのも、ライブラリにデフォルトモジュールが存在すると、ライブラリを利用するアプリケーションプロジェクトで作られるデフォルトRealmModuleと衝突してしまうからです。

下記はライブラリのRealmModuleを作って、ライブラリのモデルクラスをアプリケーションと共有する方法です。

// ライブラリ側はモジュールを必ず作成し、library = trueに設定する必要があります。
// こうすることでデフォルトモジュールが作られないようにします。
// allClasses = trueと設定するとライブラリに含まれるすべてのクラスを指定したことになります。
@RealmModule(library = true, allClasses = true)
public class MyLibraryModule {
}

// ライブラリ側では明示的にモジュールを設定しなければなりません
RealmConfiguration libraryConfig = new RealmConfiguration.Builder()
  .name("library.realm")
  .modules(new MyLibraryModule())
  .build();

// アプリケーション側ではライブラリのRealmModuleをスキーマに追加することができます
RealmConfiguration config = new RealmConfiguration.Builder()
  .name("app.realm")
  .modules(Realm.getDefaultModule(), new MyLibraryModule())
  .build();

現時点では、一つのファイルに複数のRealmModuleを記述することはできません。複数のRealmModuleを使用する場合は複数のファイルを作成し、それぞれのファイルに1つのRealmModule定義を行うようにしてください。

ライブラリプロジェクトとアプリケーションのプロジェクトを連携する際の完全なサンプルコードはこちらをご覧ください

JSON

JSONデータから直接Realmオブジェクトを作ることができます。

読み込むことができるJSON データは、StringJSONObject、およびInputStreamです。

モデルクラスに対応するフィールドが定義されていないJSONプロパティは無視されます。

一つのオブジェクトを生成する場合は、Realm.createObjectFromJson()を使用します。 JSONデータに含まれるすべてのオブジェクトを生成する場合は、Realm.createAllFromJson()を使用します。

// 都市を表すRealmObject
public class City extends RealmObject {
    private String city;
    private int id;
    // getter/setterは省略
}

// 文字列から作成
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }");
    }
});

// 複数のオブジェクトをInputStreamから作成
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        try {
            InputStream is = new FileInputStream(new File("path_to_file"));
            realm.createAllFromJson(City.class, is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
});

RealmでのJSONのパースでは以下のルールが適用されます。

  • JSONに値としてnullをもつフィールドが含まれている場合のオブジェクト作成:
    • null可のフィールドに対しては、値にnullをセットします。
    • null不可のフィールドに対しては例外をスローします。
  • JSONに値としてnullをもつフィールドが含まれている場合のオブジェクト更新:
    • null可のフィールドに対しては、値にnullをセットします。
    • null不可のフィールドに対しては例外をスローします。
  • JSONに含まれていないフィールド:
    • null可/null不可にかかわらず、既存の値を変更しません。

通知

RealmRealmResultsRealmListに対して変更を監視するためにaddChangeListenerを用いてリスナーを登録することができます。

通知はremoveChangeListener()removeAllChangeListeners()を呼ぶことで停止します。また通知はリスナーを登録したオブジェクトがCGによって回収されるか、Realmインスタンスがクローズされたときにも停止します。リスナーを登録し、通知を受けたい場合は、監視対象のオブジェクトについて強参照を保持しておく必要があります。

通知の仕組み

通知は常に登録されたスレッドで呼び出されます。スレッドにはLooperが存在していなければなりません。

監視対象のオブジェクトに関係するトランザクションが別のスレッドで発生したとき、通知は非同期にトランザクションがコミットされた後に呼び出されます。

同じスレッドで監視対象のオブジェクトに関係するトランザクションが発生したときは、通知は同期的にトランザクションがコミットされた後に呼び出されます。また、リスナーはトランザクションが開始した際にも同期的に呼ばれることがあります。これはトランザクションが開始する際には、状態を最新に更新するためです。そのとき監視対象のオブジェクトに変更がある場合は通知が呼び出されます。このとき、リスナーは書き込みトランザクション内で呼び出されるので、そこでトランザクションを開始しようとした場合は例外が発生します。そのような問題が発生した場合はRealm.isInTransaction()を用いてトランザクション中かどうかを判定し、回避します。

通知は非同期にルーパーイベントを通じて呼び出されるため、通知が届くのはルーパーキューにある他のイベントの都合により遅れることがあります。通知が即座に送られなかった場合、複数のトランザクションの変更が1つの通知にまとめられることがあります。

Realmに対する通知

リスナーを登録しておくと、バックグラウンドスレッドを使ってRealmのデータを更新したときに、UIスレッド、あるいは別のスレッドで変更に対する通知を受けることができます。 通知は他のスレッド、またはプロセスによってRealmが変更されたときに送られます。

public class MyActivity extends Activity {
    private Realm realm;
    private RealmChangeListener realmListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      realm = Realm.getDefaultInstance();
      realmListener = new RealmChangeListener() {
        @Override
        public void onChange(Realm realm) {
            // ... do something with the updates (UI, etc.) ...
        }};
      realm.addChangeListener(realmListener);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Remove the listener.
        realm.removeChangeListener(realmListener);
        // Close the Realm instance.
        realm.close();
    }
}

コレクションに対する通知

コレクションに対する通知はRealmに対する通知と異なり、きめ細やかにどのオブジェクトが変更されたのかという情報を受け取ることができます。最後の通知があったときから追加された、削除された、変更されたオブジェクトのインデックスが渡されます。

コレクションに対する通知は非同期で届きます。最初に全体の検索結果が渡され、そのあとはトランザクションごとに、対象のコレクションに変化があるたびに呼び出されます。

変更の情報についてはリスナーの引数に渡されるOrderedCollectionChangeSetオブジェクトを使って調べます。このオブジェクトは変更のあったインデックスを deletionsinsertionschanges の各要素として保持しています。

最初の2つ、 deletionsinsertions はオブジェクトがコレクションに追加されたり削除されたときのインデックスを記録しています。追加や削除はRealmにオブジェクトを追加したときや、Realmからオブジェクトを削除したとき、それと、RealmResultsの検索条件に一致する、または一致しなくなるような変更を加えたときに起こります。

オブジェクトが変更されたときには changes にインデックスがセットされます。変更とはこれまでコレクションの要素だったオブジェクトに変更が加えられたことを意味します。1対1、1対多の関連が変更されたときにも呼ばれます。

public class Dog extends RealmObject {
  public String name;
  public int age;
}

public class Person exteds RealmObject {
  public String name;
  public RealmList<Dog> dogs;
}

犬(Dog)オブジェクトの飼い主(Person)のリストに対して変更を監視すると仮定します。そのとき、次のような条件で変更が発生すると、通知が呼び出されます。

  • Personオブジェクトの名前(name)を変更する。
  • リストに含まれるPersonオブジェクトにDogオブジェクトを追加、または削除する。
  • リストに含まれるPersonオブジェクトの関連のDogオブジェクトの年齢(age)を変更する。

この通知を使うと、データの変更に対して美しくUIをアニメーションさせながら更新することができます。これまでのように、通知のたびに全体をリロードするようなことは必要ありません。

private final OrderedRealmCollectionChangeListener<RealmResults<Person>> changeListener = new OrderedRealmCollectionChangeListener<>() {
    @Override
    public void onChange(RealmResults<Person> collection, OrderedCollectionChangeSet changeSet) {
        // `null`  means the async query returns the first time.
        if (changeSet == null) {
            notifyDataSetChanged();
            return;
        }
        // For deletions, the adapter has to be notified in reverse order.
        OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges();
        for (int i = deletions.length - 1; i >= 0; i--) {
            OrderedCollectionChangeSet.Range range = deletions[i];
            notifyItemRangeRemoved(range.startIndex, range.length);
        }

        OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges();
        for (OrderedCollectionChangeSet.Range range : insertions) {
            notifyItemRangeInserted(range.startIndex, range.length);
        }

        OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges();
        for (OrderedCollectionChangeSet.Range range : modifications) {
            notifyItemRangeChanged(range.startIndex, range.length);
        }
    }
};

RealmRecyclerViewAdapterは上記の挙動を提供しています。

オブジェクトに対する通知

Realmはオブジェクトレベルの通知も提供しています。特定のRealmObjectに通知を登録すると、そのオブジェクトが削除されたり、(保存しないプロパティ以外の)フィールドに変更があった場合に通知が呼ばれます。

マネージドRealmObjectに対してのみ通知を設定できます。

変更についての詳細はリスナーに渡されるObjectChangeSet引数から取得できます。ObjectChangeSetはどのフィールドが変更されたか、もしくはRealmObject自身が削除されたかの情報を保持しています。

オブジェクトが削除された場合にはObjectChangeSet.isDeleted()trueを返します。リスナーはそれ以降呼ばれることはありません。

ObjectChangeSet.getChangedFields()は変更のあったフィールド名を返します。ObjectChangeSet.isFieldChanged()メソッドを使ってフィールドに変更があったかどうかを調べることもできます。

private final RealmObjectChangeListener<Dog> listener = new RealmObjectChangeListener<Dog>() {
    @Override
    public void onChange(Dog dog, ObjectChangeSet changeSet) {
        if (changeSet.isDeleted()) {
            Log.i(TAG, "The dog was deleted");
            return;
        }

        for (String fieldName : changeSet.getChangedFields()) {
            Log.i(TAG, "Field " + fieldName + " was changed.");
        }
    }
};

マイグレーション

データベースを使ってる場合、時間が経つにつれ、モデルクラスは変更されていくものです。 Realmでのモデルクラスは、標準的なクラスとして定義されていますので、インターフェースに変更を加えるだけで、簡単にモデル定義を変えられます。

古いスキーマのデータがディスクに保存されていない場合、モデル定義に変更を加えても問題なく動作します。 しかし、古いスキーマのデータがディスクに保存されている場合、モデル定義に対して変更を加えるとコード上のモデル定義とディスクに保存されたファイル上のスキーマが一致しなくなり例外がスローされます。 これは、RealmConfigurationに対してスキーマバージョンとマイグレーション処理を指定することで解決します。

RealmConfiguration config = new RealmConfiguration.Builder()
    .schemaVersion(2) // スキーマを変更したら必ず数字を増やしてください
    .migration(new MyMigration()) // コードが定義するモデルとディスク上のスキーマが一致しない場合に実行されるマイグレーション処理
    .build()

このようにすることで、必要に応じてマイグレーション処理が自動的に実行されます。マイグレーション処理では、Realmが提供するAPIを使用してディスクに保存されたファイルのスキーマ自体と古いスキーマに従って保存されたデータを更新することができます。

// 新たなモデルクラスを追加するマイグレーションの例
RealmMigration migration = new RealmMigration() {
  @Override
  public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {

     // DynamicRealmから変更可能なスキーマオブジェクトを取得します
     RealmSchema schema = realm.getSchema();

     // バージョン1へのマイグレーション: クラスの追加
     // Example:
     // public Person extends RealmObject {
     //     private String name;
     //     private int age;
     //     // getterとsetterは省略
     // }
     if (oldVersion == 0) {
        schema.create("Person")
            .addField("name", String.class)
            .addField("age", int.class);
        oldVersion++;
     }

     //バージョン2へのマイグレーション: プライマリキーと関連の追加
     // Example:
     // public Person extends RealmObject {
     //     private String name;
     //     @PrimaryKey
     //     private int age;
     //     private Dog favoriteDog;
     //     private RealmList<Dog> dogs;
     //     //getterとsetterは省略
     // }
     if (oldVersion == 1) {
        schema.get("Person")
            .addField("id", long.class, FieldAttribute.PRIMARY_KEY)
            .addRealmObjectField("favoriteDog", schema.get("Dog"))
            .addRealmListField("dogs", schema.get("Dog"));
        oldVersion++;
     }
  }
}

詳しくはmigrationSample appを参照してください。

Realmをオープンする時にディスクにRealmファイルがない場合は、マイグレーションの必要はありません。 その場合は、Realmはその時のスキーマのモデル定義に従って、新しい.realmファイルを作成します。 このことを利用して、開発中に頻繁にスキーマに変更があるが、 データが消えても問題がない場合 は、マイグレーションを行う代わりに.realmファイルを削除してしまう方法もあります。 開発の初期段階でモデルに頻繁に変更がある場合などに有効です。

RealmConfiguration config = new RealmConfiguration.Builder()
    .deleteRealmIfMigrationNeeded()
    .build()

暗号化

Please take note of the Export Compliance section of our LICENSE, as it places restrictions against the usage of Realm if you are located in countries with an export restriction or embargo from the United States.

RealmConfiguration.Builder.encryptionKey()に512ビットの暗号化キーを渡すことで、ディスクに保存されるRealmファイルを暗号化することができます。

byte[] key = new byte[64];
new SecureRandom().nextBytes(key);
RealmConfiguration config = new RealmConfiguration.Builder()
  .encryptionKey(key)
  .build();

Realm realm = Realm.getInstance(config);

この機能を使うと、ディスクのデータはすべて透過的に、AES-256によって暗号化/復号されます。 暗号化されたRealmをインスタンス化するには、ファイル作成時に使用された暗号化キーと同じものが毎回必要になります。

詳しくは、暗号化のサンプルコードをご覧ください。 これは暗号化キーをKeyStore APIを使って安全に管理する方法を含みます。

アダプタ

OrderedRealmCollection(RealmResultsRealmListはこのインタフェースを実装しています)のデータをUIコンポーネントとバインディングするための便利なクラスがあります。

これらのアダプターを使うためには、アプリケーションのbuild.gradleに以下のように依存ライブラリを追加する必要があります。

dependencies {
	compile 'io.realm:android-adapters:2.0.0'
}

Intent

RealmObjectをIntent経由で直接渡すことは問題が多いので、渡したいオブジェクトの識別子をIntentに含める方法をおすすめします。

オブジェクトがプライマリキーを持っている場合は、それをIntentのextraとして渡してください。

// 'id'フィールドがプライマリキーになっているpersonオブジェクトがあるとします ...
Intent intent = new Intent(getActivity(), ReceivingService.class);
intent.putExtra("person_id", person.getId());
getActivity().startService(intent);

受け取り側(Activity、Service、IntentService、BroadcastReceiverなど)では、extraからプライマリキーの値を取り出し、クエリを用いてRealmからRealmObjectを取得します。

// onCreate()やonHandleIntent()など
String personId = intent.getStringExtra("person_id");
Realm realm = Realm.getDefaultInstance();
try {
    Person person = realm.where(Person.class).equalTo("id", personId).findFirst();
    // personに対する処理など ...
} finally {
    realm.close();
}

完全なサンプルはthreadExampleにあります。

このプロジェクトでは、識別子を用いて受け渡しを行い、そこからRealmObjectを取得する典型的なコードが含まれています。

Androidフレームワークが提供するスレッド関連機能を扱う際の戦略

以下のクラスを使用する場合は注意が必要です。

AsyncTaskクラスはバックグラウンドスレッドで実行されるdoInBackground()メソッドを持っています。IntentServiceクラスにも、ワーカースレッドから呼び出されるonHandleIntent(Intent intent)メソッドがあります。

これらのメソッドの中でRealmを扱う必要がある場合は、メソッドの中でRealmのインスタンスを取得し、必要な処理が終わった後メソッドからリターンする前に確実にRealmをクローズしてください。

以下にいくつかの具体的な例を示します。

AsyncTask

以下のように、doInBackground()の中でRealmの取得とクローズを行ってください。

private class DownloadOrders extends AsyncTask<Void, Void, Long> {
    protected Long doInBackground(Void... voids) {
        // ここからはバックグラウンドスレッドで実行されています

        // Realmインスタンスの取得
        Realm realm = Realm.getDefaultInstance();
        try {
            // Realmを使った処理
            realm.createAllFromJson(Order.class, api.getNewOrders());
            Order firstOrder = realm.where(Order.class).findFirst();
            long orderId = firstOrder.getId(); // Id of order
            return orderId;
        } finally {
            realm.close();
        }
    }

    protected void onPostExecute(Long orderId) {
        // このメソッドはメインスレッドで実行されます
        // 注文(Order)IDを使ってRealmにクエリを発行して注文イブジェクトを取得します
        // 取得したインスタンスを使って処理を行ってください
    }
}

IntentService

IntentServiceLooperスレッドを持っていますが、ChangeListenerを利用することはできません。というのも、殆どの場合onHandleIntent()の実行ごとにスレッドが作りなおされてしまい、Looperが”loop”しないからです。Looperスレッドではあるので問題なくリスナーを登録することはできますが、多くの場合呼び出されることはありません。

以下のように、onHandleIntent()の中でRealmの取得とクローズを行ってください。

public class OrdersIntentService extends IntentService {
    public OrdersIntentService(String name) {
        super("OrdersIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        // ここからはバックグラウンドスレッドで実行されています

        // Realmインスタンスの取得
        Realm realm = Realm.getDefaultInstance();
        try {
          // Realmを使った処理
            realm.createAllFromJson(Order.class, api.getNewOrders());
            Order firstOrder = realm.where(Order.class).findFirst();
            long orderId = firstOrder.getId(); // Id of order
        } finally {
            realm.close();
        }
    }
}

サードパーティ製ライブラリと連携する

このセクションでは、Androidアプリケーションでよく使われるその他のライブラリとRealmを組み合わせる方法ついて説明します。

GSON

GSONは、JSONをシリアライズ/デシリアライズするGoogle製のライブラリです。GSONは特別な設定等は不要でそのままRealmとともに使用可能です。、

// Userクラスを定義します
public class User extends RealmObject {
    private String name;
    private String email;
    // getter/setterは省略
}

Gson gson = new GsonBuilder().create();
String json = "{ name : 'John', email : 'john@corporation.com' }";
User user = gson.fromJson(json, User.class);

RealmとGSONを組み合わせるサンプルコードとして、GridViewExampleも参考にしてください。

シリアライズ

Retrofitなどのライブラリで使用するような場合、デシリアライズだけでなくシリアライズも必要になります。

シリアライズについては、GSONのデフォルトのままではRealmオブジェクトをシリアライズすることはできません。 GSONはgetter/setterを使わず、直接インスタンス変数を参照するためです

GSONを使ったRealmオブジェクトのJSONシリアライズを正しく動作させるためには、それぞれのモデルに対してカスタムJsonSerializerTypeAdapterを書く必要があります。

このGistにあるコードは、それらをどのように書けばよいかを示しています。

プリミティブ型の配列

JSON APIは数値や文字列型のようなプリミティブ型(オブジェクト型ではない)の配列を含むことがあります。そのような配列を表現することは現時点のRealmではサポートされていません

JSON APIを変更するのが不可能な場合、カスタムTypeAdapterを用いて、JSONのプリミティブ型をRealmオブジェクトに自動でマッピングするようにカスタマイズすることができます。

このGistは数値型の配列をRealmオブジェクトでラップするサンプルコードです。

トラブルシューティング

Realmオブジェクトは内部に循環参照を持ったフィールドを保持している場合があります。このような場合、GSONはStackOverflowErrorエラーをスローします。過去には、RealmオブジェクトがフィールドにDrawableオブジェクトを保持しているためこの現象が発生したことがあります。

public class Person extends RealmObject {
    @Ignore
    Drawable avatar;
    // 他のフィールドやgetter/setter
}

このPersonクラスは、@IgnoreアノテーションのついたフィールドにAndroidのDrawableを保持しています。GSONがこのオブジェクトをシリアライズする際、Drawableに対する処理でStackOverflowErrorが発生していました(GitHub Issue)。これを解決するためには、以下のコードをshouldSkipFieldメソッドに記述してください。

public boolean shouldSkipField(FieldAttributes f) {
  return f.getDeclaringClass().equals(RealmObject.class) || f.getDeclaringClass().equals(Drawable.class);
}

Drawable.classの部分に注目してください。この部分によって、シリアライズの際にDrawable型のフィールドはスキップされます。

Jackson-databind

Jackson-databindは、JSONデータをJavaクラスのインスタンスへ変換するライブラリです。

Jacksonは変換を行うためにリフレクションを使用していますが、これはRealmのRxJavaサポートと競合します。というのも、セットアップによってはRxJavaのクラスをクラスローダーでロードできない場合があるからです。その場合、以下のような例外がスローされます。

java.lang.NoClassDefFoundError: rx.Observable
at libcore.reflect.InternalNames.getClass(InternalNames.java:55)
...

これを解決するためには、RxJavaへの依存を追加し実際にロードできるようにするか、以下のようなダミーファイルを作成する必要があります。

package rx;

public class Observable {
    // RxJavaのクラスが依存ライブラリに含まれていない場合に
    // Jackson-Databindのエラーを回避するためのダミーファイル
}

この問題は、Jacksonプロジェクトにも報告されています。

Kotlin

RealmはKotlinとともに使用することができます。しかし、いくつか注意すべき点があります。

具体的な例は、こちらのサンプルプロジェクトを参照してください。

Parceler

ParcelerはオブジェクトをParcelableにするために必要な、煩雑なコードを書かずにすむように、自動的にアノテーションによって生成するライブラリです。

RealmはProxyクラスを使っているので、Parcelerを使うには、さらに次に示すような設定がRealmのモデルクラスには必要になります。

// RealmObjectを継承するすべてのクラスは対応するRealmProxyクラスが生成されます。
// ParcelerにこのRealmProxyクラスの存在を伝える必要があります。
// RealmProxyクラスは、一度アプリケーションをコンパイルしないと生成されないことに気をつけてください
@Parcel(implementations = { PersonRealmProxy.class },
        value = Parcel.Serialization.BEAN,
        analyze = { Person.class })
public class Person extends RealmObject {
    // ...
}

Parcelerを使用する場合は、build.gradleで以下の設定が行われていることを確認してください。詳細はこちらを参照してください。

compile "org.parceler:parceler-api:1.0.3"
apt "org.parceler:parceler:1.0.3"

また、Parcelerを使う時には重要な制限事項がいくつかあります。

  1. モデルクラスの定義がRealmListを含む場合は、特別なアダプタクラスを登録する必要があります

  2. Realmオブジェクトが一度Percel化されると、Realmの管理から外れた状態になります。 そして、Realmオブジェクトはその時点のデータをスナップショットとして持つ、Realmの管理下にないオブジェクトとして振る舞います。その後に、どのような変更をしてもRealmには保存されません。

Retrofit

Retrofitは、REST APIを簡単にかつタイプセーフに扱うことのできるSquare製のライブラリです。

Retrofit1系、2系ともにRealmとともに使用することができますが。ただし、Retrofitが自動的にRealmにオブジェクトを保存してはくれないのでrealm.copyToRealm()realm.copyToRealmOrUpdate()を用いてRealmに保存するコードを記述する必要があります。

GitHubService service = restAdapter.create(GitHubService.class);
List<Repo> repos = service.listRepos("octocat");

// RetrofitからRealmへ要素をコピーします
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealmOrUpdate(repos);
realm.commitTransaction();

Robolectric

Robolectricは、デバイスやエミュレータの代わりにJVM上で直接、JUnitテストを動作させるライブラリです。

RobolectricはRealmにバンドルされているようなネイティブライブラリを動かすことをまだサポートしていないので、Robolectricを使ってRealmのテストを行うことはできません。

詳しくはこちらのRobolectricに対する機能リクエストをご覧ください: https://github.com/robolectric/robolectric/issues/1389

RxJava

RxJavaは、NetflixがリリースしているReactive Extensionsライブラリで、Observer patternを拡張したものです。RxJavaはデータに対する変更を監視するとともに、様々な処理を組み合わせてデータに適用することを可能にします。

RealmはRxJavaをサポートしています。これは、以下のRealmのクラスに対するObservableオブジェクトが利用できるということを意味します: RealmRealmResultsRealmObjectDynamicRealmDynamicRealmObject

// Realm、Retrofit、RxJavaを組み合わせる例(簡潔に表現するためにRetroLambdaの記法を使用)。
// すべてのPersonを読み込み、それぞれのGitHubの情報を取得して表示。
Realm realm = Realm.getDefaultInstance();
GitHubService api = retrofit.create(GitHubService.class);
realm.where(Person.class).isNotNull("username").findAllAsync().asObservable()
    .filter(persons.isLoaded)
    .flatMap(persons -> Observable.from(persons))
    .flatMap(person -> api.user(person.getGithubUserName())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe(user -> showUser(user));

非同期クエリはノンブロッキングなので、即座にRealmResultsインスタンスを返すことに注意する必要があります。クエリが完了したリストのみを扱いたい場合(たいていの場合はそうだと思います)は、filterオペレータの中でRealmResults<E>.isLoaded()を使ってObservableをフィルタリングする必要があります。

RealmResultsのロードが完了しているかを調べることで、クエリの実行が終わったかどうかを調べることができます。

詳しくはRxJavaサンプルプロジェクトを参照してください。

RxJavaに関する設定

RxJavaは必須の依存ライブラリではありません。これは、Realmが自動的にRxJavaのライブラリを依存ライブラリに含めてはくれないことを意味しています。Realmを利用するアプリでどのバージョンのRxJavaを使用するか決めることができます。また、RxJavaを含めないようにすることで、メソッド数の増加を抑えることも可能です。RxJavaを使用する場合は、build.gradleに依存ライブラリとして明示的に追加してください。

dependencies {
  compile 'io.reactivex:rxjava:1.1.0'
}

RxObservableFactoryを実装することで、RealmがどのようにObservableインスタンスを作成するかをカスタマイズすることができます。この設定は、RealmConfigurationで行います。

RealmConfiguration config = new RealmConfiguration.Builder()
  .rxFactory(new MyRxFactory())
  .build()

RxObservableFactoryが提供されない場合、RealmはRxJava 1.1.*をサポートするRealmObservableFactoryをデフォルトのRxObservableFactoryとして使用します。

テストとデバッグ

RealmをJUnit3、JUnit4、Robolectric、Mockito、PowerMockと組み合わせる方法については、unitTestExampleを御覧ください。

Android Studioでのデバッグ

Android StudioやIntelliJを使う際に気をつけなければならない落とし穴があります。それは、デバッグ機能が表示する値が、実際の値とは違う場合があるということです。

例えば、RealmObjectのフィールドの値を表示させた時に、実際にRealmが保持している値とは異なる値が表示されることがあります。というのも、Android Studioが表示しているフィールドが実際には使われていないことがあるからです。

Realmは永続化されているデータに対して読み書きを行うために、モデルクラスに対してビルドの際にプロキシクラスを生成し、getterとsetterをオーバーライドします(詳細はこちら)。フィールドではなく、実際にアクセッサメソッドが返す値に対しては正しい値を表示します。以下のスクリーンショットで実際にどのように表示されるかが確認できます。

Android Studio, IntelliJ Debug RealmObject

このスクリーンショットでは、デバッガは113行目で停止しています。personperson.getName()person.getAge()の3つに注目してください。107行目から111行目で、personインスタンスの値を更新しています。113行目でデバッガが停止していますが、表示されているフィールドの値は更新した値とは異なったものになっています。person.getName()person.getAge()が表示している値のみが正しいことを確認してください。

RealmObject.toString()メソッドは正しい値を表示しているにもかかわらず、フィールドの値を表示させると実際とは異なる値が表示されることに注意してください。

NDK部分のデバッグ

Realmはネイティブコードを含んだライブラリです。Crashlyticsのようなクラッシュレポーティングツールでネイティブエラーの情報を収集することで、バグの発生時により有益な情報を得られる可能性が高まります。

ネイティブコードのクラッシュの際はデフォルトのスタックトレースに含まれる情報はあまり役に立たず、デバッグが困難であることがよくあります。Crashlyticsはネイティブクラッシュの際に有用な情報を取得する機能を持っています。この機能を有効にするには、こちらのガイドに従ってください。androidNdkOutおよびandroidNdkLibsOutの指定は不要です。

crashlytics {
  enableNdk true
}

現バージョンにおける制限事項

Realmはできるかぎり制限なく利用できるよう努力しています。また、利用していただいている方々からのフィードバックを元に新しい機能を追加していきます。現時点で存在する制限事項について以下にまとめます。

現時点ではRealmモデルクラスはfinalvolatileフィールドをサポートしていません。これは主にRealmの管理下にあるオブジェクトとRealmの管理下にないオブジェクトとの振る舞いの食い違いを避けることが目的です。

Realmのモデルクラスは、RealmObject以外のクラスを継承することはできません。モデルクラスでは、デフォルトコンストラクタ(引き数なしpublicコンストラクタ)の中を空にしておく必要があります。というのも、デフォルトコンストラクタがフィールドにアクセスしてしまうと、モデルクラスにRealmインスタンスがセットされていることを前提とした処理が実行されてしまうからです。実際にはデフォルトコンストラクタが実行される時点ではRealmインスタンスがセットされていないため、例外がスローされます。デフォルトコンストラクタ以外のコンストラクタを作成することは問題ありません。

詳細については、GitHub issues をご覧ください。

一般的な制限事項

Realmは、柔軟性とパフォーマンスのバランスを上手く保つため、保存するデータに対していくつか制約があります。

  1. クラス名は57文字が上限です。Realm Javaの内部では、class_という文字をクラス名の前に付けて扱われます。Realm Browserはこの文字列もクラス名の一部として表示します。
  2. フィールド名は63文字が上限です。
  3. 異なるパッケージであっても、同じ名前のモデルクラスを使うことはできません。
  4. ネストしたトランザクションはサポートしていません。検出された場合、例外が投げられます。
  5. 文字列とバイト配列(byte[])は16MBが上限です。
  6. Realmモデルクラスはfinalvolatileフィールドをサポートしていません。主にRealmによって管理されているオブジェクトと管理されていないオブジェクト間の挙動の不一致を避けるためです。
  7. Realmモデルクラスにカスタムコンストラクタを記述した場合はPublicのデフォルトコンストラクタ(引数なしのコンストラクタ)を定義する必要があります。
  8. RealmモデルクラスはRealmObject以外のクラスを継承できません。

ソートと検索の際の文字列の扱い

文字列に対するソートと大文字小文字を区別しない(Case.INSENSITIVE)検索については、Latin Basic、Latin Supplement、Latin Extended A、Latin Extended Bの文字セット(UTF-8の範囲は、0-591)のみをサポートしています。どういうことかというと、大文字と小文字の並び替えについては標準ライブラリの挙動とは異なります。つまり、'- !"#0&()*,./:;?_+<=>123aAbBcCxXyYzZの並びがRealmによる並べ替えの結果です。さらに大文字小文字を区別しないフラグが設定できるのは、equalTo()notEqualTo()contains()endsWith()beginsWith()like()のクエリです。詳しくはこちらをご覧ください。

スレッド

Realmは、複数の異なるスレッドから同じRealmファイルに同時にアクセスすることができますが、スレッド間で、Realm、Realmオブジェクト、クエリ、RealmResultsを渡すことはできません。

マルチスレッド環境におけるRealmの使い方について、マルチスレッドのサンプルコードをご覧ください。また、詳しくはこちらに記載されています。

複数のプロセスからのRealmファイルへのアクセス

複数の異なるスレッドから同時にRealmファイルへアクセスすることができますが、複数のプロセスから同時にアクセスすることはできません。

Realmファイルを異なるプロセスで同時に使用するときは、Realmファイルをコピーするか新しく作成してください。

RealmObjectのhashCode

RealmObjectはライブなオブジェクトなので、他のスレッドから変更されることがあります。RealmObject.equals()trueになるRealmObject同士ではRealmObject.hashCode()も同じ値を返さなければいけませんが、RealmObject.hashCode()が返す値は変わる可能性があるためHashMapHashSetのキーとしてRealmObjectを使うことは避けてください。

オブジェクト自身を変更しない場合であっても、他のオブジェクトの削除等に伴ってRealmObject.hashCode()が変わることもあります。

ベストプラクティス

Androidとの連携

RealmはAndroidで使うことを前提に設計されています。気をつけなければいけない点は、RealmObjectスレッド間で受け渡せないということです。このことをしっかりと理解することで、アクティビティ間、バックグラウンドサービスやブロードキャストレシーバーとの間などいろいろなところでRealmのオブジェクトを扱えるようになります。

Application Not Responding(ANR)エラーを回避する

一般的にはRealmはAndroidのメインスレッド上で読み書きを行っても問題ないほどに十分高速です。しかし書き込みトランザクションはすべての他のスレッドの書き込みトランザクションをブロックしてしまうので、Realmへの書き込み操作は常に(Androidのメインスレッドではなく)バックグラウンドスレッドで行うことを推奨します。バックグラウンドスレッドで書き込み処理を行う方法については、非同期トランザクションを参照してください。

Realmインスタンスのライフサイクルを制御する

RealmObjectRealmResultsは参照するすべてのデータを遅延ロードします。そのため、RealmObjectRealmResultsにアクセスする必要がある間はRealmインスタンスをオープンしたままにしておくことが必要です。オープンやクローズにかかるオーバーヘッドを避けるため、Realmは内部で参照カウントによるキャッシングを行っています。つまり、オーバーヘッドを気にせずにRealm.getDefaultInstance()を何度も実行でき、実行した回数だけクローズを行えば関連するリソースはすべて解放されることを意味します。

UIスレッドでは、RealmインスタンスはActivityやFragmentごとにオープンし、それらが破棄される際にクローズすることが最も安全で簡単な実装方法ということになります。

// RealmをApplicationクラスでセットアップします
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder().build();
        Realm.setDefaultConfiguration(realmConfiguration);
    }
}

// onCreate()/onDestroy()はアクティビティ間の遷移でライフサイクルに重なる部分があるので、
// Activity 2 の onCreate() は Activity 1 の onDestroy() より後に実行されます。
public class MyActivity extends Activity {
    private Realm realm;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        realm = Realm.getDefaultInstance();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        realm.close();
    }
}

// FragmentではonDestroy()が呼ばれない場合があるので、onStart()/onStop()を使用してください。
public class MyFragment extends Fragment {
    private Realm realm;

    @Override
    public void onStart() {
        super.onStart();
        realm = Realm.getDefaultInstance();
    }

    @Override
    public void onStop() {
        super.onStop();
        realm.close();
    }
}

RealmResultsとRealmObjectsの再利用

UIスレッドやその他のすべてのLooperスレッドでは、すべてのRealmObjectRealmResultsはRealmへの変更が行われると自動的にリフレッシュが行われ変更が反映されます。 このことは、RealmChangedListenerが呼び出された際にRealmObjectRealmResultsを取得しなおす必要がないことを意味しています。 これらのオブジェクトはその時点でアップデート済みなので、そのまま更新後の値を表示するために使用することができます。

public class MyActivity extends Activity {

    private Realm realm;
    private RealmResults<Person> allPersons;
    private RealmChangeListener realmListener = new RealmChangeListener() {
        @Override
        public void onChange(Realm realm) {
            // ビューの再描画を要求するだけです。`allPersons`には既に最新のデータが反映されています。
            invalidateView();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        realm = Realm.getDefaultInstance();
        realm.addRealmChangeListener(listener);
        allPerson = realm.where(Person.class).findAll(); // "live"なオブジェクトを取得
        setupViews(); // ビューのセットアップ
        invalidateView(); // 現在のデータでビューの描画を要求
    }

    // ...
}

Auto-incrementing IDs

オートインクリメントIDはRealmでは意図的にサポートしていません。主な理由としては分散環境でそのような値を生成することは不可能だからかです。そしてローカルRealmと同期されたRealmの互換性は非常に重要だからです。Realmは関連を保持するにはプライマリキーを必要としないことにも注目してください。

ユースケースによってはオートインクリメントIDではなく、他の手段が適していることがあります。何の目的でオートインクリメントIDのような値がが必要なのかを十分に考慮し、使い分けてください。

1) オブジェクトを一意に識別するためのユニークIDを提供する この目的にはGUIDが利用できます。ほとんどのケースで十分に機能するユニークな値を取得できます。デバイスがオフラインであっても問題ありません。

public class Person extends RealmObject {
    @PrimaryKey
    private String id = UUID.randomUUID().toString();
    private String name;
}

2) あまり厳密でないオブジェクトの生成順を提供する. 例えばツイートを発言順で並べるようなときです。この目的にはcreatedAtフィールドが利用できます。これはプライマリキーには適していません。

public class Person extends RealmObject {
    @PrimaryKey
    private String id = UUID.randomUUID().toString();
    private Date createdAt = new Date();
    private String name;
}

3) 厳密なオブジェクトの生成順を提供する. タスクリストなどの場合です。この目的にはRealmListを使用します。このクラスは順序を保持しています。デバイスがオフラインのときも同様です。

public class SortedPeople extends RealmObject {
    @PrimaryKey
    private int id = 0
    private RealmList<Person> persons;
}

public class Person extends RealmObject {
    private String name;
}

// Create wrapper object when creating object
RealmConfiguration config = new RealmConfiguration.Builder()
  .initialData(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        realm.insert(new SortedPeople());
    }
  });

// Insert objects through the wrapper
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        SortedPeople sortedPeople = realm.where(SortedPeople.class).findFirst();
        sortedPeople.getPersons().add(new Person());
    }
});

それでもなお、オートインクリメントIDが必要なユースケースがある場合は、このようなヘルパークラスを使うことができます。ただし、このクラスで生成された値は、次の環境では使用できません。

1) Realmオブジェクトが複数のプロセスから作られるとき 2) Realmを複数のデバイスで共有したいとき

オートインクリメントIDをプロセス間で安全に作成するには、現在のIDの最大値を求めるクエリをトランザクション内で行う必要があります。

realm.beginTransaction();
Number maxValue = realm.where(MyObject.class).max("primaryKeyField");
long pk = (maxValue != null) ? maxValue + 1 : 0;
realm.createObject(MyObject.class, pk++);
realm.createObject(MyObject.class, pk++);
realm.commitTransaction();

レシピ

Realmをつかって特定のタスクを実装する際のレシピとして、いくつかのリンクを用意しました。今後拡充していく予定です。

この他にもサンプルコードの要望がある場合は、GitHubにissueを作成してください。

同期されたRealm

Realm Mobile Platform (RMP)はRealm Mobile Databaseを拡張し、ネットワークを通じてデバイス間の自動的なデータ同期を実現します。同期されたRealmに対応するために、いくつかの新しいクラスが導入されました。同期されたRealmを扱うためのコンポーネントは追加オプションとなっており、ローカルデータベースとして使う場合は必須ではありません。下記以降でRealm Mobile Databaseに新しく同期のために追加されたクラスを説明します。

Realm Mobile Platformを有効にする

Realm Mobile Platformを有効にするには、アプリケーションのbuild.gradleに下記のようにセクションを追加してください。

realm {
  syncEnabled = true;
}

設定はこれだけで完了です。

ユーザー

Realmユーザー(SyncUser)はRealm Object Serverの中心となるクラスです。サーバーと同期されたRealmファイルを開くには、まず認証済みのユーザーでログインする必要があります。SyncUserクラスはユーザー名+パスワードによる認証に加え、SNSを使う方法などいくつかの認証方式をサポートしています。

ユーザーの作成、およびログインは次の2つの値が必要です。

  • Realm認証サーバーの接続先を示すURL。
  • 認証プロバイダに渡す認証情報(例: ユーザー名/パスワード、アクセスキーなど)。

サーバーURL

認証サーバーURL とはRealm Object Serverの接続先URLを示す文字列です。

String serverUrl = "http://my.realmserver.com:9080";

さらに詳しくは認証についてのドキュメントをご覧ください。

認証

ユーザーログインには認証が必要です。どのような認証プロバイダがサポートされているかについては、Realm Object Serverの認証についてのドキュメント をご覧ください。

ユーザー認証に使用するアカウント情報は次に示すさまざまな方法で作成できます。

  • ユーザー名/パスワードの組み合わせ。
  • 組み込みのサードパーティ認証サービスによって認証されたアクセストークン。
  • カスタム認証プロバイダによって認証されたトークン(カスタム認証をご覧ください)。

ユーザー名とパスワードによる認証はRealm Object Server内に完全に閉じています。ユーザーに関するコントロールをすべて自分自身で行うことができます。その他の認証サービスを利用する場合は、アプリケーション内で外部のサービスに接続し、認証トークンを取得する必要があります。

下記に数種類のアカウント情報の作成方法を示します。

ユーザーの作成とログイン

Realmユーザー(User)はRealm Object Serverの中心となるクラスです。サーバーと同期されたRealmファイルを開くには、まず認証済みのユーザーでログインする必要があります。Userクラスはユーザー名+パスワードによる認証に加え、SNSを使う方法などいくつかの認証方式をサポートしています。

ユーザーの作成、およびログインは次の2つの値が必要です。

  • Realm認証サーバーの接続先を示すURL。
  • 認証プロバイダに渡す認証情報(例: ユーザー名/パスワード、アクセスキーなど)。 この2つのオブジェクトはUserオブジェクトの作成に使われます。

認証情報を作成する

サードパーティの認証プロバイダを使用する際の認証情報についてはRealm Object Serverの認証に関するドキュメントにて詳細を説明しています。

ユーザー名/パスワード
SyncCredentials myCredentials = SyncCredentials.usernamePassword(username, password, true);

usernamePassword()メソッドの3番目の引数は、新しくユーザーを作成するかどうかを示します。つまり最初の1回だけtrueを指定します。ユーザーがすでに存在する場合、つまりユーザーを最初に作成した後は、このパラメータはfalseにしてください。

Google
String token = "..."; // a string representation of a token obtained by Google Login API
SyncCredentials myCredentials = SyncCredentials.usernamePassword(username, password, true);
Facebook
String token = "..."; // a string representation of a token obtained by Facebook Login API
SyncCredentials myCredentials = SyncCredentials.facebook(token);

カスタム認証
String token = "..."; // a string representation of a token obtained from your authentication server
Map<String, Object> customData = new HashMap<>();
SyncCredentials myCredentials = SyncCredentials.custom(
  token,
  'myauth',
  customData,
);

メモ: ログイン情報として3つめの引数に任意を値を渡すことができます。詳しくはAPIリファレンスをご覧ください。

SSL/TLS

Realm Object Serverとの接続でTLSを有効にするには(realms://を使った接続)サーバーに信頼されたルート証明書をPEM形式のファイルで配置します。

例:

SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "realms://host/~/myapp")
                .serverCertificate("root_ca.pem")
                .build();
realm = Realm.getInstance(syncConfiguration);
  • 証明書が格納されているroot_ca.pemファイルは、アプリのassetsディレクトリに配置する必要があります。
.
├── main
│   ├── AndroidManifest.xml
│   ├── assets
│   │   └── root_ca.pem
│   ├── java
  • 信頼するルートCAの正しいPEM証明書をダウンロードして検証することは、開発者の責務になります。 Mozillaから提供されているルートCAの一覧をご覧ください。

(デバッグ時もしくは自己証明書を使っている場合に限り)disableSSLVerificationを呼ぶことで証明書の検証をオフにすることも可能です。

SyncConfiguration syncConfiguration = new SyncConfiguration.Builder(user, "realms://host/~/myapp")
                .disableSSLVerification()
                .build();
realm = Realm.getInstance(syncConfiguration);

ログイン

これでログインに必要なオブジェクトはすべて準備ができました。次のようにしてRealm Object Serverにログインします。

String authURL = "http://my.realm-auth-server.com:9080/auth";
SyncUser user = SyncUser.login(myCredentials, authURL);

Realm Mobile Platformでは1つのアプリケーション内で同時に複数のユーザーを利用することができます。

例えばメールのクライアントアプリでは複数のアカウントを切り替えて使用できます。そのような動作を実現するために、複数のユーザーを好きなときに同時に有効にできます。認証済みのユーザーオブジェクトをすべて取得するにはSyncUser.all()クラスメソッドを利用します。

ログアウト

同期されたRealmからログアウトするには、下記のようにします。

user.logout();

ユーザーがログアウトしたら同期は停止します。ログアウトするにはそのユーザーが持つRealmインスタンスすべてがクローズされている必要があります。ログアウトしたユーザーはそれ以降SyncConfigurationを使ってRealmを開くことはできません。

ユーザーを活用する

スタンドアローンのRealmではRealmの設定を変更するためにRealmConfigurationを使用しました。 Realm Mobile PlatformではSyncConfiguration`という拡張されたクラスを利用します。 設定は認証ユーザーと同期サーバーのURLと密接に関連します。 同期サーバーURLはチルダ(“~”)を含むことができます。チルダはユーザーを一意に識別するIDに内部で自動的に変換されます。 この仕組みは、ユーザーごとに別のRealm URLを指定する場合に非常に便利です。 同期されたRealmファイルはフレームワークによって管理されていますが、必要に応じて保存場所を変更することができます。

SyncUser user = getUserFromLogin();
String serverURL = "realm://my.realm-server.com:9080/~/default";
SyncConfiguration configuration = new SyncConfiguration.Builder(user, serverURL).build();

現在のユーザーを取得することもできます。現在のユーザーは認証期限が過ぎていない最後にログインしたユーザーになります。

SyncUser user = SyncUser.currentUser();

ユーザーと認証情報をJSONとして取得できます。

String userJson = user.toJson();

ユーザーをJSONとして保存した場合、使用するときに再度サードパーティの認証プロバイダにログインする必要はありません。その代わりに、下記のようにしてRealm Object Serverに対してリフレッシュアクセストークンを送ります。

SyncUser user = SyncUser.fromJson(userJson);

Realmは現在のユーザーをUserStoreに保存します。デフォルトではUserStoreはShared Preferenceファイルを利用しています。SyncManager.setUserStore(userStore)メソッドを用いて保存に使用する仕組みを変更できます。

ユーザーオブジェクトと認証データは機密情報であることを忘れないようにしてください。

複数のユーザーがログイン済みの場合は、下記のようにしてすべてのログイン済みユーザーを取得できます。

Map<String, SyncUser> users = SyncUser.all();

ログイン中のユーザーが存在しなければ、空のMapオブジェクトが返ります。

管理者ユーザー

管理者ユーザーはRealm Object Serverにおける管理レベルのアクセス権限を、同じサーバー上のすべてRealmに対して持っています。ユーザーが管理者かどうかはSyncUser.isAdmin()メソッドを使用して調べられます。このプロパティは最後にユーザーがログインした時の状態を反映しています。

SyncUser user = SyncUser.login(credentials, serverURL);
if (user != null) {
    // user.isAdmin() => the user is an admin user
}

同期されたRealmを開く

スタンドアローンで動作するRealmを設定するにはRealmConfigurationオブジェクトを使用します。Realm Mobile Platformを使って同期されたRealmの設定を行うにはSyncConfigurationを使用します。SyncConfigurationはユーザーオブジェクトと同期サーバーのURLに強く関連しています。

同期サーバーのURLにはチルダ(~)を含むことができ、各ユーザーのユニークIDを表します。Realm URL中のチルダは自動的にユーザーIDに置き換えられます。この仕組みはユーザーごとに別のURL管理する必要がなく、ユーザーごとにデータを分ける場合に便利です。同期されたRealmが実際にディスクに保存される場所はフレームワークによって自動的に管理されていますが、任意の場所に変更することもできます。

SyncUser user = getUserFromLogin();
String serverURL = "realm://my.realm-server.com:9080/~/default";
SyncConfiguration configuration = new SyncConfiguration.Builder(user, serverURL).build();

注意: URLにはファイル拡張子”.realm”を含めてはいけません。上記の例では主要なファイル名は”default”となりますが、Realmは自動的にその名前を用いて関連するファイルやディレクトリを作成します。

Realm realm = Realm.getInstance(syncConfiguration);

リモートサーバーのデータをすべてダウンロードしてからRealmを使いたいということがあります。例えば、ユーザーに利用可能なすべての郵便番号を表示する、といった場合です。そのような場合はgetInstanceAsync APIをwaitForRemoteInitialData()フラグと同時に使用します。そうするとバックグラウンドでサーバーのデータをダウンロードし、Realmが利用可能になった時点でコールバックを呼び出します。

SyncUser user = getUser();
String url = getUrl();
SyncConfiguration config = new SyncConfiguration.Builder(user, url)
    .waitForRemoteInitialData();
    .build();

RealmAsyncTask task = Realm.getInstanceAsync(config, new Realm.Callback() {
    @Override
    public void onSuccess(Realm realm) {
        // Realm is now downloaded and ready. New changes to Realm will continue
        // to be synchronized in the background as normal.
    }
});

同期セッション

同期されたRealmとRealm Object Serverとの接続はSyncSessionオブジェクトとして表されます。 特定の同期されたRealmに対するセッションオブジェクトはSyncManager.getSession(SyncConfiguration)メソッドを用いて取得します。

同期の進捗を通知

セッションオブジェクトの 同期の進捗通知を監視 することで、アプリとRealm Object Server間の同期(アップロード、ダウンロード両方)の進捗状態を監視できます。

同期の進捗通知は同期システムによって継続的に呼び出されます。コールバックはワーカースレッドから呼ばれます。つまり、通知のタイミングでUIを更新するなら、Activity.runOnUiThread(Runnable)のような仕組みを用いてUIスレッドにディスパッチします。複数の通知を同時に監視でき、アップロードとダウンロードの両方について設定することができます。

コールバックが呼び出されると、全体のバイト数と、そのうちの転送済みのバイト数がパラメータとして渡されます(全体のバイト数は、転送済みのバイト数と未転送のバイト数として渡されます)。

監視を解除するにはSyncSession.removeProgressListener(listener)を使用します。

通知にはProgressMode.CURRENT_CHANGESProgressMode.INDEFINITELYの2つのモードがあります。ProgressMode.INDEFINITELYを指定すると、通知を停止しない限りは、継続的にデータの転送が行われるたびに何度でも継続してコールバックが呼び出され、その時点の転送済みバイト数が渡されます。この種類の通知は例えばネットワークのインジケータを表示するために使用できます。下記の例では通信が発生するとインジケータを表示します。この通知では何度でもProgress.isTransferComplete()の状態が通知されます。

ProgressListener listener = new ProgressListener() {
    @Override
    public void onChange(Progress progress) {
        activity.runOnUiThread(new Runnable) {
            @Override
            public void run() {
                if (progress.isTransferComplete()) {
                    hideActivityIndicator();
                } else {
                    showActivityIndicator();
                }
            }
        }
    }
};

SyncSession session = SyncManager.getSession(getSyncConfiguration());
session.addDownloadProgressListener(ProgressMode.INDEFINITELY, listener);

// When stopping activity
session.removeProgressListener(listener);

ProgressMode.CURRENT_CHANGESを指定すると、そのタスクが完了した時点で通知が停止します。登録されたときに、転送されるべきバイト数を取得し、その値を基準に進捗が通知されます。この通知は転送バイト数がその初期値に達するか超えると、Progress.isTransferComplete()trueのイベントを通知し、その後は通知は呼ばれません。監視の登録は自動的に解除されます。この種類の通知は、例えば初回のRealmファイルの同期が完了するまでの進捗状況の表示や、大きめのデータのアップロードの進捗表示に利用できます。

final SyncSession session = SyncManager.getSession(getSyncConfiguration());
ProgressListener listener = new ProgressListener() {
    @Override
    public void onChange(Progress progress) {
        activity.runOnUiThread(new Runnable) {
            @Override
            public void run() {
                if (progress.isTransferComplete()) {
                    hideProgressBar();
                    session.removeProgressListener(this);
                } else {
                    updateProgressBar(progress.getFractionTransferred());
                }
            }
        }
    }
};
setupProgressBar();
session.addDownloadProgressListener(ProgressMode.CHANGES_ONLY, listener);

アクセスコントロール

Realm Mobile Platformでは、どのユーザーが同期されたRealmにアクセスできるのかを柔軟にコントロールできる仕組みがあります。 例えば、1つのRealmを複数のユーザーが共同で編集するようなアプリを作ることもできます。 また、あるユーザーのみが編集できるファイルを公開し、他のユーザーは読み取り専用で閲覧だけを可能にするといったことも可能です。

Realmのアクセス権限には3種類のレベルが存在します。

  • mayReadは読み取り権限が与えられていることを示します。
  • mayWriteは書き込み権限が与えられていることを示します。
  • mayManageはそのRealmに対するアクセス権限を変更できることを示します。

アクセス権限を明示的に与えなかった場合は、デフォルトの動作ではRealmファイルの作成者だけがそのファイルにアクセスできます。 唯一の例外はAdminユーザーです。Adminユーザーは常にすべてのRealmファイルに対してすべてのアクセス権限を持ちます。

書き込みのみという権限(例: mayWritemayReadなしでセット)は現在はサポートしていません。

アクセス権限の概念についてより詳しく知るには、Realm Object Serverドキュメントのアクセスコントロールセクションをご覧ください。

Management Realm

アクセス権限を変更はManagement Realmに変更内容を書き込むことで行います。 Management Realmは一般の同期されたRealmとまったく同じものです。しかし、Realm Object ServerはManagement Realmの変更を監視するように設計されています。 特定のRealmファイルに対するアクセス権限を変更するには、Permission ChangeオブジェクトをManagement Realmに追加します。すると、その変更がRealmファイルに対して適用されます。

特定のユーザーに対するManagement Realmオブジェクトを取得するには、SyncUser.getManagementRealm()メソッドを使用します。このRealmは通常のRealmを扱う場合と同じルールが適用されます。使用しなくなったら閉じる必要があります。

アクセス権限を変更する

Realmファイルに対してアクセス権限を変更することは2つの方法があります。PermissionChangeオブジェクトをManagement Realmに書き込む方法とPermissionOffer/Responseを使用する方法です。

PermissionChange

PermissionChangeオブジェクトをオブジェクトをManagement Realmに保存すると、同期されたRealmのアクセス権を直接変更することができます。

SyncUser user = SyncUser.currentUser();
String realmURL = "realm://ros.example.com/~/default"; // The remote Realm URL on which to apply the changes
String anotherUserID = "other-user-id"; // The user ID for which these permission changes should be applied

Realm realm = user.getManagementRealm();
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Boolean mayRead = true; // Grant read access
        Boolean mayWrite = null; // Keep current permission
        boolean mayManage = false; // Revoke management access
        PermissionChange change = new PermissionChange(realmUrl,
                                                       anotherUserID,
                                                       mayRead,
                                                       mayWrite,
                                                       mayManage);
        realm.insert(change);
    }
});

特定のユーザーが作成したすべてのRealmファイルのアクセス権限を変更するには、realmURL引数に*を指定します。 すべてのユーザーに対してアクセス権限を変更するには、userID引数に*を指定します。

PermissionChangeオブジェクトの内容をObject Serverが反映する処理が完了すると、statusとstatusMessage`プロパティに結果がセットされます。

アクセス権限の変更が成功したかどうかを知るには、他のRealmオブジェクトと同様に、PermissionChangeオブジェクトの変更を監視します。変更通知については通知セクションをご覧ください。

下記はPermissionChangeオブジェクトの変更を通知するコードの一例です。

SyncUser user = SyncUser.currentUser();
Realm realm = user.getManagementRealm();
final String permissionId = "permission-id";
PermissionChange change = realm.where(PermissionChange.class).equalTo("id", permissionId).findFirst();
change.addChangeListener(new RealmChangeListener<PermissionChange>() {
    @Override
    public void onChange(PermissionChange change) {
        if (change.getId().equals(permissionId)) {
            Integer status = change.getStatusCode();
            if (status == 0) {
                // Handle success
                realm.close();
            } else if (status > 0) {
                // Handle error
            }
        }
    }
});
PermissionOffer/Response

PermissionOfferPermissionOfferResponseクラスを用いてユーザー間でRealmを共有できます。すべてはクライアント側のコードだけで実現できます。サーバーサイドのコードを書く必要は一切ありません。Realmの共有は次の手順で行います。

  1. PermissionOfferオブジェクトを作成し共有するRealmファイルのユーザーのマネジメントRealmに保存します。
  2. PermissionOfferオブジェクトがサーバーに同期され、Realm Object Serverによって処理されるのを待ちます。完了するとサーバーによりtokenプロパティに値がセットされます。
  3. トークンを共有したい相手のユーザーに送ります。
  4. トークンを受け取ったユーザーはPermissionOfferResponseオブジェクトを作成してマネジメントRealmに保存します。
  5. PermissionOfferResponseオブジェクトがサーバーに同期され、ealm Object Serverによって処理されるのを待ちます。完了するとサーバーによりrealmUrlプロパティに値がセットされます。
  6. そうすると共有先のユーザーはRealmファイルにアクセスできるようになります。
SyncUser user = getUser("user");
Realm managementRealm = user.getManagementRealm();
String sharedRealmUrl = "realm://my.server/~/my-realm";
boolean mayRead = true;
boolean mayWrite = true;
boolean mayManage = true;
Date expiresAt = null; // Offer never expires;
final PermissionOffer offer = new PermissionOffer(sharedRealmUrl, mayRead, mayWrite, mayManage, expiresAt);
String offerId = offer.getId();
managementRealm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        realm.insert(offer);
    }
});

// Wait for server to handle the offer
RealmResults<PermissionOffer> offers = managementRealm.where(PermissionOffer.class)
        .equalTo("id", offerId)
        .equalTo("statusCode", 0)
        .findAll();
offers.addChangeListener(new RealmChangeListener<RealmResults<PermissionOffer>>() {
    @Override
    public void onChange(RealmResults<PermissionOffer> offers) {
        PermissionOffer offer = offers.first();
        String token = offer.getToken();
        // Offer is ready, send token to the other user
        sendTokenToOtherUser(token);
    }
});

PermissionChangeオブジェクトと同じように、アクセス権はreadwritemanageの3種類をrealmURLに対して指定できます。 expiresAtはトークンの有効期限を示します。expiresAtを指定しない、もしくはnilを渡した場合は、有効期限は無期限になります。有効期限とはトークンの使用期限であり、すでにトークンを使用したユーザーのアクセス権が無くなるわけではありません。

あるユーザーがトークンを使用した後も、別のユーザーも同じトークンを使ってPermissionOfferResponseを取得できます。

final SyncUser user2 = getUser("user2");
Realm managementRealm = user.getManagementRealm();

// Create response with received token
String offerToken = getToken();
final PermissionOfferResponse offerResponse = new PermissionOfferResponse(offerToken);

// Add to management Realm to sync with ROS
managementRealm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        realm.insert(offerResponse);
    }
});

// After processing, offerResponse's realmUrl property will contain the URL of the shared Realm

SyncPermissionOfferによるパーミッションの変更は既存の状態に追加する形で行われます。つまりすでにwrite権限のあるユーザーがread権限を取得したとしてもwrite権限が失われることはありません。

共有はPermissionOfferオブジェクトをマネジメントRealmから削除することで中止できます。またはexpiresAtプロパティを過去に設定します。しかし新しくそのファイルを共有しようとするユーザーは受け付けなくなりますが、すでにアクセス権を獲得したユーザーのアクセス権が無くなるわけでは ありません

ログ出力

同期されたRealmのデバッグは難しい問題です。そのためログを観察することは問題の調査のために重要です。下記のように、詳細なログレベルを設定すると、Logcatに多くの情報が出力されます。

RealmLog.setLevel(Log.VERBOSE);

エラー処理

同期の際に発生したエラーを捕捉するにはエラーハンドラを登録します。

SyncConfiguration configuration = new SyncConfigurtion.Builder(user, serverURL)
  .errorHandler(new Session.ErrorHandler() {
    void onError(Session session, ObjectServerError error) {
        // do some error handling
    }
  })
  .build();

下記のように、すべてのSyncConfigurationに適用されるデフォルトのグローバルエラーハンドラを登録することもできます。

SyncManager.setDefaultSessionErrorHandler(myErrorHandler);

同期されたRealmのマイグレーション

同期されたRealmについてはモデルクラスのマイグレーションは自動的に適用されます。ただし、いくつかの制限と注意事項があります。

  • 変更は追加によってのみ行われます。新しくクラスを追加する、新しくプロパティを追加する、という場合のみ自動的に変更が適用されます。
  • クラスからフィールドを削除した場合は、自動的にデータベースからその値が無くなるわけではありません。しかしRealmは削除されたフィールドを無視するので問題になりません。新しく作られたオブジェクトでもそのプロパティは存在しますが、Realmによってnilがセットされます。Null不可のプロパティの場合は数値型にはゼロが、文字列には空文字がセットされます。
  • カスタムのマイグレーションブロックは呼び出されません。セットすることもできません。
  • 破壊的な変更、例えばその変更に既存のRealmの情報に影響を与える場合など、は直接的にはサポートしていません。これはプロパティの型を名前はそのままで変更したり(またはNull不可の文字列型のプロパティをNull可に変更するなど)、プライマリーキーを変更したり、Null可をNull不可に変更したりなどを含みます。
  • SyncConfiguration.schemaVersion()を設定するかどうかは完全にオプショナルです。

同期されたRealmに対してカスタムのマイグレーション処理が必要な場合は、DynamicRealmを用いてクライアントサイドで変更を適用します。サーバーサイドの場合はNode.js SDKを利用します(サーバーサイドのコードをサポートしているエディションを使用する必要があります)。しかし、その変更が破壊的なものを含む場合は、Realm Object Serverとの同期はBad changeset receivedエラーによって中止されます。

破壊的な変更を行う場合は既存のファイルをマイグレーションするのではなく、 新しく 同期されたRealmを新しいデータモデルを使って作成し、既存のRealmファイルの変更を監視して新しいRealmにコピーするような処理を書きます。破壊的な変更を伴わない場合でもこの方法は利用することができます。

コンフリクトの解決

コンフリクトの解決については、Realm Object Serverのドキュメントをご覧ください。

FAQ

どのようにすれば、Realmファイルを閲覧することができますか?

このStackOverflowの質問で、Realmファイルの保存場所について説明しています。

Realmファイルの内容については、Realm Browserを使うことで閲覧できます。

Realmのライブラリは、どのくらいの容量がありますか?

アプリケーションをリリースビルドすると、たいていの場合は、APKが800KBほど増加します。 現在、配布されているRealmのライブラリは複数のアーキテクチャ(ARM7、ARMv7、ARM64、x86、MIPS)が含まれてるため、著しく大きくなっています。

Androidインストーラは、アプリケーションのインストール時に、そのデバイスのアーキテクチャ用のネイティブコードのみをインストールします。 最終的にインストールされるアプリケーションの容量は、APKファイルよりも小さくなります。

アーキテクチャごとにAPKファイルを分割することで、それぞれのAPKファイルのサイズを小さくすることができます。build.gradleに以下の記述を行うことで、Androidビルドツールが持っているABI Splitの機能を利用できます。

android {
    splits {
        abi {
            enable true
            reset()
            include 'armeabi-v7a', 'arm64-v8a', 'mips', 'x86', 'x86_64'
        }
    }
}

どのアーキテクチャ用のAPKファイルを作成するか指定します。ABI Split機能の詳細については、Android Toolsドキュメントを参照してください。

サンプルプロジェクトがGitHubにあります。

もし複数のAPKを扱いたくなければ、abiFiltersbuild.gradleに追加して、単一のAPKでサポートするアーキテクチャを制限することができます。

android {
    defaultConfig {
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'mips', 'x86', 'x86_64'
        }
    }
}

ABIの分割とフィルタリングに関してさらに詳しくはこちらのBrijesh Masraniによる解説をご覧ください。

Realmはオープンソースソフトウェアですか?

もちろんです!C++で書かれたRealmの内部データベースエンジンと各言語のSDKは完全にオープンソースとしてApache 2.0ライセンスのもとにソースコードが公開されています。 RealmにはクローズドソースのコンポーネントとしてRealm Platform拡張がありますが、Realmを組み込みデータベースとして使うだけならそれは必要ありません。

通常のJavaオブジェクトとRealmオブジェクトの違いは何ですか?

主な違いは、Javaオブジェクトはインスタンス変数により値を直接保持していますが、Realmオブジェクトは、データそのものは保持しておらず、必要になったときに直接データベースからから値を取得するという仕組みになっています。

RealmオブジェクトのインスタンスはRealmの管理下にある(managed)か、Realmの管理下にない(unmanaged)かのいずれかです。

  • Realmの管理下にある(managed)オブジェクトはRealm内に永続化されています。別のスレッドで値が更新された場合でも常に最新の値が自動的に反映されますが、取得したスレッド上でしか利用することができません。後述するRealm管理下にない(unmanaged)オブジェクトと比較すると一般的に軽量です。というのも、永続化された値をJavaのヒープ上に持たないからです。

  • Realm管理下にない(unmanaged)オブジェクトは、通常のJavaのオブジェクトと同じです。そのため、データは永続化されず自動的な更新も行われませんが、自由にスレッドをまたいで受け渡すことができます。

これらの2つの種類のオブジェクトは、Realm.copyToRealm()Realm.copyFromRealm()を使用することで相互に変換することができます。

なぜモデルクラスの定義で、RealmObject を継承する必要があるのですか?

主な理由は、モデルクラスに特定の機能を追加するためです。また、APIでジェネリクスを使用する際にも利用され、読みやすく、使いやすいAPIを提供することを可能にしています。

RealmObjectを継承したくない場合は、代わりにRealmModelインタフェースを実装することもできます。

RealmProxy クラスとは何ですか?

RealmProxyクラスは、オブジェクトがデータを持たないようにし、必要に応じてデータベースのデータを(遅延して)直接読み書きすることを実現するために必要です。

Realmのアノテーションプロセッサが、プロジェクト内の各モデルクラスのRealmProxyクラスを生成します。 生成されたクラスは、モデルクラスを拡張し、Realm.createObject()が呼ばれたときに、返されます。 しかし、コーディング中にProxyクラスを意識することはほとんどありません。

なぜ書き込み処理にはトランザクションを使う必要があるのですか?

トランザクションは、複数フィールドに対する更新について、それがアトミックに行われることを保証するためのものです。

トランザクションの範囲をはっきりさせることによって、変更を確定するかロールバックするかのどちらかになります(エラーが起こった場合は、ロールバックします)。

また、トランザクションの範囲を明確にしておくことで、変更を確定するタイミングをどのくらい頻繁に行うのかコントロールできます(たとえば、複数の更新を一つの操作として扱えます)。

SQLiteのようなSQLベースのデータベースで、複数のフィールドを一度に更新するような操作をしたとします。 この時、自動的にトランザクションが発生しますが、普通はユーザーは意識しません。

しかし、Realmでは、トランザクションはいつも明示的に書きます。

メモリ不足の例外が出た時はどうすべきですか?

Realmは組み込みストレージエンジンです。

ストレージエンジンは、JVMのヒープ上にメモリを確保するかわりにネイティブ領域に確保します。ストレージエンジンがネイティブ領域にメモリを確保できない場合や、ストレージに空きがない場合にjava.lang.OutOfMemoryErrorをスローします。

このエラーは空のcatch節等で無視するべきではありません。もし無視してアプリを動かし続けた場合は、Realmファイルが破損する可能性があります。この例外が発生したときは、速やかにアプリを終了させることが最も安全な対処方法です。

ファイルサイズと中間バージョンについて

SQLiteなどにデータを保存した時より、ディスクの使用容量が少なくなることを期待されることかと思います。

データの一貫性を保つために、Realmは最新のデータにアクセスしたときのみ履歴をアップデートします。 このことは、別のスレッドが多くのデータを長い時間をかけて書き込んでいる最中にデータを読み出そうとした場合、履歴はアップデートされずに古いデータを読み出すことになります。結果として、履歴の中間データが増加していくことになります。

この余分な領域は、最終的には再利用されるか消去されます。(強制的に空き領域を消去するには、compactRealmを用いて空き領域を最適化します。)

Mixpanelへの通信が行われているようですがこれは何ですか?

Realmを使用したアプリケーションをビルドする際、Realmは匿名の統計情報を収集しています。収集される情報は完全に匿名化されたもので、プロダクトの改善やサポートの廃止を検討する際にどのバージョンのRealmが使用されているか、どのOS上で使われているかなどの情報を利用しています。統計情報の収集はアプリを実行しているユーザーのデバイス上で行われることはありません。 — ビルド時にソースコードのアノテーションを処理する時のみに行われます。どのような情報がどのように収集されるかについて正確に確認したい場合は、ソースコードを参照してください。

“librealm-jni.so”がロードできないというエラーが発生します。

アプリケーションが64bitアーキテクチャをサポートしていないネイティブライブラリを使用していた場合、ARM64デバイスのAndroidはRealmのlibrealm-jni.soのロードを行うことができません。これは、Androidが32bitと64bitのネイティブライブラリを同時にはロードできないためです。 理想的な解決策はサポートする全てのABIのライブラリを揃えることですが、3rdパーティライブラリを使用している場合不可能なことがあります。詳細はVLC and realm Library conflictsを参照してください。

この場合のワークアラウンドは、RealmのARM64版ライブラリをAPKから除外することです。そのためには、build.gradleに以下の設定を追加します。詳しくはMixing 32- and 64-bit Dependencies in Androidを参照してください。

android {
    //...
    packagingOptions {
        exclude "lib/arm64-v8a/librealm-jni.so"
    }
    //...
}

Android Gradle プラグイン 1.4.0betaには、Jarファイル中の.soファイルを正しくパッケージングできないというバグがあります。詳細はRealm Java issue 1421を参照してください。Android Gradleプラグイン1.3.0に戻すが、1.5.0以降を使用することでこの問題を回避することができます。

サードパーティ製のライブラリのいくつかは64 bit環境をサポートしていません。

Realmファイルのバックアップとリストアはどのように行えばよいでしょうか

Realmはファイルシステム上にファイルとして保存されます。getPath()を用いることでこのファイルの絶対パスを取得することができます。バックアップ/リストアを行う場合は、すべてのRealmインスタンスを閉じた状態で行ってください。

あるいは、realm.writeCopyTo(File)メソッドを用いて開いているRealmをバックアップすることもできます。

RealmファイルをGoogleDrive上にバックアップする方法について、以下のページに詳細な説明がなされています(英語)。

Blackberryデバイスににおける動作について

いくつかのBlackberryデバイスではAndroidアプリが動作します。しかし、残念ながら完全に互換性が保証されいてるAndroidの実行環境が提供されているわけではありません。既知の問題として下記に示すエラーメッセージが表示されることがあります。

io.realm.exceptions.RealmFileException: Function not implemented in io_realm_internal_SharedRealm.cpp line 81 Kind: ACCESS_ERROR.

もしこのようなエラーメッセージをBlackberryデバイスで見た場合は、Realm CoreとRealm Javaに修正をコントリビュートすることを検討してください。

暗号化キーをどのように保存、取得すればいいですか?

Android KeyStoreを使うことがもっとも安全にRealmの暗号化キーを管理できる方法です。 下記に推奨する方法を示します。

  1. AndroidのKeyStoreを使ってRSAキーを生成します。生成された鍵はAndroidのフレームワークが安全に保存してくれます。M以降のバージョンでは、KeyStoreをアンロックするときにPIN(もしくは指紋認証)を必要とします。キーストアがハードウエアで実装された端末であれば、Root化されたデバイスであっても鍵そのものを取り出すことはできません。

  2. Realmのデータベースを暗号化するための鍵として使用するランダムなバイト列を生成します。

  3. 生成したバイト列を初めに作成したRSAキーで暗号化します。

  4. 暗号化されたRealm用の鍵は 安全に ファイルシステムに保存できます(例えばSharedPreferencesなどが使用できます)。

  5. 暗号化されたRealmを使用する際には、まず暗号化されたRealm用の鍵を取得し、RSAを用いて復号します。そして、RealmConfigurationにキーを渡してRealmを開きます。

次に示すライブラリは上記のステップを実装したもです。実装の参考にしてください。 https://github.com/realm/realm-android-user-store

こちらのサンプルコードでは、指紋認証APIを用いて暗号化キーを保存する機能を実装しています。