You are reading the documentation for an older version of Realm. You can view the latest documentation instead.

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

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

    // getterとsetterを生成
}
public class Person extends RealmObject {
    @Required // nameはnull不可
    private String name;
    private String imageUrl; // imageUrlはnull可
    private RealmList<Dog> dogs;

    // getterとsetterを生成
}

// 通常のJavaオブジェクトのようにモデルクラスを使用
Dog dog = new Dog();
dog.setName("Rex");
Log.v(TAG, "Name of the dog: " + dog.getName());

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

// 簡単にデータが永続化できます
realm.beginTransaction();
realm.copyToRealm(dog);
realm.commitTransaction();

// 別のスレッドからクエリを発行し、その結果を利用可能
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        // このスレッドのためのRealmインスタンスを取得
        Realm realm = Realm.getInstance(context);
        Dog dog = realm.where(Dog.class).equalTo("name", "Rex").findFirst();
        Log.v(TAG, "Age of the dog: " + dog.getAge());
        realm.close();
    }
});
thread.start();

はじめに

Download Realm for Android ソースコードはGitHubにあります

必要条件

  • 現在のバージョンではAndroind以外のJavaはサポートされていません。
  • バージョン0.8.6以上のAndroid Studio(Eclipseをお使いの場合は、下記をご覧ください)
  • 最新のAndroid SDK
  • JDK7以降
  • API Level 9(Android 2.3 Gingerbread)以降のAndroidはすべてサポートしています。

インストール

Mavenを使用してインストールする方法と、Jarファイルをプロジェクトに追加する方法があります。

Mavenを使用する

  1. 依存レポジトリとして jcenter を使ってください。(Android Gradle プラグインではデフォルトでそのように設定されています。)
  2. build.gradleに依存ライブラリとしてcompile 'io.realm:realm-android:0.86.1'を追加します。
  3. Android StudioのメニューからTools->Android->Sync Project with Gradle Filesを選択します。

Jarファイルを追加する

  1. こちらから最新版のRealmをダウンロードしてzipファイルを展開してください。
  2. Android Studioで新しいプロジェクトを作成します。
  3. realm-VERSION.jarapp/libsフォルダにコピーします。
  4. Android StudioのメニューからTools->Android->Sync Project with Gradle Filesを選択します。
  1. こちらから最新版のRealmをダウンロードしてzipファイルを展開してください。
  2. eclipse/フォルダにあるJarファイルとlibrealm-jni.soが含まれている数個のフォルダを、プロジェクトのlibsフォルダにコピーします。
  3. libsフォルダにコピーしたRealmのJarファイルを右クリックし、"Build Path" -> "Add to Build path"を選択します。
  4. プロジェクトを右クリックし、“Properties”を選択します。"Java Compiler" -> "Annotation Processing"を選択し、“Enable project specific settings”をチェックして“Apply”をクリックします。
  5. 次に"Annotation Processing" -> "Factory Path"を選択し、“Enable project specific settings”をチェックします。“Add JARs”をクリックし、“libs”にあるRealmのJarファイルを選択します。そしてビルドを実行します。
  6. アノテーションプロセッサを実行するために、RealmObject のサブクラスを定義するときには必ず@RealmClassアノテーションを追加しなければなりません。

ProGuard

Realmは、各RealmObjectのProxyクラスをコンパイル時に生成しています。 ProGuardのような難読化ツールが動作した後にもProxyクラスを見つけることができるように、下記の設定をProGuardの設定ファイルに追加してください。この設定にはRealmのバージョン0.84.1以降が必要です。

-keep class io.realm.annotations.RealmModule
-keep @io.realm.annotations.RealmModule class *
-keep class io.realm.internal.Keep
-keep @io.realm.internal.Keep class * { *; }
-dontwarn javax.**
-dontwarn io.realm.**

Realm Browser

Realm Browserは、現在Mac OS Xにのみ対応しております。Windows、Linux版は現在、準備中ですので、もうしばらくお待ちください。

RealmBrowserは、.realmデータベースを閲覧、編集するMac アプリケーションです。

Realm Browser

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

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

Realm BrowserMac App Store からダウンロードできます。Realm Java 0.83以降を使用している場合は、現時点のMac App Store版ではRealmファイルを開くことができません。GitHubから最新版のRealm Browserをダウンロードしてご利用ください。

APIリファレンス

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

サンプルコード

Realmには、いくつかのサンプルプロジェクトが含まれています。Android Studioで、Import Projectし、runで実行してみてください。

RealmIntroExampleは、Realm APIの簡単な使用例について学べます。ソースコードと、実行ログをよく見てみてください。

RealmGridViewExampleは、GridViewのデータソースとしてRealmを利用する方法について学べます。また、JSONデータをRealm保存する方法についても示します。

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

RealmAdapterExample は、RealmBaseAdapterを使ってRealmResultsをリストビューにバインドする便利な方法について学べます。

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

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

ヘルプ

  • 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
  • さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。Slack上で毎月のオフィスアワーも開催しています。(質問は日本語で構いません)
  • バグ報告や機能リクエストについては GitHubのIssuesにご報告ください。
  • Community Newsletterに参加することで定期的にRealmに関するTipsやユースケース、ブログの更新やチュートリアルなど、Realm の最新情報が届きます。

モデル

Realmで使うモデルクラスは、伝統的なJava Beanと非常によく似た形として定義できます。下記のようにRealmObjectのサブクラスを作り、RealmアノテーションプロセッサにProxyクラスを生成させます。

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; }
}

getterおよびsetterメソッドは生成されるProxyクラスによって上書きされることに注意してください。

そのため、getter/setterメソッドに書かれた処理は実行されません。

対応しているデータ型

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

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

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

必須フィールドとnull値

@Requiredアノテーションを用いることで、値としてnullを許さないフィールドを定義することができます。プリミティブ型のフィールドは言語仕様上nullが許されていないので、明示的に@Requiredアノテーションを付与する必要はありません。

保存しないプロパティ

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

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

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

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

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

プライマリキー

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

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

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

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

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

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

その後でcopyToRealm()を使い、Realmにオブジェクトを追加してください。

MyObject obj = new MyObject();
obj.setId(42);
obj.setName("Fish");
realm.beginTransaction();
// This will create a new one in Realm
// realm.copyToRealm(obj);
// This will update a existing one with the same id or create a new one instead
realm.copyToRealmOrUpdate(obj);
realm.commitTransaction();

プライマリキーは値としてnullを持つことはできません。つまり、@PrimaryKeyアノテーションは暗黙的に@Requiredアノテーションの効果を含みます。

制限事項

Proxyクラスによってgetter/setterメソッドがオーバーライドされるために、Realmのモデルクラスには、普通のクラスなら可能なことがいくつか制限されています

書き込み

読み込み処理とは違って、書き込み(追加、変更、削除)処理は、必ずトランザクションの中で行わなければなりません。

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

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

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

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

// Obtain a Realm instance
Realm realm = Realm.getInstance(this);

realm.beginTransaction();

//... add or update objects here ...

realm.commitTransaction();

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

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

//  ... 

realm.cancelTransaction();

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

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

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

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

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

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

オブジェクトの作成

Realmオブジェクトのメソッド(createObject())を使って生成したオブジェクトは、始めからRealmと結びついています。

realm.beginTransaction();
User user = realm.createObject(User.class); // Create a new object
user.setName("John");
user.setEmail("[email protected]");
realm.commitTransaction();

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

Realmは、引数を取らないコンストラクタを含む、数多くのコンストラクタをサポートしています。

User user = new User("John");
user.setEmail("[email protected]");

// Copy the object to Realm. Any further changes must happen on realmUser
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("[email protected]");
	}
});

非同期トランザクション

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

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

通常の同期トランザクションから非同期トランザクションへ切り替えるには、Realm.Transaction.Callback引数付きのメソッドを使用します。

realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm bgRealm) {
                User user = bgRealm.createObject(User.class);
                user.setName("John");
                user.setEmail("[email protected]");
            }
        }, new Realm.Transaction.Callback() {
            @Override
            public void onSuccess() {
            }

            @Override
            public void onError(Exception e) {
                // トランザクションは自動的にロールバックされます。その他に必要な後処理を記述してください。
            }
        });

コールバックオブジェクトは必須ではないので不要な場合はnullを渡すことができます。 コールバックオブジェクトが提供されている場合はトランザクションの完了時に成功または失敗のいずれかが通知されます。

ただし、コールバックの仕組みはLooperを使用しているため、Looperを持たないスレッドでは利用することができません。その場合は引数にnullを渡してください。

RealmAsyncTask transaction = realm.executeTransaction(new Realm.Transaction() {
            @Override
            public void execute(Realm bgRealm) {
                User user = bgRealm.createObject(User.class);
                user.setName("John");
                user.setEmail("[email protected]");
            }
        }, null);

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

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

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

クエリ

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として返ってきます。この時、データは一切コピーされません。

マッチしたUserオブジェクトを取り出したり、取得したオブジェクトを直接操作することができます。

RealmResultは、AbstractListクラスを継承しており、使い方はよく似ています。 例えば、RealmResultは、順序を持ち、添え字を指定してアクセスすることができます。

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

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

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

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

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

検索条件を指定する

以下の検索条件がサポートされています。

  • betweengreaterThan()lessThan()greaterThanOrEqualTo()lessThanOrEqualTo()
  • equalTo()notEqualTo()
  • contains()beginsWith()endsWith()

ただし、すべてのデータ型が対応しているわけではありません。詳しくは、RealmQueryをご覧ください。

修飾子

文字列の検索条件に対して、CASE_INSENSITIVEを指定することで、アルファベットの大文字と小文字の区別を無視することができます。

論理演算子

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

以下のような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; }
}

それぞれの条件の評価順を指定するために「かっこ」を利用してグループ化するのは、次のように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()に対してのみ使用することができます。

並べ替え

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

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

クエリの連鎖

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

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

集計

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();

繰り返し処理

RealmResultsは繰り返し処理のためにIterableインターフェースを実装しています。

RealmResults<User> results = realm.where(User.class).findAll();
for (User u : results) {
    // ... do something with the object ...
}

もちろん以下のようなforループも使用できます。

RealmResults<User> results = realm.where(User.class).findAll();
for (int i = 0; i < results.size(); i++) {
    User u = results.get(i);
    // ... do something with the object ...
}

削除

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

// obtain the results of a query
RealmResults<Dog> results = realm.where(Dog.class).findAll();

// All changes to data must happen in a transaction
realm.beginTransaction();

// remove single match
results.remove(0);
results.removeLast();

// remove a single object
Dog dog = results.get(5);
dog.removeFromRealm();

// Delete all matches
results.clear();

realm.commitTransaction()

非同期クエリ

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

ほとんどのクエリはたとえ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 RealmChangeListener callback = new RealmChangeListener() {
    @Override
    public void onChange() { // クエリ完了後、更新があるたびに呼び出されます。
        // resultを使用
    }
};

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

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

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

クエリの完了チェック

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

Realm.getInstance(Context context)メソッドを呼び出すことで、realm変数を初期化してることは、すでにお気づきのことでしょう。 このコンストラクタは、今いるスレッドで使えるRealmインスタンスを返します。default.realmで呼ばれるファイルは、Context.getFilesDir()が指すディレクトリにあります。 これは、プロジェクトフォルダのルートにある、Fileフォルダの中にある default.realm のものです。

ファイルは、書き込み可能なディレクトリのルートに作成されます。 ほとんどの場合、/data/data/<packagename>files/にファイルがあることでしょう。

realm.getPath()を使えば、Realmファイルへのパスを確認できます。 注意することは、Realmインスタンスは、スレッドシングルトンであり、このことは、RealmファイルへのRealmインスタンスを取得したとき、それぞれのスレッドで同じインスタンスが返されることを意味します。

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

Realm.getInstance(context)メソッドでRealmインスタンスを取得することは、Realmをもっとも簡単に使える方法です。

さらに細かい設定のカスタマイズが必要なときは、RealmConfigurationオブジェクトを使うと、さまざまに設定をカスタマイズしたRealmを作成することができます。

// RealmConfigurationオブジェクトの生成はBuilderパターンを使います
RealmConfiguration config = new RealmConfiguration.Builder(context)
  .name("myrealm.realm")
  .encryptionKey(getKey())
  .schemaVersion(42)
  .setModules(new MySchemaModule())
  .migration(new MyMigration())
  .build();

// 下記の2行は同じ意味になります
Realm realm = Realm.getInstance(context);
Realm realm = Realm.getInstance(new RealmConfiguration.Builder(context).build());

RealmConfigurationオブジェクトをデフォルト設定として保持しておくことができます。 下記のように、アプリケーション起動時(Applicationオブジェクトが作成されたとき)にデフォルトとして設定することで、アプリケーション全体で同じ設定のRealmを使用することができます。

public class MyApplication extends Application {
  @Override
  public void onCreate() {
    super.onCreate();
    RealmConfiguration config = new RealmConfiguration.Builder(context).build();
    Realm.setDefaultConfiguration(config);
  }
}

public class MyActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    Realm realm = Realm.getDefaultInstance();
  }
}

RealmConfigurationを複数作成して、複数の設定を使い分けることもできます。 複数のRealmファイルでそれぞれ異なるスキーマ、またはスキーマバージョンを使用することなどが可能になります。

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

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

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

In-Memory Realm

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

RealmConfiguration myConfig = new RealmConfiguration.Builder(context)
    .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(context).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を使う

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

複数のスレッドで同じデータにアクセスしたい場合は、それぞれのスレッドで別々のRealmインスタンスを(Realm.getInstance(RealmConfiguration)や似たようなメソッドを使って)取得する必要があります。取得したRealmインスタンスに対してクエリを投げることでオブジェクトを取得することができます。 それぞれのスレッドで取得したオブジェクトから、ディスク上の同じデータを読み書きすることが可能です。

Realmインスタンスを閉じる

ネイティブメモリとファイルディスクリプタを解放できるように、RealmCloseableインターフェースを実装しています。

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

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

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

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

また、下記のようにAsyncTask(とCloseable)を使うのは良い書き方です。

protected Long doInBackground(Context... contexts) {
    Realm realm = null;
    try {
        realm = Realm.getInstance(contexts[0]);

        // ... Use the Realm instance
    } finally {
        if (realm != null) {
            realm.close();
        }
    }
}

UIスレッドとは別のLooperスレッドを使う場合は、下記のように書きます。

public class MyThread extends Thread {
    private final Context;

    public MyThread(Context context) {
        this.context = context;
    }

    public void run() {
        Looper.prepare();
        Realm realm = null;
        try {
            realm = Realm.getInstance(context);

            //... Setup the handlers using the Realm instance
            Lopper.loop();
        } finally {
            if (realm != null) {
                realm.close();
            }
        }
    }
}

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

try (Realm realm = Realm.getInstance(context)) {
	// No need to close the Realm instance manually
}

オートリフレッシュ

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

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

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

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

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

Realmファイルの探し方

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

スキーマ

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

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

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

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

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

// 一つのスキーマで複数のモジュールを使用することができます
RealmConfiguration config = new RealmConfiguration.Builder(context)
  .setModules(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(context)
  .name("library.realm")
  .setModules(new MyLibraryModule())
  .build();

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

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

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

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

public class Email extends RealmObject {
    private String address;
    private boolean active;
    // ... setters and getters left out
}

public class Contact extends RealmObject {
    private String name;
    private Email email;
    // ... setters and getters left out
}

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

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

モデル間で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 {
    private RealmList<Email> emails;
    // Other fields…
}

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

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

public class Email extends RealmObject {
    private String address;
    private boolean active;
    // ... setters and getters left out
}

public class Contact extends RealmObject {
    private String name;
    private Email email;
    // ... setters and getters left out
}

また、関連のフィールドにアクセスするためのgetter/setterメソッドを追加することができます。

realm.beginTransaction();
Contact contact = realm.createObject(Contact.class);
contact.setName("John Doe");

Email email1 = realm.createObject(Email.class);
email1.setAddress("[email protected]");
email1.setActive(true);
contact.getEmails().add(email1);

Email email2 = realm.createObject(Email.class);
email2.setNumber("[email protected]");
email2.setActive(false);
contact.getEmails().add(email2);

realm.commitTransaction();

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

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

関連をたどると自身と同じクラスが出現するようなモデル定義をすると、オブジェクトが循環構造(関連をたどった先にそのオブジェクト自身が出現する構造)を作ることができます。 現状のRealmはこのような循環構造を検出する仕組みを提供していないので、リンクをたどる際に無限ループにならないよう注意してください。

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

関連に対するクエリ

関連のオブジェクトに対してクエリを実行する方法について説明します。 上記のモデルについて、emailactive=trueContactオブジェクトを取得する場合は、次のように書きます。

RealmResults<Contact> contacts = realm.where(Contact.class).equalTo("emails.active", true).findAll();

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

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

“少なくとも1つのemailactive=trueであるContactオブジェクトをすべて取得します”

このことは、取得されたContactオブジェクトに含まれる各Emailオブジェクトが、すべてactive=trueではない、ということを理解するために重要になります。

さらに言うと、それぞれの関連に対する検索条件は別々に評価されます。 最終的な検索結果は、各クエリの共通部分(論理積)になります。 このことは、下記のクエリで少なくとも1つのactive=trueemailを持ち、かつ少なくとも1つのactive=falseemailを持つすべてのContactオブジェクトが取得されることを示します。

RealmResults<Contact> contacts = realm.where(Contact.class).equalTo("emails.active", true).equalTo("emails.active", false).findAll();

JSON

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

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

モデルクラスに合う定義がないJSONプロパティは無視されます。

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

// A RealmObject that represents a city
public class City extends RealmObject {
    private String city;
    private int id;
    // getters and setters left out ...
}

// Insert from a string
realm.beginTransaction();
realm.createObjectFromJson(City.class, "{ city: \"Copenhagen\", id: 1 }");
realm.commitTransaction();

// Insert multiple items using a InputStream
InputStream is = new FileInputStream(new File("path_to_file"));
realm.beginTransaction();
try {
    realm.createAllFromJson(City.class, is);
    realm.commitTransaction();
} catch (IOException e) {
    realm.cancelTransaction();
}

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

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

通知

バージョン0.80.3以降、RealmChangeListenerはメモリリークの可能性をなくすために、内部的に弱参照によって管理されています。 そのためaddListenerの引数に匿名クラスとしてRealmChangeListenerを使うことはしてはいけません。 その代わりに、常にリスナーへの参照を必要がなくなるまで保持する必要があります。

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

public class MyActivity extends Activity {
    private Realm realm;
    // ガベージコレクションによって解放されないように、
    // RealmChangeListenerへの参照を保持しておく必要があります
    private RealmChangeListener realmListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      realm = Realm.getDefaultInstance();
      reamlListener = new RealmChangeListener() {
        @Override
        public void onChange() {
            // ... (UIの更新など)通知に対する処理をします ...
        %>;
      realm.addChangeListener(realmListener);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // リスナーを削除します
        realm.removeChangeListener(realmListener);
        // Realmインスタンスを閉じます
        realm.close();
    }
}

次のようにすると、簡単にリスナーをすべて削除することができます。

realm.removeAllChangeListeners();

マイグレーション

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

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

RealmConfiguration config = new RealmConfiguration.Builder(context)
    .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(context)
    .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.

Realm.getInstance()の引数として、512ビットの暗号化キーを用いて、Realmファイルを暗号化してディスクに保存することができます。

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

Realm realm = Realm.getInstance(config);

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

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

デバッグ

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

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

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

Android Studio, IntelliJ Debug RealmObject

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

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

アダプタ

RealmResultsのデータをUIコンポーネントとバインディングするための便利なクラスがあります。 RealmBaseAdapterは、getView()メソッドを実装することで、UIとデータのバインディングを行ってくれます。

public class Person extends RealmObject {
    private String name;
    public String getName() {
        return this.name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

public class MyAdapter extends RealmBaseAdapter<Person> implements ListAdapter {

    private static class MyViewHolder {
        TextView name;
    }

    public MyAdapter(Context context, int resId,
                     RealmResults<Person> realmResults,
                     boolean automaticUpdate) {
        super(context, realmResults, automaticUpdate);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        if (convertView == null) {
            convertView = inflater.inflate(android.R.layout.simple_list_item_1, 
                                           parent, false);
            viewHolder = new ViewHolder();
            viewHolder.name = (TextView) convertView.findViewById(android.R.id.text1);
            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        Person item = realmResults.get(position);
        viewHolder.name.setText(item.getName());
        return convertView;
    }

    public RealmResults<Person> getRealmResults() {
        return realmResults;
    }    
}

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

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

GSON

GSONは、JSONをシリアライズ/デシリアライズするGoogle製のライブラリです。RealmとGSONの最新バージョンである2.3.1を利用するときは、ExclusionStrategyを指定しなければなりません。

// Using the User class
public class User extends RealmObject {
    private String name;
    private String email;
    // getters and setters left out ...
}

Gson gson = new GsonBuilder()
        .setExclusionStrategies(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return f.getDeclaringClass().equals(RealmObject.class);
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return false;
            }
        })
        .create();

String json = "{ name : 'John', email : [email protected]' }";
User user = gson.fromJson(json, User.class);

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

シリアライズ

Retrofitなどのライブラリとの完全な互換性を保つために、シリアライズとデシリアライズの両方をしたいことでしょう。 RealmオブジェクトをJSONにシリアライズすることは、GSONのデフォルトの挙動では正しく動きません。 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型のフィールドはスキップされます。

Otto

Otto は、Androidでイベントバスを扱うための、Square製のライブラリです。RealmとOttoの組み合わせは動作しますが、タイミングとマルチスレッドに起因する問題がいくつか存在します。

Ottoは、通常イベントが送られたスレッドと同じスレッドでイベントを受け取ります。

そのため、RealmObjectをイベントに追加し、レシーバのメソッドでデータを読むことは問題ありません。

しかし、常にメインスレッドでイベントを送信するために、ここに紹介されているようなハックをした場合、常にUIスレッドでイベントを送るようにしない限りは、RealmObjectsを持たないイベントデータを受け取ることになります。

これはUIをイベントのレスポンスで更新するために通常とられる手段です。

あるスレッドでRealmObjectに変更があったとき、Realmはその他のスレッドへの更新の通知をHandlerを使ってスケジューリングします。

いっぽうOtto.post(event)はイベントを即座にディスパッチします。

ですので、Realmの更新を通知するために他のスレッドにイベントを送信する場合は、受け取る側では自分でrealm.refresh()を呼び出す必要があります。

@Subscribe
public void handleEvent(OttoEvent event) {
    realm.refresh();
    // Continue working with Realm data loaded in this thread
}

Parceler

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

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

// All classes that extend RealmObject will have a matching RealmProxy class created
// by the annotation processor. Parceler must be made aware of this class. Note that
// the class is not available until the project has been compiled at least once.
@Parcel(implementations = { PersonRealmProxy.class },
        value = Parcel.Serialization.BEAN,
        analyze = { Person.class })
public class Person extends RealmObject {
    // ...
}

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

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

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

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

2) Realmオブジェクトが一度Percel化されると、Realmの管理から外れた状態になります。 そして、Realmオブジェクトはその時点のデータをスナップショットとして持つ、スタンドアロンのオブジェクトとして振る舞います。

その後に、どのような変更をしてもRealmには保存されません。

Retrofit

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

Retrofitは、内部でGSONを使っているので、JSONからRealmObjectに変換する場合は、GsonConverterを適切に設定しなければなりません。

Gson gson = new GsonBuilder()
        .setExclusionStrategies(new ExclusionStrategy() {
            @Override
            public boolean shouldSkipField(FieldAttributes f) {
                return f.getDeclaringClass().equals(RealmObject.class);
            }

            @Override
            public boolean shouldSkipClass(Class<?> clazz) {
                return false;
            }
        })
        .create();

// Configure Retrofit to use the proper GSON converter
RestAdapter restAdapter = new RestAdapter.Builder()
    .setEndpoint("https://api.github.com")
    .setConverter(new GsonConverter(gson))
    .build();

GitHubService service = restAdapter.create(GitHubService.class);

Retrofitは、自動的にオブジェクトをRealmに追加しません。以下のようにrealm.copyToRealm()メソッド、またはrealm.copyToRealmOrUpdate()メソッドを使って追加する必要があります。

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

// Copy elements from Retrofit to Realm to persist them.
realm.beginTransaction();
List<Repo> realmRepos = realm.copyToRealm(repos);
realm.commitTransaction();

Robolectric

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

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

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

次のステップ

Realmをより深く理解する、次のステップとして、サンプルコードを用意しています。

Happy hacking! realm-javaのGoogle グループでは、いつでもRealmの開発者に相談できます。

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

Realmは、現在β版としてリリースされています。バージョン1.0に向けて、機能追加、バグの修正などを行っています。私たちは、次に導入予定のリストを作成しました。 詳しくは、GitHub issues をご覧ください。

一般的な制限事項

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

  1. クラス名は、57文字が上限です。Realmの内部では、class_という文字をクラス名の前に付けて扱われます。Realm Browserはその名前の一部を表示します。
  2. フィールド名は63文字が上限です。
  3. Date型のフィールドは、保存されるとミリ秒以下が切り捨てられます。
  4. ネストしたトランザクションはサポートしていません。検出された場合、例外が投げられます。
  5. 文字列とバイト配列(byte[])は16MB以上は保存できません。
  6. 大文字小文字を区別しない文字列のマッチについては、Latin Basic、Latin Supplement、Latin Extended A、Latin Extended Bの文字セット(UTF-8の範囲は、0-591)のみサポートしています。

オブジェクト

Proxyクラスがgetter/setterメソッドをオーバーライドするため、モデルクラスに実装できることは下記の制限の範囲に限られます。

  • privateなフィールドのみ
  • デフォルトのsetter/getterメソッドのみ
  • publicまたはprivateなStaticフィールド
  • Staticメソッド
  • メソッドを持たないインターフェースの実装

つまり、toString()equals()メソッドなどをオーバーライドしてRealmObjectを拡張することができないということです。

これらの制限をなくすための機能を現在開発中です。

ソート

現在のRealmでは、ソートはLatin Basic、Latin Supplement、Latin Extended A、Latin Extended B の文字セット(UTF-8の範囲は、0-591)でのみサポートしています。それ以外の文字セットでソートが行われた場合、RealmResultsオブジェクトは変化しません。

大文字小文字を区別しない(case sensitive)クエリ

caseSensitiveフラグを設定したとき、equalTocontainendsWithbeginsWithクエリは英語についてのみ動作します。詳しくはこちらをご覧ください。

スレッド

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

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

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

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

Realmファイルを異なるプロセスで同時に使用するときは、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();
        RealmConfiguration realmConfiguration = new RealmConfiguration.Builder(this).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() {
            // ビューの再描画を要求するだけです。`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(); // 現在のデータでビューの描画を要求
    }

    // ...
}

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の機能を利用できます。

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

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

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

Realmをプロダクション環境で使うことはできますか?

Realmは、2012年から商用のプロダクトで利用されています。

現在のRealmを利用する場合は、RealmのJava APIが、コミュニティのフィードバックを受けて変わりうるものだとしてお使いください。 機能追加、不具合の修正も同様に考えてください。

Realmを使うために料金を支払う必要がありますか?

いいえ、Realmは完全に無料です。商用の製品で利用することも可能です。

Realmはどのようなビジネスプランなのですか?

すでにエンタープライズ向けの商品の販売や、周辺サービスによって収益を得ています。もし現在リリースされているrealm-java以上の機能が必要であれば、いつでもメールで、お気軽にご連絡ください。

また私たちのビジネスとは関係なく、realm-javaはオープンソースのまま開発を続け、Apache License 2.0として公開し続けます。

“core”という文字をコードの中で見たのですが、これは何ですか?

“core”または“Realm Core”というのは、C++で実装されたストレージエンジンの名前です。現在、コアのソースコードはオープンソースではありませんが、近い将来Apache License 2.0で公開する予定です。 バイナリについては、Realm Core Binary Licenseで利用可能です。

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

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

このことにより2つの違いがあります。まず、Realmオブジェクトは、一般的なJavaオブジェクトよりも軽量です。

次に、Javaオブジェクトと異なり、Realmオブジェクトは、別のスレッドで値が更新された場合でも常に最新の値が自動的に反映されます。

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

主な理由は、モデルクラスに特定の機能を追加するためです。 現在、removeFromRealm()のみですが、他の機能についても随時追加する予定です。 このような設計はコードをより読みやすく、使いやすくするため有効です。

*RealmProxy クラスとは何ですか?

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

Realmのアノテーションプロセッサが、プロジェクト内の各モデルクラスのRealmProxyクラスを生成します。 生成されたクラスは、モデルクラスを拡張し、Realm.createObject()が呼ばれたときに、返されます。 しかし、実際にIDEを使って開発しているぶんには、それらの違いを感じないと思います。

なぜ、全てのフィールドで、getter/setterメソッドが必要なのですか?

Proxyクラスが動作するために必要です。

インスタンス変数にデータをコピーせずに、データベースから直接値を読み書きするのを保証するために、全てのフィールドのアクセッサは、Proxyクラスによってオーバーライドされます。

これは、プライベートフィールドのgetter/setterに対してのみ可能になります。

これが一番の理想的な解決方法とは思っていません。そのため、この制限は取り除きたいと考えています。AspectJJavassistのような解決策は、そういったことを可能にしてくれますが、私たちは他の可能性を探し続けています。

Proxyクラスがgetter/setterをオーバーライドするので、カスタマイズしたgetter/setterを定義することはできません。

一時的な解決策は、@Ignoreアノテーションを付けて、getter/setterを定義することです。 その時の、モデルクラスは以下のようになります。

package io.realm.entities;

import io.realm.RealmObject;
import io.realm.annotations.Ignore;

public class StringOnly extends RealmObject {

    private String name;

    @Ignore
    private String kingName;

    // custom setter
    public void setKingName(String kingName) { setName("King " + kingName); }

    // custom getter
    public String getKingName() { return getName(); }

    // setter and getter for 'name'
}

上記のコードのように、setName()の代わりに、setKingName()を使うようにします。 カスタムセッタの中で、setName()を使い、nameフィールドに直接、値を割り当てないようにします。

なぜRealmObjectをインスタンス化することはできないのですか?

RealmObjectはデータベースとのプロキシの役割を果たします。 なのでRealmによって適切に管理される必要があります。

しかし、この場合についても、モデルオブジェクトからRealmObjectに変換する機能を実装することを検討しています。

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

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

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

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

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

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

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

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

ストレージエンジンは、メモリをJVMのヒープ上に確保しません。 しかし、アプリケーション自体が、メモリ不足になる可能性があります。

そのような状況では、Realmは、io.realm.internal.OutOfMemoryErrorを投げます。

このエラーは無視するべきではありません。もし無視した場合、壊れた状態のRealmファイルが残ることになります。

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

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

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

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

‘Annotation processor may not have been executed.’という例外は何ですか?

アプリケーションをコンパイルする際に、アノテーションプロセッサにより、モデルクラスを読み込みProxyクラスが生成されます。

アノテーションプロセッサの処理が失敗した場合、ProxyクラスまたはProxyクラスのメソッドを見つけることができません。

その場合、アプリケーションの実行時に、Realmが’Annotation processor may not have been executed.’の例外を投げます。

Java6を使っている場合、Inheritedアノテーションがサポートされてないために、この例外が投げられることがあります。

その場合は@RealmClassをモデルファイルの前に追加する必要があります。または、生成されたファイルをすべて削除してから再ビルドしてください。

Encryption is not supported on this device例外

Realmの現在の暗号化の実装はシグナルハンドラに依存していますが、いくつかの古い端末(HTC One X等)ではシグナルハンドラが正しく動作しません。v0.82.2以降では、暗号化されたRealmを開く際にデバイスに問題がないかをチェックします。もし問題のあるデバイス上であった場合はRealmEncryptionNotSupportedException例外をスローします。

v0.85.0から、Realmの暗号化実装はこれらの端末を含むすべての端末で動作するものに変更されました。このため、v0.85.0以降ではRealmEncryptionNotSupportedExceptionが削除されています。

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以降を使用することでこの問題を回避することができます。