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

Crea presencia en Cloud Firestore

Según el tipo de aplicación que esté creando, puede que le resulte ú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, podría 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 puede 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, luego use Cloud Functions para reflejar esos datos en Cloud Firestore.

Usar 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 Realtime Database. Maneja múltiples desconexiones, bloqueos, etc.

Conexión a Cloud Firestore

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

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 a través de los siguientes métodos:

  1. Localmente, a la memoria caché de Cloud Firestore del dispositivo sin conexión para que la aplicación sepa que está sin conexión.
  2. Globalmente, 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 aseguramos 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 globalmente

Aunque nuestra aplicación se informa correctamente sobre la presencia en línea, 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 la ruta de status/{uid} en Realtime Database. Cuando el valor de Realtime Database cambie, el valor se sincronizará con Cloud Firestore para que los estados de todos los usuarios sean correctos.

Nodo.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);
    });

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 supervisión de 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:

  • Eliminación de rebotes: al escuchar cambios en tiempo real en Cloud Firestore, es probable que esta solución active varios 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 agentes de escucha abiertos ni operaciones pendientes. Para mantener abierta la conexión, le recomendamos que agregue 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 comienzo de cada sesión. Para obtener más información, consulte Detección del estado de conexión .