Crie presença em apps com o Cloud Functions
Consoante o tipo de app que está a criar, pode ser útil detetar quais dos seus utilizadores ou dispositivos estão ativamente online, também conhecido como detetar "presença".
Por exemplo, se estiver a criar uma app como uma rede social ou a implementar uma frota de dispositivos de IoT, pode usar estas informações para apresentar uma lista de amigos online e disponíveis para conversar ou ordenar os seus dispositivos de IoT por "última vez vistos".
O Firestore não suporta nativamente a presença, mas pode tirar partido de outros produtos Firebase para criar um sistema de presença.
Solução: Cloud Functions com a Realtime Database
Para associar o Firestore à funcionalidade de presença nativa da Firebase Realtime Database, use o Cloud Functions.
Use a Realtime Database para comunicar o estado da ligação e, em seguida, use o Cloud Functions para espelhar esses dados no Firestore.
Usar a presença na Realtime Database
Primeiro, considere como funciona um sistema de presença tradicional na base de dados em tempo real.
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 exemplo é um sistema de presença da Realtime Database completo. Lida com várias perdas de ligação, falhas de sistema, etc.
Associar ao Firestore
Para implementar uma solução semelhante no Firestore, use o mesmo código da Realtime Database e, em seguida, use o Cloud Functions para manter a Realtime Database e o Firestore sincronizados.
Caso ainda não o tenha feito, adicione a Realtime Database ao seu projeto e inclua a solução de presença acima.
Em seguida, sincronize o estado de presença com o Firestore através dos seguintes métodos:
- Localmente, para a cache do Firestore do dispositivo offline, para que a app saiba que está offline.
- Globalmente, usar uma Cloud Function para que todos os outros dispositivos que acedem ao Firestore saibam que este dispositivo específico está offline.
As funções recomendadas neste tutorial não podem ser executadas numa app cliente. Têm de ser implementadas no Cloud Functions para Firebase e requerem lógica do lado do servidor do SDK Firebase Admin. Para ver orientações detalhadas, consulte a documentação do Cloud Functions.
Atualizar a cache local do Firestore
Vejamos as alterações necessárias para resolver o primeiro problema: atualizar a cache local do 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); }); });
Com estas alterações, garantimos que o estado do Firestore local reflete sempre o estado online/offline do dispositivo. Isto significa que pode ouvir o documento /status/{uid}
e usar os dados para alterar a IU de modo a refletir o estado da ligação.
Web
userStatusFirestoreRef.onSnapshot(function(doc) { var isOnline = doc.data().state == 'online'; // ... use isOnline });
Atualizar o Firestore globalmente
Embora a nossa aplicação comunique corretamente a presença online a si própria, este estado
ainda não é preciso noutras apps do Firestore, porque a escrita do estado "offline"
é apenas local e não é sincronizada quando uma ligação é restaurada. Para contrariar
esta situação, vamos usar uma Cloud Function que monitoriza o caminho status/{uid}
na Realtime
Database. Quando o valor da base de dados em tempo real muda, o valor é sincronizado com o Firestore
para que os estados de todos os utilizadores estejam corretos.
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 implementar esta função, tem um sistema de presença completo em execução com o Firestore. Segue-se um exemplo de monitorização de utilizadores que
ficam online ou offline através de uma 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); // ... } }); });
Limitações
A utilização da Realtime Database para adicionar presença à sua app Firestore é escalável e eficaz, mas tem algumas limitações:
- Debouncing: quando ouve alterações em tempo real no Firestore, é provável que esta solução acione várias alterações. Se estas alterações acionarem mais eventos do que pretende, desative manualmente os eventos do Firestore.
- Conetividade: esta implementação mede a conetividade com a base de dados em tempo real e não com o Firestore. Se o estado da ligação a cada base de dados não for o mesmo, esta solução pode comunicar um estado de presença incorreto.
- Android: no Android, a base de dados em tempo real desliga-se do back-end após 60 segundos de inatividade. A inatividade significa que não existem ouvintes abertos nem operações pendentes. Para manter a ligação aberta, recomendamos que adicione um ouvinte de eventos de valor a um caminho além de
.info/connected
. Por exemplo, pode fazê-loFirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()
no início de cada sessão. Para mais informações, consulte o artigo Detetar o estado da ligação.