Dependiendo del tipo de app que estés compilando, te podría resultar útil detectar cuáles de tus usuarios o dispositivos están en línea de manera activa, lo que también se conoce como la detección de "presencia".
Por ejemplo, si estás creando una app como una red social o implementando una flota de dispositivos IoT, puedes usar esta información para mostrar una lista de amigos que están en línea y disponibles para chatear, o clasificar tus dispositivos IoT según la última vez que estuvieron conectados.
Cloud Firestore no admite la presencia de forma nativa, pero puedes aprovechar otros productos de Firebase para crear un sistema de presencia.
Solución: Cloud Functions con Realtime Database
Para conectar Cloud Firestore a la función de presencia nativa de Firebase Realtime Database, usa Cloud Functions.
Usa Realtime Database para informar estados de conexiones y, luego, usa Cloud Functions a fin de duplicar esos datos en Cloud Firestore.
Usa la presencia en Realtime Database
Primero, es importante entender cómo funciona un sistema de presencia tradicional en 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); }); });
Este ejemplo es un sistema completo de presencia de Realtime Database. Controla múltiples desconexiones, fallas y otros.
Conectando con Cloud Firestore
Para implementar una solución similar en Cloud Firestore, usa el mismo código de Realtime Database y, luego, usa Cloud Functions a fin de mantener la sincronización entre Realtime Database y Cloud Firestore.
Si aún no lo hiciste, agrega Realtime Database a tu proyecto y, luego, incluye la solución de presencia que aparece más arriba.
A continuación, deberás sincronizar el estado de presencia con Cloud Firestore a través de los siguientes métodos:
- De manera local, en la caché de Cloud Firestore del dispositivo sin conexión para que la app sepa que está sin conexión.
- De manera global, mediante una función de Cloud Functions para que todos los demás dispositivos que acceden a Cloud Firestore sepan que este dispositivo está sin conexión
Actualiza la caché local de Cloud Firestore
Revisemos los cambios necesarios para resolver el primer problema: actualizar la caché local de 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); }); });
Estos cambios permiten asegurarnos de que el estado local de Cloud Firestore refleje siempre el estado en línea o sin conexión del dispositivo. Esto significa que puedes escuchar el documento /status/{uid}
y usar los datos para realizar cambios en la IU a fin de reflejar el estado de conexión.
Web
userStatusFirestoreRef.onSnapshot(function(doc) { var isOnline = doc.data().state == 'online'; // ... use isOnline });
Actualiza Cloud Firestore a nivel mundial
Si bien nuestra aplicación informa la presencia en línea a sí misma de manera correcta, este estado
aún no será preciso en otras apps de Cloud Firestore, ya que nuestra escritura de estado
“sin conexión” es local y no se sincronizará cuando se restablezca una conexión. Para solucionar
este problema, usaremos una Cloud Function que supervisa la ruta de acceso status/{uid}
de Realtime Database. Cuando cambia el valor de Realtime Database, el valor se sincronizará con Cloud Firestore para que los estados de todos los usuarios sean correctos.
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); // ... } }); });
Una vez que implementes esta función, tendrás un sistema de presencia completo que se ejecutará con Cloud Firestore. A continuación, se muestra un ejemplo de supervisión de los usuarios que se conectan o desconectan con una consulta 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); // ... } }); });
Limitaciones
El uso de Realtime Database para agregar presencia a la app de Cloud Firestore es escalable y eficaz, pero presenta algunas limitaciones:
- Debouncing: Cuando se detectan cambios en tiempo real en Cloud Firestore, es probable que esta solución active varios de ellos. Si estos cambios activan más eventos de los que deseas, desactiva los eventos de Cloud Firestore de forma manual.
- Conectividad: Esta implementación mide la conectividad a Realtime Database, no a Cloud Firestore. Si el estado de la conexión a cada base de datos no es el mismo, es posible que esta solución informe un estado de presencia incorrecto.
- Android: en Android, Realtime Database se desconecta del
backend después de 60 segundos de inactividad. Inactividad se refiere a que no hay objetos de escucha abiertos ni operaciones pendientes. Para mantener abierta la conexión, te recomendamos que agregues un objeto de escucha de eventos de valores a una ruta distinta de
.info/connected
. Por ejemplo, podrías usarFirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()
al inicio de cada sesión. Para obtener más información, consulta Detecta el estado de conexión.