Crie presença no Cloud Firestore

Dependendo do tipo de aplicativo que você está criando, pode ser útil detectar quais dos seus 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 "visto pela última vez". ."

O Cloud Firestore não oferece suporte nativo à presença, mas você pode aproveitar outros produtos do 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 presença no Realtime Database

Primeiro, considere como funciona um sistema de presença tradicional 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 de presença completo do Realtime Database. Ele lida com múltiplas desconexões, travamentos 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, no cache do Cloud Firestore do dispositivo off-line para que o aplicativo saiba que ele está off-line.
  2. Globalmente, usando uma Cloud Function para que todos os outros dispositivos que acessam o Cloud Firestore saibam que este dispositivo específico está offline.

Atualizando o cache local do Cloud Firestore

Vamos dar uma olhada nas mudanças necessárias para resolver o 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 mudanças, 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 online para si mesmo, esse status ainda não será preciso em outros aplicativos Cloud Firestore porque nossa gravação de status "offline" é apenas local e não será sincronizada quando uma conexão for restaurada. Para combater isso, usaremos uma função do Cloud que monitora o caminho status/{uid} no Realtime Database. Quando o valor do Realtime Database for alterado, o valor será sincronizado com o Cloud Firestore para que o status de todos os usuários esteja correto.

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

Depois de implantar esta função, você terá um sistema de presença completo rodando com 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

Usar o Realtime Database para adicionar presença ao seu aplicativo Cloud Firestore é escaloná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, debounce 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 com cada banco de dados não for o mesmo, esta solução poderá relatar um estado de presença incorreto.
  • Android : no Android, o Realtime Database se 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 .