▼ Firebase Realtime DB
▼ Firebase Realtime DB
-------------------------------------------------
▼ 設計なしで、いきなり値を置くことが可能
-------------------------------------------------
private DatabaseReference databaseReference;
private DatabaseReference helloEndpoint;
private DatabaseReference worldEndpoint;
private void someMethod(){
// 下記3つとも DatabaseReference だが、ルート と その子 2つ という構成になる
databaseReference = FirebaseDatabase.getInstance().getReference();
helloEndpoint = databaseReference.child("Hello");
worldEndpoint = databaseReference.child("World");
helloEndpoint.setValue("Hello");
}
-------------------------------------------------
▼ 上記コードで、下記 構成が生成される。これが、 NoSQL
-------------------------------------------------
ruten-b9288{
Hello:"Hello"
}
-------------------------------------------------
▼ 一連のシークエンスを DB 保存するにはどうする?
-------------------------------------------------
案1:
ArrayList だけのためのテーブルを作り、他のテーブルから参照する
案2:
Gson を使う
- 格納時
-------------------------------------------------
ArrayList<String> inputArray=new ArrayList<String>();
Gson gson = new Gson();
String inputString= gson.toJson(inputArray);
-------------------------------------------------
- 読み込み時
-------------------------------------------------
Type type = new TypeToken<ArrayList<String>>() {}.getType();
ArrayList<String> finalOutputString = gson.fromJson(outputarray, type);
-------------------------------------------------
▼ Model 設計の制約のアイデア
-------------------------------------------------
Model 設計の制約
Model 名は単数形の名詞にする
- Model 任意でKeyをセットする場合はSortできるキーをセットする
Firebase の性能はKeyで決まります。
Key は時系列を含み作成された順番を保っています。
Model は_updatedAt _createdAtを保持する
「いつ発生した」は、あらゆる問題を解決するための重要ないとぐち
-------------------------------------------------
▼ Model 内のプロパティにAccessKeyを含める
-------------------------------------------------
{
user: {
userID0: {
activities: {
activityID0: true,
activityID1: true
}
}
},
activity: {
activityID0: {
userID: userID0 // activityID0にアクセスするためのAccessKey
content: "hello",
_createdAt: 012345678,
_updatedAt: 012345678,
}
}
}
-------------------------------------------------
// Rule
{
"rules": {
"user": {
"$user_id": {
// 認証後、ログインユーザーと一致している場合データを読むことができる
".read": "auth != null && auth.uid == $user_id",
// 認証後、ログインユーザーと一致している場合データを書き込むことができる
".write": "auth != null && auth.uid == $user_id"
}
},
"activity": {
"$activity_id": {
// Userのactivitiesの中に$activity_idを保持していればデータを読むことができる
".read": "root.child('user/'+ auth.uid +'/activities/'+$activity_id).exists()"
// このデータのuser_idがログインユーザーのIDと一致していればデータを読むことができる。
".read": "data.child('user_id').val() == auth.uid"
}
}
}
}
-------------------------------------------------
▼ 各モデルが相互で参照を持つ構造は、Firebase Realtime DB でのModel設計の定石
-------------------------------------------------
{
"group":{
"0":{
"name":"groupA"
"users":{// 相互で参照を持つ →Gourp に所属する User を取得できるようになる
"0":true,// users の id 0 と リレーションを持つ(true と表現)
"1":true
}
},
"1":{
"name":"groupB"
"users":{
"2":true,
"3":true
}
}
},
"user":{
"0":{
"groups":{// 相互で参照を持つ
"0":true// groups の id 0 と リレーションを持つ
},
"name":"userA"
},
"1":{
"groups":{
"0":true
},
"name":"userB"
},
"2":{
"groups":{
"1":true
},
"name":"userC"
},
"3":{
"groups":{
"1":true
},
"name":"userD"
}
}
-------------------------------------------------
▼ データは平坦にしてネストしないようにする。
// データベース内の特定の場所にあるデータをフェッチすると、そのすべての子ノードも取得されてしまうため
-------------------------------------------------
{
// Chats → 各会話のメタ情報のみ内包
"chats": {
"one": {// chats の ユニークID としての one
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
},
"two": { ... },
"three": { ... }
},
// chats と同じ階層。直下の chats ID を保持して相互参照。その配下に、members プロパティ
"members": {
// we'll talk about indices like this below
"one": {
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
// chats と同じ階層。直下の chats ID を保持して相互参照。
"messages": {
"one": {
"m1": {
"name": "eclarke",
"message": "The relay seems to be malfunctioning.",
"timestamp": 1459361875337
},
"m2": { ... },
"m3": { ... }
},
"two": { ... },
"three": { ... }
}
}
-------------------------------------------------
▼ データを拡張性高くする // M:N な構造
-------------------------------------------------
/users/$uid/groups/$group_id
を読み取ってそれが null であることを確認するだけで、キーがないかのチェックが済む
インデックスはデータのクエリやスキャンを実行するよりも迅速かつ効率的。
-------------------------------------------------
// An index to track Ada's memberships
{
"users": {
"alovelace": {// ID
"name": "Ada Lovelace",
// このユーザーのグループを、プロパティ内でインデックスする
"groups": {
"techpioneers": true,// IDの相互参照。IOW, グループをユーザーは以下でインデックス化するという言い方。
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {// ID
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,// IDの相互参照。アイテム削除時は、双方を更新する必要がある。
"ghopper": true,
"eclarke": true
}
},
...
}
}
-------------------------------------------------
▼ リンクテーブルみたいなのを作って、検索を簡素化するという方法
-------------------------------------------------
{
"users": {
"1": {
"name": "David"
},
"9": {
"name": "Alice"
},
"events":{
"fm":{
"name":"Firebase Meetup",
"date":924654321
}
},
"eventAttendees":{// users と events とのリンクブロック
"fm":{// イベントのプライマリキー
"1":"David",// users のプライマリキー:Name の key:value
"9":"Alice"
}
}
}
}
-------------------------------------------------
エンドポイントをよういしておいて。。。
然るべきポイントで リンクブロックに追加
private DatabaseReference eventsEndpoint = dbRootReference.child("events/fm");
private DatabaseReference attendeeEndpoint = dbRootReference.child("eventAttendees/fm");
-------------------------------------------------
▼ denormalization 非正規化 (複数のパスに分割する) 平坦にする
-------------------------------------------------
{
"chats": {// メタデータを持つ
"one": {// chat のユニークID
"title": "Historical Tech Pioneers",
"lastMessage": "ghopper: Relay malfunction found. Cause: moth.",
"timestamp": 1459361875666
},
"two": { ... },
"three": { ... }
},
// Conversation members are easily accessible
// and stored by chat conversation ID
"members": {
// we'll talk about indices like this below
"one": {// chat のユニークID
"ghopper": true,
"alovelace": true,
"eclarke": true
},
"two": { ... },
"three": { ... }
},
// Messages are separate from data we may want to iterate quickly
// but still easily paginated and queried, and organized by chat
// conversation ID
"messages": {
"one": {
"m1": {
"name": "eclarke",
"message": "The relay seems to be malfunctioning.",
"timestamp": 1459361875337
},
"m2": { ... },
"m3": { ... }
},
"two": { ... },
"three": { ... }
}
}
-------------------------------------------------
▼ インデックス化
-------------------------------------------------
/users/$uid/groups/$group_id これが null かどうかで、キーの有無をチェックできる
-------------------------------------------------
// An index to track Ada's memberships
{
"users": {
"alovelace": {
"name": "Ada Lovelace",
// Index Ada's groups in her profile
"groups": {
// the value here doesn't matter, just that the key exists
"techpioneers": true,// users のこの ID が所属する groups
"womentechmakers": true
}
},
...
},
"groups": {
"techpioneers": {
"name": "Historical Tech Pioneers",
"members": {
"alovelace": true,// groups のこの ID に属している users
"ghopper": true,
"eclarke": true
}
},
...
}
}
-------------------------------------------------
▼ ユーザーに紐付いた 値 を格納するために、ユーザー情報を取得して。。。
-------------------------------------------------
// Disable button so there are no multi-posts
setEditingEnabled(false);
Toast.makeText(this, "Posting...", Toast.LENGTH_SHORT).show();
// [START single_value_read]
final String userId = getUid();// Auth から ID を取得
mDatabase.child("users").child(userId).addListenerForSingleValueEvent(//users/userId/ から。。。
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get user value
User user = dataSnapshot.getValue(User.class);// Userにあたる値を取得
// [START_EXCLUDE]
if (user == null) {// 取得した値が nullでないのを確認して
// User is null, error out
Log.e(TAG, "User " + userId + " is unexpectedly null");
Toast.makeText(NewPostActivity.this,
"Error: could not fetch user.",
Toast.LENGTH_SHORT).show();
} else {
// Write new post
writeNewPost(userId, user.username, title, body);// ID、User 情報、今回書き込む内容を渡して。。。
}
// Finish this Activity, back to the stream
setEditingEnabled(true);
finish();
// [END_EXCLUDE]
}
-------------------------------------------------
▼ ユーザー限定パスと、全ユーザーの投稿用パスに、一度に 保存。
-------------------------------------------------
// [START write_fan_out]
private void writeNewPost(String userId, String username, String title, String body) {
// Create new post at /user-posts/$userid/$postid and at
// /posts/$postid simultaneously
String key = mDatabase.child("posts").push().getKey();// 書き込みの ユニークID を取得し、
Post post = new Post(userId, username, title, body);// Post モデルを作成し、
Map<String, Object> postValues = post.toMap();// それを Map にする
Map<String, Object> childUpdates = new HashMap<>();// 空の Map を作成し、
// エンドポイントをキーに、Map を value で格納
childUpdates.put("/posts/" + key, postValues);// 書き込み のパスに格納
childUpdates.put("/user-posts/" + userId + "/" + key, postValues);// 固有のユーザーの書き込みのパスに格納
mDatabase.updateChildren(childUpdates);// updateChildren() で、複数の子に格納
}
// [END write_fan_out]
-------------------------------------------------
▼ Listener のデタッチ
-------------------------------------------------
@Override
public void onStop() {
super.onStop();
// Remove post value event listener
if (mPostListener != null) {
mPostReference.removeEventListener(mPostListener);
}
// Clean up comments listener
mAdapter.cleanupListener();
}
-------------------------------------------------
▼ SQL のステートメントの、Firebase への置き換え
-------------------------------------------------
// 1. Select a user by UID
private DatabaseReference user1Ref = rootRef.child("users").child("1");
// 2. Find a user by email address
private DatabaseReference mailRef = rootRef.child("users").orderByChild("email").equalTo("alice@email.com");
// 3. Limit to 10 users
SELECT * FROM Users LIMIT 10;
private DatabaseReference limit10Ref = rootRef.child("users").limitToFirst(10);
// 4. Get all users names that start with "D"
SELECT * FROM Users WHERE Name LIKE 'D%';
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("name").startAt("D").endAt("D/uf8ff");
// 5. Get all users who are less than 50
SELECT * FROM Users WHERE Age < 50;
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("age").endAt(49);
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("age").startAt(51);//Age > 50
private DatabaseReference limit10Ref = rootRef.child("users").orderByChild("age").startAt(20).endAt(10);//20 >=Age >= 100
// 6. get all users who are 28 and Live in Berlin
SELECT * FROM Users WHERE Age = 28 && Location = "Berlin"
下記のように、age と location との組み合わせで index を作っておく
{
"users": {
"1": {
"name": "David",
"age":99,
"location":"SF",
"age_location":"99_SF"
}
}
private DatabaseReference limit10Ref = rootRef.child("age_location").orderByChild("age").equalTo("99_SF");
-------------------------------------------------
▼ 読み取り //////////////////////////////////////////////////////////////////////
▼ 二つの Listener
-------------------------------------------------
アタッチした Listener の回数分、
removeEventListener() しましょう。
-------------------------------------------------
▼ ValueEventListener
-------------------------------------------------
特定のパスにあるコンテンツの静的スナップショットを、
イベントのときに存在していたとおりに読み取る
-------------------------------------------------
▼ ChildEventListener
-------------------------------------------------
ノードの 子 の追加や更新が トリガーとなる。特定の イベントに対する リッスン に使える
-------------------------------------------------
▼ 単発読み取り(リッスンなし)
-------------------------------------------------
final String userId = getUid();
mDatabase.child("users").child(userId)
.addListenerForSingleValueEvent(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
// Get user value
User user = dataSnapshot.getValue(User.class);
// ...
}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "getUser:onCancelled", databaseError.toException());
}
});
-------------------------------------------------
▼ ルール//////////////////////////////////////////////////////////////////////
▼ Advanced ルール
-------------------------------------------------
一部のユーザーに管理者アクセスを与えたり。
ユーザーが目標を達成したら、DB に格納された feature をアンロックしたり。メッセージを送ったり。
課金したときにのみ提供する featureなど。
-------------------------------------------------
例)課金者限定プライベートチャットルーム
1.スペシャルチャットのメッセージを含むメッセージのチャイルドを設定する
1.課金者のみが プライベートチャットルーム にアクセスできるというルールを設定
-------------------------------------------------
- DB
{
// トップレベルノード 1
"chat": {
"messages": {
"-KS3PV-iwUZp5wkNq70s": {
"name": "person1",
"text": "hey!"
},
"-KS3PXhIhs8J_inrExy4": {
"name": "person2",
"text": "what’s up?"
}
}
},
// トップレベルノード 2// 課金者のみにアクセス許可
"special_chat": {
"messages": {
"-KR-DwqtKzlWGxSn9P0y": {
"name": "person1",
"text": "Want to go to the movies?"
},
"-KR4tIpWmNn-EYxquSrw": {
"name": "person3",
"text": "Yeah! Let’s meet at 7."
}
}
},
// トップレベルノード 3// Uid ノード。課金booleanフラグあり
"users": {
"uid1": {
"paid": true
},
"uid2": {
"paid": false
},
"uid3": {
"paid": true
}
}
}
-------------------------------------------------
- DB セキュリティルール
{
"rules": {
// 認証者のみ読み書き
"chat" : {
"messages" : {
".read": "auth != null",
".write": "auth != null"
}
},
// users DB の paid の値が true のユーザーのみ。
"special_chat" : {
"messages": {
".read":
// root 変数を使用
"root.child('users').child(auth.uid).child('paid').val() === true",
".write":
"root.child('users').child(auth.uid).child('paid').val() === true"
}
}
}
}
-------------------------------------------------
その他の例
".write": "root.child('allow_writes').val() === true &&
!data.parent().child('readOnly').exists() &&
newData.child('foo').exists()"
-------------------------------------------------
{
"rules": {
"messages": {
// 認証済みデータのみ読み書き許可
".read": "auth != null",
".write": "auth != null",
"$id": {
// 読み書き許可がカスケードされて。。。
// メッセージがどんな 子 を持つべきかの検証ルールを定義
".validate": "newData.hasChildren(['name', 'text']) && !newData.hasChildren(['photoUrl']) || newData.hasChildren(['name', 'photoUrl']) && !newData.hasChildren(['text'])"
-------------------------------------------------
▼ ルールをカスケードする
-------------------------------------------------
.read .write が true の場合 →そのルールが、子にもカスケードされる(子が false にしても効果なし)// 段々滝みたく連なっていく感じ。
親が true false の場合はカスケードされない。
-------------------------------------------------
例)
{
"chat": {
"messages": {// chat の子
"-KRiMpW5bate5qV0Rt7i": {
"name": "person1",
"text": "hey!"
},
"-KQWHI_eepS4CGr8-kJd": {
"name": "person2",
"text": "what’s up?"
}
},
"admin_blog": {// chat の子
"Jan 1": "Welcome to my page",
"Jan 2": "Enjoying the weather?"
},
"special_chat": {
},
"users": {
"uid1": {
"paid": true
},
"uid2": {
"paid": false
},
"uid3": {
"paid": true
}
}
}
}
-------------------------------------------------
▼ 親のルールが強制され、子のルールは無視される
-------------------------------------------------
{
"rules": {
"chat" : {// "chat" の read/write が true になっている。
// 子セクションにも true がカスケードされていく
".read": "true",
".write": "true",
"admin_blog" : {
// 親セクションで true になっているため、
// ここで false としても、chat の子である限り、true がカスケードされ、false は適用されない。
".write" : "false"
}
}
}
}
-------------------------------------------------
▼ 親がルールを定義していないため、子は独自ルールを立てられる
-------------------------------------------------
{
"rules": {
"chat" : {
"messages" : {// "messages" の read/write が true になっている。
".read": "true",
".write": "true"
},
"admin_blog" : {
// 親セクションにルールは定義されていないため、カスケードはおこらず、独自のルールを定義できる。
".read" : "true",
".write" : "false"
}
}
}
}
-------------------------------------------------
$ Variables を使って、ルール定義時に 値を参照
-------------------------------------------------
{
"rules": {
"rooms": {
// this rule applies to any child of /rooms/, the key for each room id
// is stored inside $room_id variable for reference
"$room_id": {
"topic": {
// the room's topic can be changed if the room id has "public" in it
".write": "$room_id.contains('public')"
}
-------------------------------------------------
▼ 特殊変数 $other →それ以外 を意味する
-------------------------------------------------
{
"rules": {
"widget": {
// a widget can have a title or color attribute
"title": { ".validate": true },
"color": { ".validate": true },
// but no other child paths are allowed
// in this case, $other means any key excluding "title" and "color"
"$other": { ".validate": false }
}
-------------------------------------------------
▼ 認証時に取得した値を使って ユーザーデータ保護
-------------------------------------------------
auth 変数// 認証時に下記情報が取得される
provider: password, facebook, github などなど
uid:
token:
-------------------------------------------------
{
"rules": {
"users": {
"$user_id": {
// grants write access to the owner of this user account
// whose uid must exactly match the key ($user_id)
".write": "$user_id === auth.uid"
}
}
}
}
-------------------------------------------------
子がすべてのユーザーの uid 値である単一の users ノードにすべてのユーザーを保存して、
ログインユーザーのみが自分のデータを参照できるようにする
-------------------------------------------------
{
"rules": {
"users": {
"$uid": {
".read": "auth != null && auth.uid == $uid"
}
}
-------------------------------------------------
▼ 複雑なルール TODO:下記パートですべてのルールをまとめたい
-------------------------------------------------
{
"rules": {
"room_names": {
// ログインしていれば、room_names は誰でも読める
".read": "auth !== null",
"$room_id": {
// this is just for documenting the structure of rooms, since
// they are read-only and no write rule allows this to be set
".validate": "newData.isString()"
}
},
"members": {
// I can join or leave any room (otherwise it would be a boring demo)
// I can have a different name in each room just for fun
"$room_id": {
// any member can read the list of member names
".read": "data.child(auth.uid).exists()",
// room must already exist to add a member
".validate": "root.child('room_names/'+$room_id).exists()",
"$user_id": {
".write": "auth.uid === $user_id",
".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 20"
}
}
},
"messages": {
"$room_id": {
// the list of messages for a room can be read by any member
".read": "root.child('members/'+$room_id+'/'+auth.uid).exists()",
// room we want to write a message to must be valid
".validate": "root.child('room_names/'+$room_id).exists()",
"$message_id": {
// a new message can be created if it does not exist, but it
// cannot be modified or deleted
// any member of a room can write a new message
".write": "root.child('members/'+$room_id+'/'+auth.uid).exists() && !data.exists() && newData.exists()",
// the room attribute must be a valid key in room_names/ (the room must exist)
// the object to write must have a name, message, and timestamp
".validate": "newData.hasChildren(['user', 'message', 'timestamp'])",
// the message must be written by logged in user
"user": {
".validate": "newData.val() === auth.uid"
},
// the message must be longer than 0 chars and less than 50
"message": { ".validate": "newData.isString() && newData.val().length > 0 && newData.val().length < 50" },
// messages cannot be added in the past or the future
// clients should use firebase.database.ServerValue.TIMESTAMP
// to ensure accurate timestamps
"timestamp": { ".validate": "newData.val() <= now" },
// no other fields can be included in a message
"$other": { ".validate": false }
}
}
}
}
}
-------------------------------------------------
.validate
▼ プロパティ の型や長さなど制限を設ける// カスケードはされない
// .write ルールでアクセス権の付与に成功した後にのみ実行
-------------------------------------------------
{
"rules": {
"foo": {
".validate": "newData.isString() && newData.val().length < 100"
}
}
}
-------------------------------------------------
その他の例
"newData.isString() &&
newData.val().matches(/^(19|20)[0-9][0-9][-\\/. ](0[1-9]|1[012])[-\\/. ](0[1-9]|[12][0-9]|3[01])$/)"
-------------------------------------------------
{
"rules": {
// write is allowed for all paths
".write": true,
"widget": {
// a valid widget must have attributes "color" and "size"
// allows deleting widgets (since .validate is not applied to delete rules)
".validate": "newData.hasChildren(['color', 'size'])",
"size": {
// the value of "size" must be a number between 0 and 99
".validate": "newData.isNumber() &&
newData.val() >= 0 &&
newData.val() <= 99"
},
"color": {
// the value of "color" must exist as a key in our mythical
// /valid_colors/ index
".validate": "root.child('valid_colors/' + newData.val()).exists()"
}
}
}
}
-------------------------------------------------
// Java における書き込み 成否例
FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("widget");
// PERMISSION_DENIED: does not have children color and size
ref.setValue("foo");
// PERMISSION DENIED: does not have child color
ref.child("size").setValue(22);
// PERMISSION_DENIED: size is not a number
Map<String,Object> map = new HashMap<String, Object>();
map.put("size","foo");
map.put("color","red");
ref.setValue(map);
// SUCCESS (assuming 'blue' appears in our colors list)
map = new HashMap<String, Object>();
map.put("size", 21);
map.put("color","blue");
ref.setValue(map);
// If the record already exists and has a color, this will
// succeed, otherwise it will fail since newData.hasChildren(['color', 'size'])
// will fail to validate
ref.child("size").setValue(99);
-------------------------------------------------
例その2
-------------------------------------------------
{
"rules": {
// this variant will NOT allow deleting records (since .write would be disallowed)
"widget": {
// a widget must have 'color' and 'size' in order to be written to this path
".write": "newData.hasChildren(['color', 'size'])",
"size": {
// the value of "size" must be a number between 0 and 99, ONLY IF WE WRITE DIRECTLY TO SIZE
".write": "newData.isNumber() && newData.val() >= 0 && newData.val() <= 99"
},
"color": {
// the value of "color" must exist as a key in our mythical valid_colors/ index
// BUT ONLY IF WE WRITE DIRECTLY TO COLOR
".write": "root.child('valid_colors/'+newData.val()).exists()"
}
}
}
}
-------------------------------------------------
Firebase ref = new Firebase(URL + "/widget");
// ALLOWED? Even though size is invalid, widget has children color and size,
// so write is allowed and the .write rule under color is ignored
Map<String,Object> map = new HashMap<String, Object>();
map.put("size", 99999);
map.put("color", "red");
ref.setValue(map);
// ALLOWED? Works even if widget does not exist, allowing us to create a widget
// which is invalid and does not have a valid color.
// (allowed by the write rule under "color")
ref.child("size").setValue(99);
-------------------------------------------------
▼ セキュリティ
▼ A JSON Web Token
-------------------------------------------------
DB と クライアント間 などで、JSON OBJ として情報をやり取りする際の標準セキュア方法。
-------------------------------------------------
プロバイダ: メール+パスワード、Googleサインイン、Facebookログイン
Uid : プロバイダ横断してユニークであることが保証されるID。
-------------------------------------------------
▼ Firebase Auth ID
-------------------------------------------------
a web token inside of the Auth web token!
下記データを含んでいる
email
email_verified →boolean。 メアドが verify されてたら true。
name
sub →Firebase Uid
firebase.identities →対象ユーザーアカウントに紐付いたすべての ID の辞書
firebase.sign_in_provider →対象の Firebase Auth ID token を取得するのに使った サインインプロバイダ
-------------------------------------------------
▼ データのインデックス作成
-------------------------------------------------
開発時にはインデックス作成不要。
配信開始直前でよい。// 配信してから、規模は拡大するから
-------------------------------------------------
アプリの規模が拡大するにつれ、このクエリのパフォーマンスは低下する。
対策:
クエリ実行対象のキーを定義しておけば、サーバにそのキーのインデックスが作成され、パフォーマンスが向上する。
// ノードのキー インデックスは自動的に作成されるため、明示的にインデックスを作成する必要はありません。
-------------------------------------------------
▼ orderByChild を使用したインデックス作成
-------------------------------------------------
- 恐竜DB があるとする
{
"lambeosaurus": {
"height" : 2.1,
"length" : 12.5,
"weight": 5000
},
"stegosaurus": {
"height" : 4,
"length" : 9,
"weight" : 2500
}
}
-------------------------------------------------
{
"rules": {
"dinosaurs": {
".indexOn": ["height", "length"]// 左記 の値によるソートクエリが早くなる
}
}
}
-------------------------------------------------
▼ orderByValue によるインデックス作成
-------------------------------------------------
// 恐竜の戦闘力で構成されるリーダーボードを作成するとする
{
"scores": {
"bruhathkayosaurus" : 55,
"lambeosaurus" : 21,
"linhenykus" : 80,
"pterodactyl" : 93,
"stegosaurus" : 5,
"triceratops" : 22
}
}
-------------------------------------------------
// /scores ノードに .value ルールを追加すると、クエリを最適化できる
{
"rules": {
"scores": {
".indexOn": ".value"
}
}
}
-------------------------------------------------
▼ オフラインでの永続性有効化
-------------------------------------------------
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
-------------------------------------------------