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

Crie presença no Cloud Firestore

Dependendo do tipo de aplicativo que você está criando, pode ser útil detectar quais usuários ou dispositivos estão ativamente online, também conhecido como detecção de "presença".

Por exemplo, se você estiver criando um aplicativo como uma rede social ou implantando uma frota de dispositivos IoT, poderá usar essas informações para exibir uma lista de amigos que estão online e livres para bater papo ou classificar seus dispositivos IoT por "vistos pela última vez ."

O Cloud Firestore não oferece suporte nativo à presença, mas você pode aproveitar outros produtos Firebase para criar um sistema de presença.

Solução: Cloud Functions com Realtime Database

Para conectar o Cloud Firestore ao recurso de presença nativa do Firebase Realtime Database, use o Cloud Functions.

Use o Realtime Database para relatar o status da conexão e, em seguida, use o Cloud Functions para espelhar esses dados no Cloud Firestore.

Usando a presença no Realtime Database

Primeiro, considere como um sistema de presença tradicional funciona no Realtime Database.

Rede

// 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 exemplo é um sistema completo de presença do Realtime Database. Ele lida com várias desconexões, falhas e assim por diante.

Conectando-se ao Cloud Firestore

Para implementar uma solução semelhante no Cloud Firestore, use o mesmo código do Realtime Database e, em seguida, use o Cloud Functions para manter o Realtime Database e o Cloud Firestore sincronizados.

Se ainda não o fez, adicione o Realtime Database ao seu projeto e inclua a solução de presença acima.

Em seguida, você sincronizará o estado de presença com o Cloud Firestore por meio dos seguintes métodos:

  1. Localmente, para o cache do Cloud Firestore do dispositivo off-line para que o aplicativo saiba que está off-line.
  2. Globalmente, usando uma Cloud Function para que todos os outros dispositivos que acessam o Cloud Firestore saibam que esse dispositivo específico está offline.

Atualizando o cache local do Cloud Firestore

Vamos dar uma olhada nas alterações necessárias para atender ao primeiro problema - atualizar o cache local do Cloud Firestore.

Rede

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

Com essas alterações, garantimos que o estado local do Cloud Firestore sempre refletirá o status online/offline do dispositivo. Isso significa que você pode ouvir o documento /status/{uid} e usar os dados para alterar sua IU para refletir o status da conexão.

Rede

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

Atualizando o Cloud Firestore globalmente

Embora nosso aplicativo relate corretamente a presença on-line para si mesmo, esse status ainda não será preciso em outros aplicativos do Cloud Firestore porque nossa gravação de status "off-line" é apenas local e não será sincronizada quando uma conexão for restaurada. Para combater isso, usaremos uma Cloud Function que monitora o caminho status/{uid} no Realtime Database. Quando o valor do Realtime Database mudar, o valor será sincronizado com o Cloud Firestore para que todos os status dos usuários estejam corretos.

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

Depois de implantar essa função, você terá um sistema de presença completo em execução com o Cloud Firestore. Abaixo está um exemplo de monitoramento para qualquer usuário que fique online ou offline usando uma consulta where() .

Rede

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

Limitações

O uso do Realtime Database para adicionar presença ao seu aplicativo Cloud Firestore é escalável e eficaz, mas tem algumas limitações:

  • Debouncing - ao ouvir alterações em tempo real no Cloud Firestore, esta solução provavelmente acionará várias alterações. Se essas alterações acionarem mais eventos do que você deseja, rejeite manualmente os eventos do Cloud Firestore.
  • Conectividade - esta implementação mede a conectividade com o Realtime Database, não com o Cloud Firestore. Se o status da conexão para cada banco de dados não for o mesmo, esta solução pode relatar um estado de presença incorreto.
  • Android - no Android, o Realtime Database desconecta do back-end após 60 segundos de inatividade. Inatividade significa que não há ouvintes abertos ou operações pendentes. Para manter a conexão aberta, recomendamos que você adicione um ouvinte de evento de valor a um caminho além de .info/connected . Por exemplo, você poderia fazer FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() no início de cada sessão. Para obter mais informações, consulte Detectando o estado da conexão .