(省略可)Firebase Local Emulator Suite でプロトタイプを作成してテストする
アプリが Realtime Database に対して読み取りと書き込みを行う方法について説明する前に、Realtime Database 機能のプロトタイプ作成とテストに使用できる一連のツール、Firebase Local Emulator Suite を紹介しましょう。さまざまなデータ モデルを試したり、セキュリティ ルールを最適化したり、バックエンドと対話するための最も費用対効果の高い方法を見つけようとしている場合、ライブ サービスをデプロイせずにローカルで作業できることは素晴らしいアイデアです。
Realtime Database エミュレーターは、ローカル エミュレーター スイートの一部であり、アプリがエミュレートされたデータベースのコンテンツと構成、および必要に応じてエミュレートされたプロジェクト リソース (関数、他のデータベース、およびセキュリティ ルール) とやり取りできるようにします。
Realtime Database エミュレーターを使用するには、いくつかの手順を実行する必要があります。
- アプリのテスト構成にコード行を追加して、エミュレーターに接続します。
- ローカル プロジェクト ディレクトリのルートから、
firebase emulators:start
を実行します。 - 通常どおり Realtime Database プラットフォーム SDK を使用するか、Realtime Database REST API を使用して、アプリのプロトタイプ コードから呼び出しを行います。
Realtime Database と Cloud Functions に関する詳細なチュートリアルが利用可能です。 Local Emulator Suite Introductionも参照してください。
データベース参照を取得する
データベースからデータを読み書きするには、 firebase.database.Reference
のインスタンスが必要です。
Web version 9
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web version 8
var database = firebase.database();
書き込みデータ
このドキュメントでは、データ取得の基本と、Firebase データの順序付けとフィルタリングの方法について説明します。
Firebase データは、非同期リスナーをfirebase.database.Reference
にアタッチすることによって取得されます。リスナーは、データの初期状態に対して 1 回トリガーされ、データが変更されるたびに再度トリガーされます。
基本的な書き込み操作
基本的な書き込み操作では、 set()
を使用して指定した参照にデータを保存し、そのパスにある既存のデータを置き換えることができます。たとえば、ソーシャル ブログ アプリケーションでは、次のようにset()
を使用してユーザーを追加できます。
Web version 9
import { getDatabase, ref, set } from "firebase/database"; function writeUserData(userId, name, email, imageUrl) { const db = getDatabase(); set(ref(db, 'users/' + userId), { username: name, email: email, profile_picture : imageUrl }); }
Web version 8
function writeUserData(userId, name, email, imageUrl) { firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }); }
set()
を使用すると、子ノードを含め、指定された場所のデータが上書きされます。
データの読み取り
値イベントをリッスンする
パスでデータを読み取り、変更をリッスンするには、 onValue()
を使用してイベントを観察します。このイベントを使用して、イベントの時点で存在していた、特定のパスにあるコンテンツの静的スナップショットを読み取ることができます。このメソッドは、リスナーがアタッチされたときに 1 回トリガーされ、子を含むデータが変更されるたびに再度トリガーされます。イベント コールバックには、子データを含む、その場所にあるすべてのデータを含むスナップショットが渡されます。データがない場合、 exists()
を呼び出すとスナップショットはfalse
を返し、 val()
を呼び出すとnull
を返します。
次の例は、データベースから投稿の星の数を取得するソーシャル ブログ アプリケーションを示しています。
Web version 9
import { getDatabase, ref, onValue} from "firebase/database"; const db = getDatabase(); const starCountRef = ref(db, 'posts/' + postId + '/starCount'); onValue(starCountRef, (snapshot) => { const data = snapshot.val(); updateStarCount(postElement, data); });
Web version 8
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount'); starCountRef.on('value', (snapshot) => { const data = snapshot.val(); updateStarCount(postElement, data); });
リスナーは、イベント時にデータベース内の指定された場所にあるデータを含むsnapshot
を受け取ります。 val()
メソッドを使用して、 snapshot
内のデータを取得できます。
データを 1 回読み取る
get() でデータを 1 回読み取る
SDK は、アプリがオンラインかオフラインかに関係なく、データベース サーバーとの対話を管理するように設計されています。
一般に、上記の値イベント手法を使用してデータを読み取り、バックエンドからデータへの更新の通知を受け取る必要があります。リスナーの手法により、使用量と請求額が削減され、オンラインとオフラインの両方でユーザーに最高のエクスペリエンスを提供するように最適化されます。
データが一度だけ必要な場合は、 get()
を使用してデータベースからデータのスナップショットを取得できます。何らかの理由でget()
がサーバーの値を返すことができない場合、クライアントはローカル ストレージ キャッシュを調べ、それでも値が見つからない場合はエラーを返します。
get()
を不必要に使用すると、帯域幅の使用が増加し、パフォーマンスが低下する可能性があります。これは、上記のようにリアルタイム リスナーを使用することで防ぐことができます。
Web version 9
import { getDatabase, ref, child, get } from "firebase/database"; const dbRef = ref(getDatabase()); get(child(dbRef, `users/${userId}`)).then((snapshot) => { if (snapshot.exists()) { console.log(snapshot.val()); } else { console.log("No data available"); } }).catch((error) => { console.error(error); });
Web version 8
const dbRef = firebase.database().ref(); dbRef.child("users").child(userId).get().then((snapshot) => { if (snapshot.exists()) { console.log(snapshot.val()); } else { console.log("No data available"); } }).catch((error) => { console.error(error); });
オブザーバーでデータを 1 回読み取る
場合によっては、サーバーで更新された値を確認するのではなく、ローカル キャッシュの値をすぐに返したい場合があります。そのような場合は、 once()
を使用して、ローカル ディスク キャッシュからデータをすぐに取得できます。
これは、一度だけロードする必要があり、頻繁に変更されることやアクティブなリッスンを必要としないことが予想されるデータに役立ちます。たとえば、前の例のブログ アプリでは、ユーザーが新しい投稿の作成を開始するときに、このメソッドを使用してユーザーのプロファイルを読み込みます。
Web version 9
import { getDatabase, ref, onValue } from "firebase/database"; import { getAuth } from "firebase/auth"; const db = getDatabase(); const auth = getAuth(); const userId = auth.currentUser.uid; return onValue(ref(db, '/users/' + userId), (snapshot) => { const username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... }, { onlyOnce: true });
Web version 8
var userId = firebase.auth().currentUser.uid; return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => { var username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... });
データの更新または削除
特定のフィールドを更新する
他の子ノードを上書きせずにノードの特定の子に同時に書き込むには、 update()
メソッドを使用します。
update()
を呼び出すときに、キーのパスを指定することで、下位レベルの子の値を更新できます。スケーリングを改善するためにデータが複数の場所に保存されている場合は、データ ファンアウトを使用してそのデータのすべてのインスタンスを更新できます。
たとえば、ソーシャル ブログ アプリは投稿を作成し、次のようなコードを使用して、最近のアクティビティ フィードと投稿ユーザーのアクティビティ フィードを同時に更新する場合があります。
Web version 9
import { getDatabase, ref, child, push, update } from "firebase/database"; function writeNewPost(uid, username, picture, title, body) { const db = getDatabase(); // A post entry. const postData = { author: username, uid: uid, body: body, title: title, starCount: 0, authorPic: picture }; // Get a key for a new Post. const newPostKey = push(child(ref(db), 'posts')).key; // Write the new post's data simultaneously in the posts list and the user's post list. const updates = {}; updates['/posts/' + newPostKey] = postData; updates['/user-posts/' + uid + '/' + newPostKey] = postData; return update(ref(db), updates); }
Web version 8
function writeNewPost(uid, username, picture, title, body) { // A post entry. var postData = { author: username, uid: uid, body: body, title: title, starCount: 0, authorPic: picture }; // Get a key for a new Post. var newPostKey = firebase.database().ref().child('posts').push().key; // Write the new post's data simultaneously in the posts list and the user's post list. var updates = {}; updates['/posts/' + newPostKey] = postData; updates['/user-posts/' + uid + '/' + newPostKey] = postData; return firebase.database().ref().update(updates); }
この例では、 push()
を使用して、 /posts/$postid
にあるすべてのユーザーの投稿を含むノードに投稿を作成し、同時にキーを取得します。このキーを使用して、 /user-posts/$userid/$postid
にあるユーザーの投稿に 2 番目のエントリを作成できます。
これらのパスを使用すると、この例で両方の場所に新しい投稿を作成する方法のように、 update()
を 1 回呼び出すだけで、JSON ツリー内の複数の場所に対して同時に更新を実行できます。この方法で行われる同時更新はアトミックです。すべての更新が成功するか、すべての更新が失敗します。
完了コールバックを追加する
データがいつコミットされたかを知りたい場合は、完了コールバックを追加できます。 set()
とupdate()
はどちらも、書き込みがデータベースにコミットされたときに呼び出されるオプションの完了コールバックを受け取ります。呼び出しが失敗した場合、失敗が発生した理由を示すエラー オブジェクトがコールバックに渡されます。
Web version 9
import { getDatabase, ref, set } from "firebase/database"; const db = getDatabase(); set(ref(db, 'users/' + userId), { username: name, email: email, profile_picture : imageUrl }) .then(() => { // Data saved successfully! }) .catch((error) => { // The write failed... });
Web version 8
firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }, (error) => { if (error) { // The write failed... } else { // Data saved successfully! } });
データを削除する
データを削除する最も簡単な方法は、そのデータの場所への参照に対してremove()
を呼び出すことです。
set()
やupdate()
) などの別の書き込み操作の値としてnull
を指定して削除することもできます。この手法をupdate()
で使用して、1 回の API 呼び出しで複数の子を削除できます。
Promise
を受け取る
データが Firebase Realtime Database サーバーにいつコミットされるかを知るには、 Promise
を使用できます。 set()
とupdate()
はどちらも、書き込みがいつデータベースにコミットされたかを知るために使用できるPromise
を返すことができます。
リスナーを切り離す
Firebase データベース参照でoff()
メソッドを呼び出すと、コールバックが削除されます。
パラメータとしてoff()
に渡すことで、単一のリスナーを削除できます。その場所で引数なしでoff()
を呼び出すと、その場所にあるすべてのリスナーが削除されます。
親リスナーでoff()
を呼び出しても、その子ノードに登録されているリスナーは自動的に削除されません。コールバックを削除するには、すべての子リスナーでoff()
も呼び出す必要があります。
データをトランザクションとして保存
増分カウンターなど、同時変更によって破損する可能性があるデータを操作する場合は、トランザクション操作を使用できます。この操作には、更新関数とオプションの完了コールバックを与えることができます。更新関数は、データの現在の状態を引数として取り、書き込みたい新しい望ましい状態を返します。新しい値が正常に書き込まれる前に別のクライアントがその場所に書き込むと、更新関数が新しい現在の値で再度呼び出され、書き込みが再試行されます。
たとえば、ソーシャル ブログ アプリの例では、次のように、ユーザーが投稿にスターを付けたり、スターを外したり、投稿が獲得したスターの数を追跡したりできるようにすることができます。
Web version 9
import { getDatabase, ref, runTransaction } from "firebase/database"; function toggleStar(uid) { const db = getDatabase(); const postRef = ref(db, '/posts/foo-bar-123'); runTransaction(postRef, (post) => { if (post) { if (post.stars && post.stars[uid]) { post.starCount--; post.stars[uid] = null; } else { post.starCount++; if (!post.stars) { post.stars = {}; } post.stars[uid] = true; } } return post; }); }
Web version 8
function toggleStar(postRef, uid) { postRef.transaction((post) => { if (post) { if (post.stars && post.stars[uid]) { post.starCount--; post.stars[uid] = null; } else { post.starCount++; if (!post.stars) { post.stars = {}; } post.stars[uid] = true; } } return post; }); }
トランザクションを使用すると、複数のユーザーが同じ投稿に同時にスターを付けたり、クライアントのデータが古い場合に、スター カウントが正しくなくなるのを防ぐことができます。トランザクションが拒否された場合、サーバーは現在の値をクライアントに返し、クライアントは更新された値でトランザクションを再度実行します。これは、トランザクションが受け入れられるか、トランザクションを中止するまで繰り返されます。
サーバー側のアトミックインクリメント
上記の使用例では、データベースに 2 つの値を書き込んでいます。投稿に星を付けた/星を外したユーザーの ID と、増加した星の数です。ユーザーが投稿にスターを付けていることがすでにわかっている場合は、トランザクションの代わりにアトミック インクリメント操作を使用できます。
function addStar(uid, key) { const updates = {}; updates[`posts/${key}/stars/${uid}`] = true; updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); updates[`user-posts/${key}/stars/${uid}`] = true; updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); firebase.database().ref().update(updates); }
このコードはトランザクション操作を使用しないため、競合する更新がある場合に自動的に再実行されることはありません。ただし、インクリメント操作はデータベース サーバーで直接行われるため、競合が発生する可能性はありません。
ユーザーが以前にスターを付けた投稿にスターを付けるなど、アプリケーション固有の競合を検出して拒否する場合は、そのユース ケース用のカスタム セキュリティ ルールを作成する必要があります。
オフラインでデータを操作する
クライアントがネットワーク接続を失った場合でも、アプリは正常に機能し続けます。
Firebase データベースに接続されているすべてのクライアントは、アクティブなデータの独自の内部バージョンを維持します。データが書き込まれると、最初にこのローカル バージョンに書き込まれます。次に、Firebase クライアントは、そのデータをリモート データベース サーバーおよび他のクライアントと「ベスト エフォート」ベースで同期します。
その結果、データベースへのすべての書き込みは、データがサーバーに書き込まれる前に、すぐにローカル イベントをトリガーします。これは、ネットワークの遅延や接続に関係なく、アプリの応答性が維持されることを意味します。
接続が再確立されると、アプリは適切な一連のイベントを受け取り、クライアントが現在のサーバーの状態と同期するようにします。カスタム コードを記述する必要はありません。
オフラインの動作については、オンラインとオフラインの機能の詳細をご覧ください..