根据您正在构建的应用的类型,您可能会发现如果能够检测哪些用户或设备活跃在线上(也称为检测“在线状态”),会带来很多好处。
例如,如果您正在构建一个类似社交网络的应用或正在部署一组 IoT 设备,那么您可以使用这些信息来显示在线和有空聊天的朋友列表,或者按照“上次上线时间”对您的 IoT 设备进行排序。
Cloud Firestore 本身不提供在线状态支持,但您可以利用其他 Firebase 产品来构建一个在线状态系统。
解决方案:将 Cloud Functions 与 Realtime Database 搭配使用
要将 Cloud Firestore 连接到 Firebase Realtime Database 的原生在线状态功能,请使用 Cloud Functions。
您可以使用 Realtime Database 报告连接状态,然后使用 Cloud Functions 将该数据镜像到 Cloud Firestore。
在 Realtime Database 中使用在线状态
首先,请考虑传统的在线状态系统在 Realtime Database 中的运行方式。
Web
// 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); }); });
这是一个 Realtime Database 在线状态系统的完整示例。它可以处理多个连接断开的情况、崩溃等。
正在连接到“Cloud Firestore”
要在 Cloud Firestore 中实现类似的解决方案,请使用相同的 Realtime Database 代码,然后使用 Cloud Functions 来保持 Realtime Database 和 Cloud Firestore 之间的同步。
如果还未将 Realtime Database 添加到项目中并加入上述在线状态解决方案,请先完成这些操作。
接下来,您需要通过以下方法将在线状态同步到 Cloud Firestore:
- 在本地,同步到离线设备的 Cloud Firestore 缓存,以便应用知道自己已经离线。
- 在全局范围内,使用一个 Cloud Functions 函数,以便访问 Cloud Firestore 的其他所有设备都知道此特定设备处于离线状态。
更新 Cloud Firestore 的本地缓存
我们来看看解决第一个问题所需的更改 - 更新 Cloud Firestore 的本地缓存。
Web
// ... 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}
文档,并使用数据更改界面以反映连接状态。
Web
userStatusFirestoreRef.onSnapshot(function(doc) { var isOnline = doc.data().state == 'online'; // ... use isOnline });
在全局更新 Cloud Firestore
虽然我们的应用正确地向“自己”报告了在线状态,但是在其他 Cloud Firestore 应用中,这个状态还并不准确,因为“离线”状态写入操作仅在本地执行,并不会在连接恢复时进行同步。为了解决这个问题,我们将使用一个 Cloud Functions 函数来监控 Realtime Database 中的 status/{uid}
路径。当 Realtime Database 中的值发生更改时,该值将同步到 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()
查询监控任何上线或下线用户的示例。
Web
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); // ... } }); });
限制
使用 Realtime Database 为您的 Cloud Firestore 应用添加在线状态的操作可以扩缩并发挥应有的作用,但存在一些限制:
- 去抖动 - 在 Cloud Firestore 中监听实时更改时,此解决方案可能会多次触发同一项更改。如果这些更改触发了比预期更多的事件,请手动对 Cloud Firestore 事件进行去抖动处理。
- 连接 - 此实现方案衡量的是与 Realtime Database(而不是 Cloud Firestore)的连接状态。如果与每个数据库的连接状态均不同,此解决方案可能会报告不正确的在线状态。
- Android - 在 Android 上,不活跃状态持续 60 秒后,Realtime Database 会断开与后端的连接。不活跃意味着没有打开的监听器或待处理操作。要使连接保持开启状态,我们建议您除了
.info/connected
之外,还应该为另一个路径添加值事件监听器。例如,您可以在每次会话开始时执行FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()
。如需了解详情,请参阅检测连接状态。