啟用離線功能

即使應用程式暫時失去網路連線,Firebase 應用程式仍可運作。此外,Firebase 也提供工具,可讓您在本機保存資料、管理狀態及處理延遲問題。

磁碟持續性

Firebase 應用程式會自動處理暫時性的網路中斷問題。 離線時可使用快取資料,網路連線恢復後,Firebase 會重新傳送所有寫入作業。

啟用磁碟持續性後,應用程式會將資料寫入裝置本機,因此即使使用者或作業系統重新啟動應用程式,應用程式也能在離線時維持狀態。

您只需一行程式碼,即可啟用磁碟持續性。

FirebaseDatabase.instance.setPersistenceEnabled(true);

持續性行為

啟用持續性後,Firebase 即時資料庫用戶端在連線時同步處理的任何資料都會保留在磁碟中,並可在離線時使用,即使使用者或作業系統重新啟動應用程式也一樣。這表示應用程式會使用儲存在快取中的本機資料,因此運作方式與連線時相同。系統仍會針對本機更新觸發接聽程式回呼。

應用程式離線時,Firebase 即時資料庫用戶端會自動將所有寫入作業排入佇列。啟用持續性後,這個佇列也會持續存在磁碟中,因此當使用者或作業系統重新啟動應用程式時,所有寫入作業都會繼續執行。應用程式恢復連線後,所有作業都會傳送至 Firebase 即時資料庫伺服器。

如果應用程式使用 Firebase 驗證,Firebase 即時資料庫用戶端會在應用程式重新啟動時,持續保留使用者的驗證權杖。如果應用程式離線時驗證權杖過期,用戶端會暫停寫入作業,直到應用程式重新驗證使用者為止,否則寫入作業可能會因安全性規則而失敗。

保持資料最新狀態

Firebase 即時資料庫會同步處理並儲存有效監聽器的資料本機副本。此外,你也可以同步處理特定位置。

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.keepSynced(true);

Firebase 即時資料庫用戶端會自動下載這些位置的資料,即使參照沒有任何有效監聽器,也會保持資料同步。您可以使用下列程式碼行,重新關閉同步功能。

scoresRef.keepSynced(false);

根據預設,系統會快取 10 MB 的先前同步資料。這應該足以應付大多數應用程式。如果快取超出設定大小,Firebase 即時資料庫會清除最近最少使用的資料。系統不會清除快取中保持同步的資料。

離線查詢資料

Firebase 即時資料庫會儲存查詢傳回的資料,供離線時使用。如果是離線時建構的查詢,Firebase 即時資料庫會繼續處理先前載入的資料。如果要求的資料尚未載入,Firebase 即時資料庫會從本機快取載入資料。網路連線恢復後,系統就會載入資料並反映查詢結果。

舉例來說,這段程式碼會查詢分數資料庫中的最後四個項目:

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.orderByValue().limitToLast(4).onChildAdded.listen((event) {
  debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});

假設使用者失去連線、離線並重新啟動應用程式。應用程式在離線狀態下,會從相同位置查詢最後兩項商品。由於應用程式已載入上述查詢中的所有四個項目,因此這項查詢會成功傳回最後兩個項目。

scoresRef.orderByValue().limitToLast(2).onChildAdded.listen((event) {
  debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});

在上例中,Firebase 即時資料庫用戶端會使用持續性快取,針對得分最高的兩隻恐龍引發「已新增子項」事件。但由於應用程式從未在連線時執行該查詢,因此不會觸發「value」事件。

如果應用程式在離線時要求最後六個項目,會立即取得四個已快取項目的「已新增子項」事件。裝置恢復連線後,Firebase 即時資料庫用戶端會與伺服器同步,並取得應用程式的最後兩個「已新增子項」和「值」事件。

處理離線交易

應用程式離線時執行的任何交易都會加入佇列。 應用程式重新連上網路後,交易就會傳送至即時資料庫伺服器。

Firebase 即時資料庫提供多項功能,可處理離線情境和網路連線問題。無論您是否已啟用持續性,本指南的其餘部分都適用於您的應用程式。

管理在宅感應設定

在即時應用程式中,偵測用戶端連線和中斷連線的時間通常很有用。舉例來說,您可能會想在用戶端中斷連線時,將使用者標示為「離線」。

Firebase 資料庫用戶端提供簡單的基元,您可以在用戶端與 Firebase 資料庫伺服器中斷連線時,使用這些基元寫入資料庫。無論用戶端是否正常中斷連線,都會發生這些更新,因此即使連線中斷或用戶端當機,您也可以依賴這些更新來清理資料。中斷連線後,您仍可執行所有寫入作業,包括設定、更新及移除。

以下是使用 onDisconnect 原始項目,在連線中斷時寫入資料的簡單範例:

final presenceRef = FirebaseDatabase.instance.ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");

onDisconnect 的運作方式

建立 onDisconnect() 作業時,該作業會存在於 Firebase 即時資料庫伺服器上。伺服器會檢查安全性,確保使用者可以執行要求的寫入事件,並在無效時通知應用程式。伺服器隨後會監控連線。如果連線在任何時間點逾時,或由 Firebase 即時資料庫用戶端主動關閉,伺服器會再次檢查安全性 (確保作業仍有效),然後叫用事件。

try {
    await presenceRef.onDisconnect().remove();
} catch (error) {
    debugPrint("Could not establish onDisconnect event: $error");
}

您也可以呼叫 .cancel() 取消 onDisconnect 事件:

final onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();

偵測連線狀態

對於許多狀態相關功能,應用程式瞭解自己是否連線很有幫助。Firebase 即時資料庫會在 /.info/connected 提供特殊位置,每當 Firebase 即時資料庫用戶端的連線狀態變更時,這個位置就會更新。範例如下:

final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
  final connected = event.snapshot.value as bool? ?? false;
  if (connected) {
    debugPrint("Connected.");
  } else {
    debugPrint("Not connected.");
  }
});

/.info/connected 是布林值,不會在即時資料庫用戶端之間同步處理,因為該值取決於用戶端狀態。換句話說,如果某個用戶端將 /.info/connected 讀取為 false,這不保證另一個用戶端也會讀取為 false。

處理延遲

伺服器時間戳記

Firebase 即時資料庫伺服器提供機制,可將伺服器產生的時間戳記插入為資料。這項功能搭配 onDisconnect,可讓您輕鬆可靠地記錄即時資料庫用戶端中斷連線的時間:

final userLastOnlineRef =
    FirebaseDatabase.instance.ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(ServerValue.timestamp);

時鐘偏差

雖然 ServerValue.timestamp 的準確度高出許多,且適用於大多數讀取/寫入作業,但有時估算用戶端時鐘與 Firebase 即時資料庫伺服器之間的時鐘偏差,也可能很有用。您可以將回呼附加至 /.info/serverTimeOffset 位置,取得 Firebase 即時資料庫用戶端加到本機回報時間 (以毫秒為單位的紀元時間) 的值,藉此估算伺服器時間。請注意,網路延遲可能會影響這個偏移量的準確度,因此主要用於發現時鐘時間的大幅差異 (大於 1 秒)。

final offsetRef = FirebaseDatabase.instance.ref(".info/serverTimeOffset");
offsetRef.onValue.listen((event) {
  final offset = event.snapshot.value as num? ?? 0.0;
  final estimatedServerTimeMs =
      DateTime.now().millisecondsSinceEpoch + offset;
});

範例 Presence 應用程式

結合中斷連線作業、連線狀態監控和伺服器時間戳記,即可建構使用者在線狀態系統。在這個系統中,每位使用者都會在資料庫位置儲存資料,指出 Realtime Database 用戶端是否處於連線狀態。用戶端上線時會將這個位置設為 true,並在連線中斷時加上時間戳記。這個時間戳記表示指定使用者上次上線的時間。

請注意,應用程式應在使用者標示為上線前,將中斷連線作業加入佇列,以免在兩個指令都能傳送至伺服器前,用戶端的網路連線中斷,導致任何競爭狀況。

// Since I can connect from multiple devices, we store each connection
// instance separately any time that connectionsRef's value is null (i.e.
// has no children) I am offline.
final myConnectionsRef =
    FirebaseDatabase.instance.ref("users/joe/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final lastOnlineRef =
    FirebaseDatabase.instance.ref("/users/joe/lastOnline");

final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
  final connected = event.snapshot.value as bool? ?? false;
  if (connected) {
    final con = myConnectionsRef.push();

    // When this device disconnects, remove it.
    con.onDisconnect().remove();

    // When I disconnect, update the last time I was seen online.
    lastOnlineRef.onDisconnect().set(ServerValue.timestamp);

    // Add this device to my connections list.
    // This value could contain info about the device or a timestamp too.
    con.set(true);
  }
});