Realmこと始め(React Native)
React NativeでローカルDBとしてRealmを使ってみたのでまとめてみます
Realm とは
React Nativeのアプリ内でデータを永続化して保持しておきたい場合、以下の3つが候補に上がってくると思います。
- FirebaseやRDBMSを立てるなどして、アプリ外にデータを持つ
- AsyncStorageを使用する
- RealmやSQLiteなどのローカルDBを使用する
RealmはローカルDBとして使用されるケースが多いですが、Atlas App Servicesと連携すれば、複数端末間でデータを同期することも可能です。
これは個人的な意見ですが、普段からRDBMS(MySQLなど)を使用された経験がある場合は、だいぶ取っ掛かりやすいと思います。
AsyncStorageとの違い
そもそも私自身がAsyncStorageを使用したことがなく、あまり詳しくないので詳細は書けませんが、AsycStorageもローカルにデータを残すことが可能です。
Realmと異なる点はクエリでデータを取得したりすることができないこと。
データをkey:value
のJSON形式で残していく形になるので、複雑なデータ構造やデータ量が多くなる場合には不向きです。
またDate()
などのObject形式でのデータ保持がAsyncStorageではできません。
Realm インストール
※ インストール済みの方は飛ばしていただいて大丈夫です。
以下は Ver.11.2.0
で動作確認済みです
Realmをインストール
$ npm install realm
Pod インストール
$ cd ios
$ pod install
Schema Objectを定義
DBのschemaを定義します。
例えばユーザーというデータを持ちたい場合
import Realm from 'realm';
const userSchema = {
name: 'User', // テーブル名
properties: { // カラム
_id: {
type: 'objectId',
default: () => new Realm.BSON.ObjectId(),
indexed: true
},
name: 'string',
email: 'string',
plan: {type: 'int', default: 0}, // 0:Free, 1:Pro
receipt: {type: 'string', optional: true},
createdAt: {type: 'date', default: new Date()},
},
primaryKey: '_id', // 主キー
};
id
やcreatedAt
なんかのデフォルト値をセットする場合はdefault
プロパティをセットしておくと楽です
また、カラムに対してindexed
を追加するとインデックスを張ることが可能です
_id: {
type: 'objectId',
default: () => new Realm.BSON.ObjectId(),
indexed: true
},
オプショナル(null許容)にしたい場合はoptional
をセットしておきます
receipt: {type: 'string', optional: true},
その他、使用できるタイプ
Realmの呼び出し
執筆時に確認のためドキュメントを読んでいたら、Provider
があって、それを使う方法が正式みたいです。
本当は公式の方法を推奨しますが、以下の方法で私は使用してます。
import Realm from 'realm';
const UserSchema = {(省略)}
// Realmの初期化
export const openRealm = () => {
const config = {
path: 'hogeApp',
schema: [UserSchema,], // スキーマをセット
schemaVersion: 1,
// NOTE: 本番は絶対falseにすること
deleteRealmIfMigrationNeeded: false,
};
return new Realm(config);
};
deleteRealmIfMigrationNeeded
は、本番リリースするときには必ずfalse
にするか、configから消しておくことを忘れないようにしてください(デフォルトはfalse
)
でないと、スキーマのバージョンを上げたり、カラムを追加などの変更があった場合にデータが自動で削除されてしまいます。
開発時は頻繁にカラム追加や変更したりすると思うので、true
にしておいた方がいちいちschemaVersion
を上げなくて済みます。
configはこちらを参考に設定
アプリをアップデートしていく中でマイグレーションをする必要が出て来る場合があると思いますが、その場合は以下の対応が必要です。
-
schemaVersion
に+1をする -
onMigration
にマイグレーション処理を追加
例えば、「Setting
テーブルを追加して、User
と紐づけておきたい」となった場合、
SettingSchema
を追加して以下のようにマイグレーション処理を追記することになります。
import Realm from 'realm';
const UserSchema = {(省略)}
+ const SettingSchema = {
+ name: 'Setting',
+ properties: {
+ _id: {
+ type: 'objectId',
+ default: () => new Realm.BSON.ObjectId(),
+ indexed: true
+ },
+ userId: {type: 'string', indexed: true},
+ theme: {type: 'int', default: 0} // テーマ(0:ライト, 1:ダーク)
+ },
+ primaryKey: '_id',
+ };
// Realmの初期化
export const openRealm = () => {
const config = {
path: 'hogeApp',
- schema: [UserSchema,],
+ schema: [UserSchema, SettingSchema], // スキーマを追加
- schemaVersion: 1,
+ schemaVersion: 2, // Versionに+1
// NOTE: 本番は絶対falseにすること
deleteRealmIfMigrationNeeded: false,
+ // マイグレーション処理を追加
+ onMigration: (oldRealm, newRealm) => {
+ if (oldRealm.schemaVersion < 2) {
// 紐づけたいユーザーを取得
+ const user = newRealm.objects('User')[0];
+ // Settingが作成されていないのでマイグレーションで作成する
+ newRealm.create('Setting', {
+ _id: new Realm.BSON.ObjectId(),
+ userId: String(user._id),
+ theme: 0
+ });
+ }
+ }
};
return new Realm(config);
};
CRUD処理
Create(データ作成)
import { openRealm } from '../realm';
// Realmの初期化
const realm = openRealm();
type UserProps = {
name: string;
email: string;
};
/**
* Userを作成する
* @param {Object} userData
* @returns {Boolean}
*/
export const createUser = (data: UserProps): Boolean => {
try {
// DBにデータを保存
realm.write(() => {
realm.create('User', data);
});
return true;
} catch (error) {
console.error(error);
return false;
}
};
Read(データ取得)
/**
* ユーザーを取得する
* @param {string} id
* @returns {UserProps}
*/
export const getLoginUser = (id: string): UserProps => {
let user = <UserProps>{};
try {
let users = realm.objects('User') ?? [];
user = users.filtered('isLogin == $0', true)[0];
} catch (error) {
console.error(error);
}
return user;
};
絞り込み検索をする場合filtered
で絞り込みがかけれる
検索かけたい値を引数にセットする。引数は$0
で表す。複数ある場合は$0
,$1
,$2
となる
const today = new Date();
const start = new Date(target);
let users = realm.objects('User') ?? [];
const user = users.filtered('date >= $0 AND date <= $1', start, today);
またsorted
で降順、昇順の切り替えが可能
const today = new Date();
const start = new Date(target);
let users = realm.objects('User') ?? [];
const user = users
.filtered('date >= $0 AND date <= $1', start, today)
+ .sorted('date', false); // 降順で並び替え
Update(更新)
/**
* ユーザー情報をアップデートする
* @param {UserProps}
* @returns {Boolean}
*/
export const updateUser = (data: UserProps): Boolean => {
try {
let user: UserProps = realm.objectForPrimaryKey('User', data._id);
realm.write(() => {
user.name = data.name;
user.email = data.email;
});
return true;
} catch (error) {
console.error(error);
return false;
}
};
primaryKey
でオブジェクト取得する場合はobjectForPrimaryKey
を使用する
let user = realm.objectForPrimaryKey('User', User._id);
Delete(削除)
/**
* ユーザーを削除する
* @param {string} id
* @returns {boolean}
*/
export const deleteUserById = (id: string): boolean => {
try {
let user = realm.objectForPrimaryKey('User', id);
realm.write(() => {
realm.delete(user);
});
return true;
} catch (error) {
console.error(error);
return false;
}
};
注意しないといけないのが、データ削除後にView側で削除したrealmデータを参照しに行ってしまいエラーとなる現象があるので、削除後は空のオブジェクトをセットする必要があります。
参考
Discussion