This is not the current version. View the latest documentation

Realm React Nativeはアプリケーションのモデル層を効率的に安全で迅速な方法で記述することができます。下記の例をご覧ください:

// モデルクラスとスキーマを定義します
class Car {}
Car.schema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: 'int',
  }
};
class Person {}
Person.schema = {
  name: 'Person',
  properties: {
    name:    {type: 'string'},
    cars:    {type: 'list', objectType: 'Car'},
    picture: {type: 'data', optional: true}, // オプショナル(NULL可)のプロパティ
  }
};

// モデルクラスを与えてデフォルトRealmを取得します
let realm = new Realm({schema: [Car, Person]});

// Realmオブジェクトを作成してローカルDBに保存します
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Honda',
    model: 'Civic',
    miles: 1000,
  });
  myCar.miles += 20; // 保存済みの値を更新することもできます
});

// 'miles > 1000'に該当するCarオブジェクトを検索します
let cars = realm.objects('Car').filtered('miles > 1000');

// 上記の条件に該当するCarオブジェクトは1件です
cars.size // => 1

// もう一つ別のCarオブジェクトを保存します
realm.write(() => {
  let myCar = realm.create('Car', {
    make: 'Ford',
    model: 'Focus',
    miles: 2000,
  });

// 検索結果は自動的に最新の状態が映されます
cars.size // => 2

はじめに

Realm React Nativeはnpmを利用してインストールすることができます。ソースコードはGitHubにあります。

必要条件

  • React Nativeアプリケーションの開発環境が整っている必要があります。React Nativeの開発環境のセットアップについては公式サイトの説明をご覧ください。
  • React Native 0.20.0以降をサポートしています。
  • 実行環境としてiOS、およびAndroidをサポートしています。

インストール

  • 新しくReactNativeプロジェクトを作成します。

    react-native init <project-name>

  • 作成したプロジェクトのディレクトリに移動します(cd <project-name>)。さらにRealmを依存関係として追加します。

    npm install --save realm

  • 作成されたXcodeプロジェクト(ios/<project-name>.xcodeproj)を開きます。
  • サイドバーで プロジェクトファイル が選択されていることを確認し、プロジェクト設定から”iOS Deployment Target”を”8.0”以上に指定します。
  • サイドバーの Libraries グループを右クリックし、 Add Files to “<project-name>” を選択します。そして../node_modules/realm/RealmJS.xcodeprojをファイル選択ダイアログから選択します。
  • アプリケーションターゲットの設定の”General”タブを選択します。左のカラムにある Libraries > RealmJS > Products を展開し、RealmReact.frameworkを”General”タブにある Embedded Binaries セクションにドラッグします。見つからない場合はスクリーンショットをご覧ください。
  • アプリケーションターゲットの設定の Build Phases タブを選択し、RealmReact.frameworkがビルドフェーズの Link Binary with Libraries セクションに追加されていることを確認してください。
  • 新しくReactNativeプロジェクトを作成します。

    react-native init <project-name>

  • 作成したプロジェクトのディレクトリに移動します(cd <project-name>)。さらにRealmを依存関係として追加します。

    npm install --save realm

  • 下記のコマンドをプロジェクトのディレクトリで実行します。

    react-native link realm

  • android/app/src/main/java/com/<project-name>/MainActivity.javaを開き、さらに下記を実行します。

    • import io.realm.react.RealmReactPackage;を既存のimport文の下に追加します。
    • new RealmReactPackage()getPackages()メソッドの戻り値の最後に追加します。


ここまでで、Realmを使用する準備が整いました。下記の定義をindex.ios.jsまたはindex.android.jsファイルのclass <project-name>に記述して、Realmが正しくセットアップされているか試してみてください。

const Realm = require('realm');

class <project-name> extends Component {
 render() {
   var realm = new Realm({schema:[{name:'Dog', properties:{name: 'string'}}]});
   realm.write(()=>{
     realm.create('Dog', ['Rex']);
   })
   return (
     <View style={styles.container}>
       <Text style={styles.welcome}>
         Count of Dogs in Realm: {realm.objects('Dog').length}
       </Text>
     </View>
   );
 }
}

デバイスまたはシミュレータを使って実行できます。

APIリファレンス

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

サンプルコード

サンプルコードを動かすにはまずRealm React NativeのプロジェクトをGitHubリポジトリからクローンします。

git clone https://github.com/realm/realm-js.git

サンプルプロジェクトはクローンしたリポジトリのexamples/ReactExampleフォルダに格納されています。初回のみ、サンプルを動かす前に、このディレクトリでnpm installを実行してください。

ヘルプ

  • 使い方に困ったときは、StackOverflowで#realmタグを付けて質問してください。私たちは毎日StackOverflowをチェックしています。
  • さらに複雑な問題に対する質問は、こちらの Slackチャットにて聞いてください。(質問は日本語で構いません)
  • 問題を発見した場合はGitHubのIssuesに報告してください。できる限り、ご使用のRealmのバージョン、エラーメッセージやログ、スアックトレースやRealmのデータファイル、問題を再現可能なプロジェクトなどを添えてください。
  • 機能のリクエストもGitHubのIssuesで教えてください。どのような機能が欲しいのか、また、どうしてその機能が必要なのかできるだけ具体的に教えてください。

もしクラッシュレポートツール(CrashlyticsやHockeyAppなど)を利用しているなら、ログコレクターを有効にしてください。Realmのログにはユーザーデータ以外のデバッグに有用なメタデータを含んでいます。それは私たちが問題を調査するときに非常に役に立ちます。

モデル

Realmのデータモデルは、スキーマの情報をRealmインスタンスの初期化時に渡すことによって定義されます。スキーマの情報とはモデルオブジェクトの名前、およびプロパティの名前や型などを示す一連の属性です。型は基本的な型に加えて、1対1の関連を示すobjectTypeや1対多の関連を示すリスト型が指定できます。またoptional(NULL可)やdefault(デフォルト値)についてもここで指定します。

var Realm = require('realm');

const CarSchema = {
  name: 'Car',
  properties: {
    make:  'string',
    model: 'string',
    miles: {type: 'int', default: 0},
  }
};
const PersonSchema = {
  name: 'Person',
  properties: {
    name:     'string',
    birthday: 'date',
    cars:     {type: 'list', objectType: 'Car'},
    picture:  {type: 'data', optional: true}, // オプショナル(NULL可)のプロパティ
  }
};

// CarとPersonのモデルクラスを指定してRealmを初期化します
let realm = new Realm({schema: [CarSchema, PersonSchema]});

既存のクラスを継承してモデルクラスを定義する場合は、スキーマをコンストラクタ内で定義して、コンストラクタをRealmの初期化時に渡します。

class Person {
  get ageSeconds() {
    return Math.floor((Date.now() - this.birthday.getTime()));
  }
  get age() {
    return ageSeconds() / 31557600000;
  }
}

Person.schema = PersonSchema;

// 注: `Person`クラスのコンストラクタをRealmに渡します
let realm = new Realm({schema: [CarSchema, Person]});

モデルクラスの定義ができていれば、オブジェクトをRealmに保存したり、検索することができます。

realm.write(() => {
  let car = realm.create('Car', {
    make: 'Honda',
    model: 'Civic',
    miles: 750,
  });

  // モデルに定義したすべてのプロパティを読み書きできます
  console.log('Car type is ' + car.make + ' ' + car.model);
  car.miles = 1500;
});

対応しているデータ型

Realmでは、次に示すデータ型を基本的なデータ型としてサポートしています: boolintfloatdoublestringdataおよびdate

  • bool型のプロパティは、JavaScriptにおけるBoolean型にマッピングされます。
  • intfloatおよびdouble型のプロパティは、JavaScriptにおけるNumber型にマッピングされます。内部的にはintおよびdouble型は64ビットの値として保存されます。一方float型は32ビットの値として保存されます。
  • string型のプロパティはString型にマッピングされます。
  • data型のプロパティはArrayBuffer型にマッピングされます。
  • date型のプロパティはDate型にマッピングされます。

基本的なデータ型をプロパティに指定する場合は、省略記法が使えます。プロパティの属性を含むオブジェクトを渡す代わりに、型名を示す文字列を渡します。

const CarSchema = {
  name: 'Car',
  properties: {
    // 下記のtypeプロパティの定義はどちらも同じ意味です
    make:   {type: 'string'},
    model: 'string',
  }
}

オブジェクト型のプロパティ

1対1の関連として、オブジェクト型のプロパティを指定するには、型にスキーマに定義した別のオブジェクトの名前(name)を指定します。

const PersonSchema = {
  name: 'Person',
  properties: {
    // 下記のtypeプロパティの定義はどちらも同じ意味です
    car: {type: 'Car'},
    van: 'Car',
  }
};

オブジェクト型のプロパティを使用する場合は、Realmを初期化する際に、指定したすべてのオブジェクト型がスキーマに定義されている必要があります。

// PersonSchema内で'Car'型のプロパティとしてCarSchemaが使われているので、CarSchemaも渡す必要があります。
let realm = new Realm({schema: [CarSchema, PersonSchema]});

オブジェクト型のプロパティにアクセスする場合は、標準の文法を用いて、ネストしたプロパティとしてアクセスします。

realm.write(() => {
  var nameString = person.car.name;
  person.car.miles = 1100;

  // JSON記法を用いて新しくCar型のプロパティを作成して代入します。
  person.van = {make: 'Ford', model: 'Transit'};

  // 同じCar型のプロパティであるcarとvanに同じインスタンスを設定します。
  person.car = person.van;
});

リスト型のプロパティ

1対多の関連として、リスト型のプロパティを指定するには、list型としてスキーマに指定し、同時に格納する要素をobjectTypeに指定します。

const PersonSchema = {
  name: 'Person',
  properties: {
    cars: {type: 'list', objectType: 'Car'},
  }
}

リスト型のプロパティにアクセスするとListオブジェクトが返ります。Listオブジェクトは通常のJavaScriptの配列オブジェクトとほとんど同じメソッドを持っています。大きく異なる点は、Listオブジェクトに対する変更はすべて自動的に永続化されるという点です。さらに、Listオブジェクトは取得したオブジェクトが保持しています。そのため、Listオブジェクトをプログラマが自分で生成することはできません。別のオブジェクトのプロパティとしてアクセスし、取得します。

let carList = person.cars;

// CarオブジェクトをListに追加します
realm.write(() => {
  carList.push({make: 'Honda', model: 'Accord', miles: 100});
  carList.push({make: 'Toyota', model: 'Prius', miles: 200});
});

let secondCar = carList[1].model;  // 添字を使って各要素にアクセスします

オプショナル(NULL可)プロパティ

各プロパティはoptional属性を用いて、オプショナル(NULL可)、または非オプショナル(NULL不可)として定義できます。

const PersonSchema = {
  name: 'Person',
  properties: {
    name:     {type: 'string'},               // 必須(非オプショナル・NULL不可)プロパティ
    birthday: {type: 'date', optional: true}, // オプショナル(NULL可)プロパティ

    // オブジェクト型のプロパティは常にオプショナル(NULL可)です
    car:      {type: 'Car'},
  }
};

let realm = new Realm({schema: [PersonSchema, CarSchema]});

realm.write(() => {
  // オプショナル(NULL可)プロパティは生成時にはnullまたはundefinedを設定できます
  let charlie = realm.create('Person', {
    name: 'Charlie',
    birthday: new Date(1995, 11, 25),
    car: null,
  });

  // オプショナル(NULL可)プロパティは`null`、`undefined`、
  // または通常の値のいずれかを設定できます
  charlie.birthday = undefined;
  charlie.car = {make: 'Honda', model: 'Accord', miles: 10000};
});

上記に示す通り、オブジェクト型のプロパティは明示的に指定しなくても常にオプショナルとして扱われます。一方、List型のプロパティはオプショナルとして定義することはできず、nullをセットすることはできません。List型のプロパティを空にする際は、プロパティに空の配列をセットします。

デフォルト値

各プロパティはdefault属性を用いて、デフォルト値を定義できます。デフォルト値を設定したいプロパティは、オブジェクトの生成時にそのプロパティはundefinedのままにしておきます。

const CarSchema = {
  name: 'Car',
  properties: {
    make:  {type: 'string'},
    model: {type: 'string'},
    drive: {type: 'string', default: 'fwd'},
    miles: {type: 'int',    default: 0}
  }
};

realm.write(() => {
  // `miles`プロパティは何も指定していないのでデフォルト値の`0`が設定されます。
  // また`drive`プロパティはここで指定しているので、デフォルト値は使用されず指定した値で上書きされます。
  realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});

プライマリキー

string型およびint型のプロパティについてはprimaryKey属性を用いてプライマリキーとして指定できます。プラオマリキーが定義されていると、オブジェクトの検索と更新を効率的に行えることに加え、値が重複していないことを保証できます。プライマリキーが設定されたオブジェクトは、Realmに保存した後でプライマリキーの値を変更することはできなくなります。

const PersonSchema = {
  name: 'Person',
  primaryKey: 'id',
  properties: {
    id:   'int',    // プライマリキー
    name: 'string'
  }
};

書き込み

Realmへのオブジェクトの追加、変更、削除は、トランザクションの内部で行う必要があります。

トランザクションのオーバーヘッドは大きいのでコード中のトランザクションは、できる限り少なくなるように設計してください。

オブジェクトの生成

これまでに説明したように、オブジェクトの生成にはcreateメソッドを使用します。

let realm = new Realm({schema: [CarSchema]);

realm.write(() => {
  realm.create('Car', {make: 'Honda', model: 'Accord', drive: 'awd'});
});

ネストしたオブジェクト

オブジェクト型のプロパティを持つオブジェクトは、各オブジェクトのプロパティの値をJSONを用いて再帰的に子のプロパティも含めて一度に生成できます。

let realm = new Realm({schema: [PersonSchema, CarSchema]);

realm.write(() => {
  realm.create('Person', {
    name: 'Joe',
    // ネストしたオブジェクトを1度に再帰的に生成できます
    car: {make: 'Honda', model: 'Accord', drive: 'awd'},
  });
});

オブジェクトの更新

プロパティへの代入

オブジェクトを更新するには、トランザクションの中でプロパティに値を設定します。

realm.write(() => {
  car.miles = 1100;
});

プライマリキーを使ってオブジェクトを作成・更新する

モデルにプライマリキーを指定しているなら、オブジェクトがすでに存在する場合は更新、存在しない場合は新しく追加というように、追加または更新を一度に行うことができます。この機能を利用するにはcreateメソッドの3つ目の引数にtrueを渡します。

realm.write(() => {
  // Bookオブジェクトを生成して保存します
  realm.create('Book', {primaryId: 1, title: 'Recipes', price: 35});

  // 上で保存したBookオブジェクトのPriceプロパティをプライマリキーを指定して更新します
  realm.create('Book', {primaryId: 1, price: 55}, true);
});

上記の例では、最初に保存されたBookオブジェクトはプライマリキーとしてprimaryIdプロパティを持ち、プライマリキーは1です。次の行で同じプライマリキー1を持つオブジェクトを渡し、3つ目の引数をtrueに指定します。そのため、新しくオブジェクトが作成されるのではなく、既存のオブジェクトのpriceプロパティが更新されます。nameプロパティは渡しているオブジェクトに含まれていないので、更新されず元の値が維持されます。

オブジェクトの削除

オブジェクトを削除するにはトランザクションの中でdeleteメソッドを使用します。

realm.write(() => {
  // Bookオブジェクトを作成し、保存します
  let book = realm.create('Book', {primaryId: 1, title: 'Recipes', price: 35});

  // Bookオブジェクトを削除します
  realm.delete(book);

  // `Results`、`List`、またはJavaScriptの`Array`を渡すと
  // 1度に複数のオブジェクトを削除できます
  let allBooks = realm.objects('Book');
  realm.delete(allBooks); // すべてのBookオブジェクトを削除します
});

クエリ

Realmのクエリは、どれか1つのオブジェクト型を指定して保存されているオブジェクトをRealmから取得します。検索条件を指定して結果をフィルタしたり、並べ替えることもできます。すべてのクエリと検索結果のプロパティアクセスは自動的に遅延されます。実際のデータはオブジェクトとプロパティにアクセスしたときにのみ取得されます。このことにより、大量のデータでも効率よく扱うことができます。

クエリを実行するとResultsオブジェクトが返ります。Resultsは検索結果を表します。Resultsオブジェクトの内容を変更することはできません。

オブジェクトを検索するもっとも基本的なメソッドは、Realmobjectsメソッドです。引数で与えられた型のオブジェクトをすべて取得します。

let dogs = realm.objects('Dog'); // Realmに保存されているすべてのDogオブジェクトを取得します

検索条件を指定する

filteredメソッドにクエリ文字列で検索条件を渡すことで、Resultsオブジェクトに含まれるオブジェクトをフィルタすることができます。

下記の例では、先のDogオブジェクトをすべて取得する例に少し手を加えて、colorプロパティが”tan”かつnameが”B”から始まるオブジェクトを取得します。

let dogs = realm.objects('Dog');
let tanDogs = dogs.filtered('color = "tan" AND name BEGINSWITH "B"');

並べ替え

1つまたは複数のプロパティを指定してResultsを並べ替えることができます。下記の例では、milesプロパティの昇順で並べ替えます。

let hondas = realm.objects('Car').filtered('make = "Honda"');

// ホンダ製(make = "Honda")かつ、走行距離(miles)の昇順
let sortedHondas = hondas.sorted('miles');

クエリの実行結果(Results)の順序はソートしなければ保証されません。パフォーマンス上の都合により、オブジェクトの挿入順は保持されません。

検索結果(Results)の自動更新(ライブアップデート)

Resultsは常に最新の状態に自動的に更新されます。このため、同じ検索条件なら繰り返し検索を実行して、結果を取得し直す必要はありません。Resultsは常に現在の最新の状態を反映します。

let hondas = realm.objects('Car').filtered('make = "Honda"');
// hondas.length == 0

realm.write(() => {
  realm.create('Car', {make: 'Honda', model: 'RSX'});
});
// hondas.length == 1

この仕組みはすべてのResultsインスタンスに(検索条件や、並べ替えの有無にかかわらず)適用されます。

Resultsが持つこの性質によって、Realmは効率的で高速な処理を実現しています。さらにアプリケーションのコードをシンプルかつリアクティブにすることを可能にします。例えば、クエリの検索結果を表示するビューに対しては、Resultsを保持して直接データの表示に使うことで、アクセスするたびに再検索することなく、常に最新のデータを表示することができます。

合わせてRealmの変更通知イベントを利用すると、データが更新され、アプリケーションのUIをアップデートするタイミングがわかります。その場合も、Resultsを取得し直す必要はありません。

取得データの数を制限

Realm以外のほとんどのデータベースには検索結果を「ページネーション(ページング)」する仕組みが備わっています(例えばSQLiteの’LIMIT’句によるものなどです)。この仕組みはディスクの過剰な読み込み、あるいは一度に大量のデータをメモリに読み込むことを避けるために使われます。

Realmのクエリは遅延実行されるので、このような「ページネーション」の仕組みはまったく必要ありません。なぜなら、Realmはクエリの実行結果の要素に対して、実際にアクセスしたときだけオブジェクトを読み込むからです。

UIや実装の都合により、クエリの実行結果の一部分だけが必要だったとします。そのときは、単にResultsオブジェクトを用いて、必要な要素にだけアクセスすれば良いのです。

let cars = realm.objects('Car');

// データを5件に制限したい場合は、
// 単に最初から5番目までのオブジェクトにアクセスします
let firstCars = Array.prototype.slice.call(cars, 0, 5);

Realmについて

複数のRealmを使い分ける

複数のRealmファイルを別の場所に保存して使い分けることができると便利です。例えば、事前に用意した組み込み済みデータを、メインのデータファイルとは別に読み込み専用のRealmとして利用するなどです。

Realmオブジェクトの初期化時にpathを指定することで、Realmファイルの保存場所を指定できます。pathの指定はアプリケーションのドキュメントディレクトリからの相対パスになります。

// Realmをデフォルト値とは別の保存先を指定して取得します
let realmAtAnotherPath = new Realm({
  path: 'anotherRealm.realm',
  schema: [CarSchema]
});

デフォルトRealmの保存先

このドキュメントにおけるこれまでのコード例では、path引数を指定していなかったことにお気づきでしょう。path引数を指定しない場合は、デフォルトの保存先が使われます。デフォルトの保存先を知る、または変更するにはグローバルプロパティに定義されているRealm.defaultPathを使用します。

スキーマバージョン

Realmの初期化時に指定できる最後の1つのプロパティはschemaVersionです。指定しなかった場合は、デフォルトの値として0が使われます。既存のデータからスキーマを変更した場合は、必ずschemaVersionを初期化時に指定しなければなりません。もしスキーマが変更されているにもかかわらず、schemaVersionを指定しなかった場合は例外が発生します。

const PersonSchema = {
  name: 'Person',
  properties: {
    name: 'string'
  }
};

// schemaVersionのデフォルトは0です
let realm = new Realm({schema: [PersonSchema]});

const UpdatedPersonSchema = {
  // スキーマの`name`プロパティが同じなので`Person`オブジェクトのスキーマが
  // 更新されたことになります
  name: 'Person',
  properties: {
    name: 'string',
    dog:  'Dog'     // 新しいプロパティを追加
  }
};

// 下記の記述はスキーマが更新されているにもかかわらず、
// スキーマバージョンを指定していないので例外が発生します。
let realm = new Realm({schema: [UpdatedPersonSchema]});

// 下記の記述ではスキーマの更新が成功し、新しいスキーマのRealmを取得します
let realm = new Realm({schema: [UpdatedPersonSchema], schemaVersion: 1});

マイグレーション

マイグレーション機能については、現在のバージョンでは上記の例に示したような、単純なスキーマの変更とschemaVersionの更新による自動マイグレーションのみサポートしています。個々のデータのマイグレーションは未サポートです。将来的にサポートを予定しています。

変更通知イベント

トランザクションが完了した際には変更通知イベントが送られます。変更通知イベントを受けるためにはリスナーを登録します。

// Realmの変更通知イベントを監視します。
realm.addListener('change', () => {
  // このタイミングでUIを更新します
  ...
});

// 変更通知イベントの監視を解除します
realm.removeAllListeners();

React Native ListView

ListResultsのインスタンスをListViewのデータソースとして利用する場合は、realm/react-nativeモジュールにて提供されているListViewListView.DataSourceを利用することを強く推奨します。

import { ListView } from 'realm/react-native';

realm/react-nativeモジュールが提供するListViewのAPIは完全にReact.ListViewと同じです。使い方はReact.ListViewのドキュメントをご覧ください。

トラブルシューティング

クラッシュレポート

私たちは開発者の方にクラッシュレポーターを使用していただくことを推奨しています。Realmの操作のうちの多くは(ディスクI/Oを伴う操作などと同様に)実行時に失敗する可能性があります。そのため、クラッシュレポートを収集することは、何が原因で問題が発生したのかを特定し、エラー処理の改善や不具合の修正に有効です。

多くの商用のクラッシュレポーターはログを収集するオプションを提供しています。この機能を有効にすることを強く推奨します。Realm例外が発生した時や、致命的な状態に陥った際にはメタデータの情報をログに出力しており(そこにユーザーデータは一切含まれていません)、それらのログは我々が問題を調査する際に役に立ちます。

Realmの問題や不具合を報告するには

Realmについて問題を発見した際は、GitHubで問題を報告するか、help@realm.ioにEメール、またはSlackで報告してください。その際には、こちらで問題を再現できるように、できるだけ多くの情報をあわせて教えてください。

下記に示す情報は問題を解決するために非常に役に立ちます。

  1. あなたが実際にやりたいこと・目的。
  2. 期待している結果。
  3. 実際に発生した結果。
  4. 問題の再現方法。
  5. 問題を再現、または理解できるサンプルコード (そのままビルドして実行できるプロジェクトだと理想的です)
  6. Realmのバージョン
  7. クラッシュログやスタックトレース。上述のクラッシュレポートの項目も参考にしてください。