始めましょう
アプリとデータベースへのアクセスをまだ設定していない場合は、まずGet Started
にガイドを参照してください。
DatabaseReference を取得する
データベースにデータを書き込むには、 DatabaseReference
のインスタンスが必要です。
// Get the root reference location of the database. firebase::database::DatabaseReference dbref = database->GetReference();
データの保存
Firebase Realtime Database にデータを書き込むには、次の 4 つの方法があります。
方法 | 一般的な用途 |
---|---|
SetValue() | users/<user-id>/<username> などの定義済みパスにデータを書き込むか、または置き換えます。 |
PushChild() | データのリストに追加します。 Push() を呼び出すたびに、Firebase はuser-scores/<user-id>/<unique-score-id> などの一意の識別子としても使用できる一意のキーを生成します。 |
UpdateChildren() | すべてのデータを置き換えることなく、定義されたパスのいくつかのキーを更新します。 |
RunTransaction() | 同時更新によって破損する可能性がある複雑なデータを更新します。 |
参照でのデータの書き込み、更新、または削除
基本的な書き込み操作
基本的な書き込み操作では、 SetValue()
を使用して指定した参照にデータを保存し、そのパスにある既存のデータを置き換えることができます。このメソッドを使用して、次をサポートする Variant 型を介して JSON で受け入れられる型を渡すことができます。
- Null (これによりデータが削除されます)
- 整数 (64 ビット)
- 倍精度浮動小数点数
- ブール値
- ストリングス
- バリアントのベクトル
- バリアントへの文字列のマップ
このようにSetValue()
を使用すると、子ノードを含め、指定された場所のデータが上書きされます。ただし、オブジェクト全体を書き換えなくても子を更新できます。ユーザーがプロファイルを更新できるようにしたい場合は、次のようにユーザー名を更新できます。
dbref.Child("users").Child(userId).Child("username").SetValue(name);
データのリストに追加
マルチユーザー アプリケーションのリストにデータを追加するには、 PushChild()
メソッドを使用します。 PushChild()
メソッドは、指定された Firebase 参照に新しい子が追加されるたびに一意のキーを生成します。リスト内の新しい要素ごとにこれらの自動生成されたキーを使用することにより、複数のクライアントが、書き込みの競合なしで同時に同じ場所に子を追加できます。 PushChild()
によって生成される一意のキーはタイムスタンプに基づいているため、リスト項目は自動的に時系列順に並べられます。
PushChild()
メソッドによって返された新しいデータへの参照を使用して、子の自動生成キーの値を取得したり、子のデータを設定したりできます。 PushChild()
参照で GetKey( GetKey()
を呼び出すと、自動生成されたキーの値が返されます。
特定のフィールドを更新する
他の子ノードを上書きせずにノードの特定の子に同時に書き込むには、 UpdateChildren()
メソッドを使用します。
UpdateChildren()
を呼び出すときに、キーのパスを指定することで、下位レベルの子の値を更新できます。スケーリングを改善するためにデータが複数の場所に保存されている場合は、データ ファンアウトを使用してそのデータのすべてのインスタンスを更新できます。たとえば、ゲームには次のようなLeaderboardEntry
クラスがあるとします。
class LeaderboardEntry { std::string uid; int score = 0; public: LeaderboardEntry() { } LeaderboardEntry(std::string uid, int score) { this->uid = uid; this->score = score; } std::map<std::string, Object> ToMap() { std::map<string, Variant> result = new std::map<string, Variant>(); result["uid"] = Variant(uid); result["score"] = Variant(score); return result; } }
LeaderboardEntry
を作成し、それを最近のスコア フィードとユーザー自身のスコア リストに同時に更新するために、ゲームは次のコードを使用します。
void WriteNewScore(std::string userId, int score) { // Create new entry at /user-scores/$userid/$scoreid and at // /leaderboard/$scoreid simultaneously std::string key = dbref.Child("scores").PushChild().GetKey(); LeaderBoardEntry entry = new LeaderBoardEntry(userId, score); std::map<std::string, Variant> entryValues = entry.ToMap(); std::map<string, Variant> childUpdates = new std::map<string, Variant>(); childUpdates["/scores/" + key] = entryValues; childUpdates["/user-scores/" + userId + "/" + key] = entryValues; dbref.UpdateChildren(childUpdates); }
この例では、 PushChild()
を使用して、 /scores/$key
にあるすべてのユーザーのエントリを含むノードにエントリを作成し、同時にkey()
でキーを取得します。このキーを使用して、 /user-scores/$userid/$key
でユーザーのスコアに 2 番目のエントリを作成できます。
これらのパスを使用すると、この例で両方の場所に新しいエントリを作成する方法のように、 UpdateChildren()
を 1 回呼び出すだけで、JSON ツリー内の複数の場所に対して同時に更新を実行できます。この方法で行われる同時更新はアトミックです。すべての更新が成功するか、すべての更新が失敗します。
データを削除する
データを削除する最も簡単な方法は、そのデータの場所への参照に対してRemoveValue()
を呼び出すことです。
SetValue()
やUpdateChildren()
) などの別の書き込み操作の値としてnull
Variant
を指定して削除することもできます。この手法をUpdateChildren()
で使用して、1 回の API 呼び出しで複数の子を削除できます。
データがいつコミットされるかを把握します。
データが Firebase Realtime Database サーバーにいつコミットされるかを知るには、 Futureの結果で成功を確認します。
データをトランザクションとして保存
増分カウンターなど、同時変更によって破損する可能性があるデータを操作する場合は、トランザクション操作を使用できます。この操作にDoTransaction
関数を指定します。この更新関数は、データの現在の状態を引数として取り、書き込みたい新しい目的の状態を返します。新しい値が正常に書き込まれる前に別のクライアントがその場所に書き込むと、更新関数が新しい現在の値で再度呼び出され、書き込みが再試行されます。
たとえば、ゲームでは、ユーザーが 5 つの最高スコアでリーダーボードを更新できるようにすることができます。
void AddScoreToLeaders(std::string email, long score, DatabaseReference leaderBoardRef) { leaderBoardRef.RunTransaction([](firebase::database::MutableData* mutableData) { if (mutableData.children_count() >= MaxScores) { long minScore = LONG_MAX; MutableData *minVal = null; std::vector<MutableData> children = mutableData.children(); std::vector<MutableData>::iterator it; for (it = children.begin(); it != children.end(); ++it) { if (!it->value().is_map()) continue; long childScore = (long)it->Child("score").value().int64_value(); if (childScore < minScore) { minScore = childScore; minVal = &*it; } } if (minScore > score) { // The new score is lower than the existing 5 scores, abort. return kTransactionResultAbort; } // Remove the lowest score. children.Remove(minVal); } // Add the new high score. std::map<std::string, Variant> newScoreMap = new std::map<std::string, Variant>(); newScoreMap["score"] = score; newScoreMap["email"] = email; children.Add(newScoreMap); mutableData->set_value(children); return kTransactionResultSuccess; }); }
トランザクションを使用すると、複数のユーザーが同時にスコアを記録したり、クライアントのデータが古い場合にリーダーボードが不正確になるのを防ぐことができます。トランザクションが拒否された場合、サーバーは現在の値をクライアントに返し、クライアントは更新された値でトランザクションを再度実行します。これは、トランザクションが受け入れられるか、試行回数が多すぎるまで繰り返されます。
オフラインでデータを書き込む
クライアントがネットワーク接続を失った場合でも、アプリは正常に機能し続けます。
Firebase データベースに接続されているすべてのクライアントは、アクティブなデータの独自の内部バージョンを維持します。データが書き込まれると、最初にこのローカル バージョンに書き込まれます。次に、Firebase クライアントは、そのデータをリモート データベース サーバーおよび他のクライアントと「ベスト エフォート」ベースで同期します。
その結果、データベースへのすべての書き込みは、データがサーバーに書き込まれる前に、すぐにローカル イベントをトリガーします。これは、ネットワークの遅延や接続に関係なく、アプリの応答性が維持されることを意味します。
接続が再確立されると、アプリは適切な一連のイベントを受け取り、クライアントが現在のサーバーの状態と同期するようにします。カスタム コードを記述する必要はありません。