讀取及寫入資料

(選用) 使用 Firebase Emulator 套件設計原型並進行測試

在討論應用程式如何讀取即時資料庫及寫入資料之前, 現在就來介紹一組工具 可用於設計原型及測試即時資料庫 功能:Firebase 模擬器套件。如要透過其他資料 最佳化安全性規則,或是盡量找出 以符合成本效益的方式與後端互動 未部署即時服務,也是不錯的點子

即時資料庫模擬器屬於 Emulator 套件的一部分 可讓應用程式與模擬的資料庫內容和設定互動,如 以及選用的模擬專案資源 (函式、其他資料庫 和安全性規則).emulator_suite_short

使用即時資料庫模擬器只需完成幾個步驟:

  1. 將一行程式碼新增至應用程式的測試設定,即可與模擬器連線。
  2. 從本機專案目錄的根目錄中執行 firebase emulators:start
  3. 使用即時資料庫平台,從應用程式的原型程式碼發出呼叫 繼續使用 SDK,或是使用即時資料庫 REST API。

歡迎查看詳細的即時資料庫和 Cloud Functions 逐步操作說明。建議您也參閱模擬器套件簡介

取得 DatabaseReference

如要從資料庫讀取或寫入資料,您需要使用 DatabaseReference:

DatabaseReference ref = FirebaseDatabase.instance.ref();

寫入資料

本文說明讀取及寫入 Firebase 資料的基本概念。

Firebase 資料會寫入 DatabaseReference,並由以下方法擷取: 等待或監聽參照產生的事件。發出事件 也就是資料的初始狀態,並在資料變更時再次執行

基本寫入作業

針對基本寫入作業,您可以使用 set() 將資料儲存至指定 取代該路徑中的任何現有資料。您可以設定一個參考檔案 直至下列類型:StringbooleanintdoubleMapList

舉例來說,您可以新增包含 set() 的使用者,如下所示:

DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");

await ref.set({
  "name": "John",
  "age": 18,
  "address": {
    "line1": "100 Mountain View"
  }
});

以這種方式使用 set() 會覆寫指定位置的資料, 包括任何子節點不過,您仍可在沒有權限的情況下更新孩子 重寫整個物件如要允許使用者更新個人資料 您可以按照下列方式更新使用者名稱:

DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");

// Only update the name, leave the age and address!
await ref.update({
  "age": 19,
});

update() 方法接受節點的子路徑,可讓您更新多個節點 一次建立資料庫節點:

DatabaseReference ref = FirebaseDatabase.instance.ref("users");

await ref.update({
  "123/age": 19,
  "123/address/line1": "1 Mountain View",
});

讀取資料

監聽價值事件來讀取資料

如要在路徑中讀取資料及監聽變更,請使用 要監聽的 onValue 屬性 (共 DatabaseReference) DatabaseEvent 秒。

您可以使用 DatabaseEvent 讀取指定路徑的資料。 與活動當下一樣。這個事件會在 並在每次資料 (包括任何子項) 時 並輸入變更內容這個事件有一個 snapshot 屬性,其中包含該事件的所有資料 包括兒童資料如果沒有資料,則快照的 exists 屬性為 false,其 value 屬性為空值。

下列範例示範社交網誌應用程式擷取 資料庫中貼文的詳細資料:

DatabaseReference starCountRef =
        FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
    final data = event.snapshot.value;
    updateStarCount(data);
});

事件監聽器收到含有指定指定位置資料的 DataSnapshot 位置是在事件發生時位於資料庫的 value 屬性中。

讀取資料一次

使用 get() 讀取一次

SDK 的設計用意是管理與資料庫伺服器的互動 應用程式處於連線狀態或離線。

一般而言,您應使用上述的價值事件技術,以便讀取 接收資料更新通知,以便接收來自後端的資料更新通知。這些技術 減少用量與帳單,並經過最佳化調整 都能更輕鬆有效率地完成工作

如果只需要資料一次,可以使用 get() 取得 儲存資料庫資料如果因任何原因導致 get() 無法傳回 伺服器值時,用戶端會探測本機儲存空間快取,並傳回錯誤 。

以下範例說明如何擷取公開的使用者名稱 只需刪除資料庫一次:

final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
    print(snapshot.value);
} else {
    print('No data available.');
}

不必要的使用 get() 可能會增加頻寬用量,導致損失 如果使用即時事件監聽器可以避免最佳化,如圖所示 。

使用 one() 讀取資料一次

在某些情況下,您可能會希望傳回本機快取中的值 ,而不是在伺服器上檢查更新的值。在這些情境下 在這類情況下,您可以使用 once() 從本機磁碟快取取得資料 立即生效

這項功能適用於只需載入一次且預期會執行 經常調整或需要主動聆聽舉例來說,網誌應用程式 都採用這個方法,在使用者選擇載入個人資料時, 開始撰寫新文章:

final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';

更新或刪除資料

更新特定欄位

於不覆寫其他節點的情況下,同時寫入節點的特定子項 子節點,請使用 update() 方法。

呼叫 update() 時,您可以透過 指定金鑰的路徑如果資料儲存在多個位置,以便進行擴充 您可以使用 kubectl 指令 資料擴散傳遞。舉例來說 社交網誌應用程式可用來建立文章,並同時更新到 最近的活動動態消息和發布使用者的活動動態消息。如要這麼做, 網誌應用程式使用以下程式碼:

void writeNewPost(String uid, String username, String picture, String title,
        String body) async {
    // A post entry.
    final postData = {
        'author': username,
        'uid': uid,
        'body': body,
        'title': title,
        'starCount': 0,
        'authorPic': picture,
    };

    // Get a key for a new Post.
    final newPostKey =
        FirebaseDatabase.instance.ref().child('posts').push().key;

    // Write the new post's data simultaneously in the posts list and the
    // user's post list.
    final Map<String, Map> updates = {};
    updates['/posts/$newPostKey'] = postData;
    updates['/user-posts/$uid/$newPostKey'] = postData;

    return FirebaseDatabase.instance.ref().update(updates);
}

本例使用 push() 在包含貼文的節點中張貼文章 位於 /posts/$postid 的所有使用者,並同時擷取 key。接著,該鍵就會在使用者的 在 /user-posts/$userid/$postid 張貼的訊息。

您可以使用這些路徑,同時更新位於 透過單一呼叫 update() 的 JSON 樹狀結構 (如本範例所示) 就會在兩個位置建立新貼文以這種方式同時進行更新 不可分割:所有更新成功或所有更新都失敗

新增完成回呼

如要瞭解資料修訂時間,可以註冊 完成回呼。set()update() 都會傳回 Future,也就是 您可附加在寫入含有 已提交至資料庫以及呼叫失敗時。

FirebaseDatabase.instance
    .ref('users/$userId/email')
    .set(emailAddress)
    .then((_) {
        // Data saved successfully!
    })
    .catchError((error) {
        // The write failed...
    });

刪除資料

刪除資料最簡單的方法是對對物件的參照呼叫 remove() 這些資料的位置

您也可以指定空值做為另一項寫入作業的值來刪除 例如 set()update()您可以將這項技巧與 update() 搭配使用, 在單一 API 呼叫中刪除多個子項。

將資料儲存為交易

在處理可能由並行修改損毀的資料時, 例如漸進式計數器,您可以傳送 交易處理常式至 runTransaction()。交易處理常式 資料目前狀態做為引數 會傳回要寫入的新所需狀態如果其他客戶 會先寫入位置,等到成功寫入新值之前, 系統會使用新的目前值再次呼叫 update 函式,然後寫入 再重試一次。

以社交網誌應用程式為例,您可以讓使用者加上星號 也可以移除貼文的星號,並追蹤貼文獲得的星星數量,方法如下:

void toggleStar(String uid) async {
  DatabaseReference postRef =
      FirebaseDatabase.instance.ref("posts/foo-bar-123");

  TransactionResult result = await postRef.runTransaction((Object? post) {
    // Ensure a post at the ref exists.
    if (post == null) {
      return Transaction.abort();
    }

    Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
    if (_post["stars"] is Map && _post["stars"][uid] != null) {
      _post["starCount"] = (_post["starCount"] ?? 1) - 1;
      _post["stars"][uid] = null;
    } else {
      _post["starCount"] = (_post["starCount"] ?? 0) + 1;
      if (!_post.containsKey("stars")) {
        _post["stars"] = {};
      }
      _post["stars"][uid] = true;
    }

    // Return the new data.
    return Transaction.success(_post);
  });
}

根據預設,每次執行交易更新函式時,都會引發事件。 多次執行這個函式,可能會看到中間狀態。 您可以將 applyLocally 設為 false 隱藏這些中繼狀態, 而是等到交易完成後再引發事件:

await ref.runTransaction((Object? post) {
  // ...
}, applyLocally: false);

交易的結果是 TransactionResult,其中包含 例如交易是否已修訂,以及新的快照:

DatabaseReference ref = FirebaseDatabase.instance.ref("posts/123");

TransactionResult result = await ref.runTransaction((Object? post) {
  // ...
});

print('Committed? ${result.committed}'); // true / false
print('Snapshot? ${result.snapshot}'); // DataSnapshot

取消交易

如要安全地取消交易,請呼叫 Transaction.abort() 以 擲回 AbortTransactionException

TransactionResult result = await ref.runTransaction((Object? user) {
  if (user !== null) {
    return Transaction.abort();
  }

  // ...
});

print(result.committed); // false

整體伺服器端增量

在上述用途中,我們要將兩個值寫入資料庫: 使用者為貼文加上星號/移除星號,以及逐漸增加的星號數量。如果我們 就可以知道使用者已為貼文加上星號 而不是交易作業

void addStar(uid, key) async {
  Map<String, Object?> updates = {};
  updates["posts/$key/stars/$uid"] = true;
  updates["posts/$key/starCount"] = ServerValue.increment(1);
  updates["user-posts/$key/stars/$uid"] = true;
  updates["user-posts/$key/starCount"] = ServerValue.increment(1);
  return FirebaseDatabase.instance.ref().update(updates);
}

此程式碼不會使用交易作業,因此不會自動取得 如果有衝突的更新,則重新執行。不過,由於遞增作業 這並不會發生衝突。

如要偵測並拒絕應用程式特定衝突 (例如使用者) 為先前加上星號的訊息加上星號,請自訂 應用情境

離線使用資料

如果用戶端的網路連線中斷,您的應用程式會繼續運作 正確。

連接至 Firebase 資料庫的所有用戶端都會保有自己的內部版本 任何有效資料。寫入資料時,會寫入這個本機版本 首先。接著 Firebase 用戶端會將這些資料與遠端資料庫同步處理 並與其他用戶端共用。

因此,所有寫入資料庫的動作都會立即觸發本機事件, 任何資料都會寫入伺服器也就是說,您的應用程式 回應,無論網路延遲或連線。

連線恢復後,應用程式會收到一組適當的 以便用戶端與目前的伺服器狀態同步, 即可撰寫任何自訂程式碼

我們將在下列單元中進一步說明離線行為: 進一步瞭解線上和離線功能

後續步驟