A seconda del tipo di app che stai creando, potrebbe essere utile rilevare quali utenti o dispositivi sono attivamente online, ovvero rilevare la "presenza".
Ad esempio, se stai creando un'app come un social network o eseguendo il deployment di una flotta di dispositivi IoT, potresti utilizzare queste informazioni per visualizzare un elenco di amici online e disponibili per la chat o ordinare i dispositivi IoT in base alla "data dell'ultima visualizzazione".
Cloud Firestore non supporta in modo nativo la presenza, ma puoi utilizzare altri prodotti Firebase per creare un sistema di presenza.
Soluzione: Cloud Functions con Realtime Database
Per connettere Cloud Firestore alla funzionalità di presenza nativa di Firebase Realtime Database, utilizza Cloud Functions.
Utilizza Realtime Database per segnalare lo stato della connessione, quindi utilizza Cloud Functions per eseguire il mirroring di questi dati in Cloud Firestore.
Utilizzare la presenza in Realtime Database
Innanzitutto, considera come funziona un sistema di presenza tradizionale in 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); }); });
Questo esempio è un sistema di presenza completo di Realtime Database. Gestisce più disconnessioni, arresti anomali e così via.
Connettersi a Cloud Firestore
Per implementare una soluzione simile in Cloud Firestore utilizza lo stesso codice di Realtime Database, quindi utilizza Cloud Functions per mantenere sincronizzati Realtime Database e Cloud Firestore
Se non l'hai già fatto, aggiungi Realtime Database al tuo progetto e includi la soluzione di presenza sopra indicata.
Ora sincronizzerai lo stato di presenza con Cloud Firestore utilizzando i seguenti metodi:
- Localmente, nella cache Cloud Firestore del dispositivo offline, in modo che l'app sappia che è offline.
- A livello globale, utilizzando una funzione Cloud in modo che tutti gli altri dispositivi che accedono a Cloud Firestore sappiano che questo dispositivo specifico è offline.
Le funzioni consigliate in questo tutorial non possono essere eseguite in un'app client. Devono essere sottoposte a deployment in Cloud Functions for Firebase, e richiedono la logica lato server dell'SDK Firebase Admin. Per indicazioni dettagliate, consulta la documentazione di Cloud Functions.
Aggiornare la cache locale di Cloud Firestore
Diamo un'occhiata alle modifiche necessarie per risolvere il primo problema: aggiornare Cloud Firestore la cache locale.
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 queste modifiche, abbiamo assicurato che lo stato locale Cloud Firestore rifletta sempre
lo stato online/offline del dispositivo. Ciò significa che puoi rimanere in ascolto del documento /status/{uid} e utilizzare i dati per modificare l'UI in modo che rifletta lo stato della connessione.
Web
userStatusFirestoreRef.onSnapshot(function(doc) { var isOnline = doc.data().state == 'online'; // ... use isOnline });
Aggiornamento Cloud Firestore a livello globale
Sebbene la nostra applicazione segnali correttamente la presenza online, questo stato
non sarà ancora accurato in altre Cloud Firestore app perché la scrittura dello stato "offline"
è solo locale e non verrà sincronizzata quando viene ripristinata una connessione. Per ovviare a questo problema, utilizzeremo una funzione Cloud Functions che monitora il percorso status/{uid} in Realtime Database. Quando il valore di Realtime Database cambia, il valore viene sincronizzato con Cloud Firestore
in modo che lo stato di tutti gli utenti sia corretto.
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); // ... } }); });
Una volta eseguito il deployment di questa funzione, avrai un sistema di presenza completo in esecuzione
con Cloud Firestore. Di seguito è riportato un esempio di monitoraggio di tutti gli utenti che vanno online o offline utilizzando una query 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); // ... } }); });
Limitazioni
L'utilizzo di Realtime Database per aggiungere la presenza all'app Cloud Firestore è scalabile ed efficace, ma presenta alcune limitazioni:
- Debouncing : quando si ascoltano le modifiche in tempo reale in Cloud Firestore, è probabile che questa soluzione attivi più modifiche. Se queste modifiche attivano più eventi di quelli desiderati, esegui manualmente il debouncing degli eventi Cloud Firestore.
- Connettività : questa implementazione misura la connettività a Realtime Database, non a Cloud Firestore. Se lo stato della connessione a ogni database non è lo stesso, questa soluzione potrebbe segnalare uno stato di presenza errato.
- Android : su Android, Realtime Database si disconnette dal
backend dopo 60 secondi di inattività. Per inattività si intendono nessun listener aperto o operazioni in attesa. Per mantenere aperta la connessione, ti consigliamo di aggiungere un listener di eventi di valore a un percorso diverso da
.info/connected. Ad esempio, puoi eseguireFirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()all'inizio di ogni sessione. Per saperne di più, vedi Rilevare lo stato della connessione.