获取我们在 Firebase 峰会上发布的所有信息,了解 Firebase 可如何帮助您加快应用开发速度并满怀信心地运行应用。了解详情

在 Cloud Firestore 中建立存在感

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

根据您正在构建的应用程序的类型,您可能会发现检测哪些用户或设备处于活跃状态很有用——也称为检测“存在”。

例如,如果您正在构建像社交网络这样的应用程序或部署一组 IoT 设备,您可以使用此信息来显示在线且可以免费聊天的朋友列表,或者按“最后一次看到”对您的 IoT 设备进行排序。”

Cloud Firestore 本身并不支持在线状态,但您可以利用其他 Firebase 产品来构建在线状态系统。

解决方案:带有实时数据库的云函数

要将 Cloud Firestore 连接到 Firebase 实时数据库的本机状态功能,请使用 Cloud Functions。

使用实时数据库报告连接状态,然后使用 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 Functions 使实时数据库和 Cloud Firestore 保持同步。

如果您还没有,请将实时数据库添加到您的项目中并包含上述状态解决方案。

接下来,您将通过以下方法将在线状态同步到 Cloud Firestore:

  1. 在本地,到离线设备的 Cloud Firestore 缓存,以便应用知道它处于离线状态。
  2. 在全球范围内,使用 Cloud Function 让所有其他访问 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);
    });
});

通过这些更改,我们现在确保本地Cloud Firestore 状态将始终反映设备的在线/离线状态。这意味着您可以收听/status/{uid}文档并使用数据来更改您的 UI 以反映连接状态。

网络

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

在全球范围内更新 Cloud Firestore

尽管我们的应用程序正确地向自己报告在线状态,但此状态在其他 Cloud Firestore 应用程序中仍不准确,因为我们的“离线”状态写入仅在本地,并且在连接恢复时不会同步。为了解决这个问题,我们将使用一个云函数来监视实时数据库中的status/{uid}路径。当实时数据库值更改时,该值将同步到 Cloud Firestore,以便所有用户的状态都是正确的。

节点.js

const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();

// Since this code will be running in the Cloud Functions environment
// we call initialize Firestore without any arguments because it
// detects authentication from the environment.
const firestore = admin.firestore();

// Create a new function which is triggered on changes to /status/{uid}
// Note: This is a Realtime Database trigger, *not* Firestore.
exports.onUserStatusChanged = functions.database.ref('/status/{uid}').onUpdate(
    async (change, context) => {
      // Get the data written to Realtime Database
      const eventStatus = change.after.val();

      // Then use other event data to create a reference to the
      // corresponding Firestore document.
      const userStatusFirestoreRef = firestore.doc(`status/${context.params.uid}`);

      // It is likely that the Realtime Database change that triggered
      // this event has already been overwritten by a fast change in
      // online / offline status, so we'll re-read the current data
      // and compare the timestamps.
      const statusSnapshot = await change.after.ref.once('value');
      const status = statusSnapshot.val();
      functions.logger.log(status, eventStatus);
      // If the current timestamp for this data is newer than
      // the data that triggered this event, we exit this function.
      if (status.last_changed > eventStatus.last_changed) {
        return null;
      }

      // Otherwise, we convert the last_changed field to a Date
      eventStatus.last_changed = new Date(eventStatus.last_changed);

      // ... and write it to Firestore.
      return userStatusFirestoreRef.set(eventStatus);
    });

部署此功能后,您将拥有一个运行 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() 。有关详细信息,请参阅检测连接状态