Construya presencia en Cloud Firestore

Dependiendo del tipo de aplicación que esté creando, puede resultarle útil detectar cuáles de sus usuarios o dispositivos están activamente en línea, lo que también se conoce como detección de "presencia".

Por ejemplo, si está creando una aplicación como una red social o implementando una flota de dispositivos IoT, puede usar esta información para mostrar una lista de amigos que están en línea y libres para chatear, u ordenar sus dispositivos IoT por "vistos por última vez". ".

Cloud Firestore no admite la presencia de forma nativa, pero puedes aprovechar otros productos de Firebase para crear un sistema de presencia.

Solución: funciones en la nube con base de datos en tiempo real

Para conectar Cloud Firestore a la función de presencia nativa de Firebase Realtime Database, use Cloud Functions.

Use Realtime Database para informar el estado de la conexión y luego use Cloud Functions para reflejar esos datos en Cloud Firestore.

Usando presencia en Realtime Database

Primero, considere 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 bases de datos en tiempo real. Maneja múltiples desconexiones, fallas, etc.

Conexión a Cloud Firestore

Para implementar una solución similar en Cloud Firestore, use el mismo código de Realtime Database y luego use Cloud Functions para mantener Realtime Database y Cloud Firestore sincronizados.

Si aún no lo ha hecho, agregue Realtime Database a su proyecto e incluya la solución de presencia anterior.

A continuación, sincronizará el estado de presencia con Cloud Firestore mediante los siguientes métodos:

  1. Localmente, en la caché de Cloud Firestore del dispositivo sin conexión para que la aplicación sepa que está sin conexión.
  2. A nivel global, usar una función de nube para que todos los demás dispositivos que acceden a Cloud Firestore sepan que este dispositivo específico está fuera de línea.

Actualización del caché local de Cloud Firestore

Echemos un vistazo a los cambios necesarios para cumplir con el primer problema: actualizar el 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);
    });
});

Con estos cambios ahora nos hemos asegurado de que el estado local de Cloud Firestore siempre refleje el estado en línea/fuera de línea del dispositivo. Esto significa que puede escuchar el documento /status/{uid} y usar los datos para cambiar su interfaz de usuario para reflejar el estado de la conexión.

Web

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

Actualización de Cloud Firestore a nivel mundial

Aunque nuestra aplicación informa correctamente la presencia en línea a sí misma, este estado no será preciso en otras aplicaciones de Cloud Firestore todavía porque nuestra escritura de estado "fuera de línea" es solo local y no se sincronizará cuando se restablezca una conexión. Para contrarrestar esto, usaremos una función en la nube que observa el status/{uid} en Realtime Database. Cuando el valor de la base de datos en tiempo real cambia, el valor se sincronizará con Cloud Firestore para que los estados de todos los usuarios sean correctos.

Nodo.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 implemente esta función, tendrá un sistema de presencia completo ejecutándose con Cloud Firestore. A continuación se muestra un ejemplo de monitoreo para cualquier usuario que se conecte o se desconecte mediante 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 su aplicación Cloud Firestore es escalable y efectivo, pero tiene algunas limitaciones:

  • Antirrebote : al escuchar cambios en tiempo real en Cloud Firestore, es probable que esta solución desencadene múltiples cambios. Si estos cambios desencadenan más eventos de los que desea, elimine manualmente los eventos de Cloud Firestore.
  • Conectividad : esta implementación mide la conectividad con Realtime Database, no con Cloud Firestore. Si el estado de conexión a cada base de datos no es el mismo, esta solución podría informar un estado de presencia incorrecto.
  • Android : en Android, Realtime Database se desconecta del backend después de 60 segundos de inactividad. Inactividad significa que no hay oyentes abiertos ni operaciones pendientes. Para mantener la conexión abierta, le recomendamos agregar un detector de eventos de valor a una ruta además de .info/connected . Por ejemplo, podría hacer FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() al inicio de cada sesión. Para obtener más información, consulte Detectar el estado de la conexión .