在 Cloud Firestore 中建構產品

視您要建構的應用程式的類型而定,您可能會發現哪些使用者或裝置經常在線上 (又稱為偵測「是否存在」) 會很有幫助。

舉例來說,如果您要建構社群網路等應用程式,或部署一整套 IoT 裝置,可以利用這項資訊顯示線上的好友清單,讓使用者可以與他們即時通訊,或是依據「上次看到」排序 IoT 裝置。

Cloud Firestore 並未原生支援狀態,但您可以利用其他 Firebase 產品建構狀態系統。

解決方案:搭配即時資料庫的 Cloud 函式

如要將 Cloud Firestore 連結至 Firebase 即時資料庫的原生狀態功能,請使用 Cloud Functions。

使用 Realtime Database 回報連線狀態,然後使用 Cloud Functions 將該資料鏡像到 Cloud Firestore

使用即時資料庫中的狀態

首先,請考慮傳統即時狀態系統在即時資料庫中的運作方式。

網路

// Fetch the current user's ID from Firebase Authentication.
var uid = firebase.auth().currentUser.uid;

// Create a reference to this user's specific status node.
// This is where we will store data about being online/offline.
var userStatusDatabaseRef = firebase.database().ref('/status/' + uid);

// We'll create two constants which we will write to 
// the Realtime database when this device is offline
// or online.
var isOfflineForDatabase = {
    state: 'offline',
    last_changed: firebase.database.ServerValue.TIMESTAMP,
};

var isOnlineForDatabase = {
    state: 'online',
    last_changed: firebase.database.ServerValue.TIMESTAMP,
};

// Create a reference to the special '.info/connected' path in 
// Realtime Database. This path returns `true` when connected
// and `false` when disconnected.
firebase.database().ref('.info/connected').on('value', function(snapshot) {
    // If we're not currently connected, don't do anything.
    if (snapshot.val() == false) {
        return;
    };

    // If we are currently connected, then use the 'onDisconnect()' 
    // method to add a set which will only trigger once this 
    // client has disconnected by closing the app, 
    // losing internet, or any other means.
    userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
        // The promise returned from .onDisconnect().set() will
        // resolve as soon as the server acknowledges the onDisconnect() 
        // request, NOT once we've actually disconnected:
        // https://firebase.google.com/docs/reference/js/firebase.database.OnDisconnect

        // We can now safely set ourselves as 'online' knowing that the
        // server will mark us as offline once we lose connection.
        userStatusDatabaseRef.set(isOnlineForDatabase);
    });
});

這個範例是完整的即時資料庫系統。可處理多個斷線和異常終止情形。

正在連線至「Cloud Firestore

如要在 Cloud Firestore 中實作類似的解決方案,請使用相同的即時資料庫程式碼,然後使用 Cloud 函式讓即時資料庫和 Cloud Firestore 保持同步。

如果您尚未將即時資料庫新增至專案,並納入上述業務解決方案,請先完成這項操作。

接下來,您將透過下列方法將狀態同步至 Cloud Firestore

  1. 在本機,將資料寫入離線裝置的 Cloud Firestore 快取,讓應用程式知道自己處於離線狀態。
  2. 在全球範圍內使用 Cloud 函式,讓所有其他存取 Cloud Firestore 的裝置都知道這個特定裝置處於離線狀態。

更新 Cloud Firestore 的本機快取

我們來看看解決第一個問題所需的變更 - 更新 Cloud Firestore 的本機快取。

網路

// ...
var userStatusFirestoreRef = firebase.firestore().doc('/status/' + uid);

// Firestore uses a different server timestamp value, so we'll 
// create two more constants for Firestore state.
var isOfflineForFirestore = {
    state: 'offline',
    last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

var isOnlineForFirestore = {
    state: 'online',
    last_changed: firebase.firestore.FieldValue.serverTimestamp(),
};

firebase.database().ref('.info/connected').on('value', function(snapshot) {
    if (snapshot.val() == false) {
        // Instead of simply returning, we'll also set Firestore's state
        // to 'offline'. This ensures that our Firestore cache is aware
        // of the switch to 'offline.'
        userStatusFirestoreRef.set(isOfflineForFirestore);
        return;
    };

    userStatusDatabaseRef.onDisconnect().set(isOfflineForDatabase).then(function() {
        userStatusDatabaseRef.set(isOnlineForDatabase);

        // We'll also add Firestore set here for when we come online.
        userStatusFirestoreRef.set(isOnlineForFirestore);
    });
});

透過這些變更,我們現在已確保 local Cloud Firestore 狀態一律會反映裝置的線上/離線狀態。也就是說,您可以監聽 /status/{uid} 文件,並使用資料變更 UI,以反映連線狀態。

網路

userStatusFirestoreRef.onSnapshot(function(doc) {
    var isOnline = doc.data().state == 'online';
    // ... use isOnline
});

全域更新 Cloud Firestore

雖然我們的應用程式會正確向自身回報線上狀態,但這項狀態在其他 Cloud Firestore 應用程式中仍不正確,因為我們的「離線」狀態寫入作業僅限於本機,且不會在連線恢復時同步。為因應此問題,我們會使用 Cloud 函式,監控即時資料庫中的 status/{uid} 路徑。當即時資料庫值變更時,該值會同步處理至 Cloud Firestore,以便所有使用者的狀態都正確無誤。

Node.js

firebase.firestore().collection('status')
    .where('state', '==', 'online')
    .onSnapshot(function(snapshot) {
        snapshot.docChanges().forEach(function(change) {
            if (change.type === 'added') {
                var msg = 'User ' + change.doc.id + ' is online.';
                console.log(msg);
                // ...
            }
            if (change.type === 'removed') {
                var msg = 'User ' + change.doc.id + ' is offline.';
                console.log(msg);
                // ...
            }
        });
    });

部署這個函式後,您將擁有可透過 Cloud Firestore 執行的完整狀態系統。以下是透過 where() 查詢監控所有上線或離線的使用者。

網路

firebase.firestore().collection('status')
    .where('state', '==', 'online')
    .onSnapshot(function(snapshot) {
        snapshot.docChanges().forEach(function(change) {
            if (change.type === 'added') {
                var msg = 'User ' + change.doc.id + ' is online.';
                console.log(msg);
                // ...
            }
            if (change.type === 'removed') {
                var msg = 'User ' + change.doc.id + ' is offline.';
                console.log(msg);
                // ...
            }
        });
    });

限制

使用即時資料庫為 Cloud Firestore 應用程式新增狀態資訊雖然可擴充且有效,但仍有以下限制:

  • 去抖動:當您監聽 Cloud Firestore 中的即時變更時,這個解決方案可能會觸發多項變更。如果這些變更觸發的事件比您預期的多,請手動去抖動 Cloud Firestore 事件。
  • 連線能力:這個實作會測量連至即時資料庫 (而非 Cloud Firestore) 的連線能力。如果每個資料庫的連線狀態不一致,這個解決方案可能會回報錯誤的狀態。
  • Android:在 Android 上,如果 60 秒內沒有任何操作,即時資料庫就會與後端中斷連線。無活動表示沒有開放的事件監聽器或待處理作業。為確保連線保持開啟,建議您在 .info/connected 以外的路徑中新增值事件監聽器。例如,您可以在每個工作階段開始時執行 FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()。詳情請參閱「偵測連線狀態」。