Buduj obecność w Cloud Firestore

W zależności od typu tworzonej aplikacji może okazać się przydatne wykrywanie, którzy z Twoich użytkowników lub urządzeń są aktywnie online – inaczej nazywamy to wykrywaniem „obecności”.

Na przykład, jeśli tworzysz aplikację, taką jak sieć społecznościowa lub wdrażasz flotę urządzeń IoT, możesz użyć tych informacji, aby wyświetlić listę znajomych, którzy są online i mogą swobodnie czatować, lub posortować swoje urządzenia IoT według „ostatnio widzianych ”.

Cloud Firestore nie obsługuje natywnie obecności, ale możesz wykorzystać inne produkty Firebase, aby zbudować system obecności.

Rozwiązanie: Funkcje w chmurze z bazą danych czasu rzeczywistego

Aby połączyć Cloud Firestore z natywną funkcją obecności w Bazie danych czasu rzeczywistego Firebase, użyj Cloud Functions.

Użyj Bazy danych czasu rzeczywistego, aby zgłosić stan połączenia, a następnie użyj Cloud Functions, aby zdublować te dane w Cloud Firestore.

Korzystanie z obecności w Bazie danych czasu rzeczywistego

Najpierw zastanów się, jak działa tradycyjny system obecności w Bazie danych czasu rzeczywistego.

Sieć

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

Ten przykład to kompletny system obecności w Bazie Danych Czasu Rzeczywistego. Obsługuje wielokrotne rozłączenia, awarie i tak dalej.

Łączenie z Cloud Firestore

Aby wdrożyć podobne rozwiązanie w Cloud Firestore, użyj tego samego kodu Bazy danych czasu rzeczywistego, a następnie użyj Cloud Functions do synchronizacji Bazy danych czasu rzeczywistego i Cloud Firestore.

Jeśli jeszcze nie masz, dodać Aktualizacje bazy danych do projektu i obejmują powyższy roztwór obecności.

Następnie zsynchronizujesz stan obecności z Cloud Firestore za pomocą następujących metod:

  1. Lokalnie do pamięci podręcznej Cloud Firestore urządzenia offline, aby aplikacja wiedziała, że ​​jest offline.
  2. Globalnie za pomocą funkcji Cloud Functions, aby wszystkie inne urządzenia uzyskujące dostęp do Cloud Firestore wiedziały, że to konkretne urządzenie jest offline.

Aktualizowanie lokalnej pamięci podręcznej Cloud Firestore

Przyjrzyjmy się zmianom wymaganym do wypełnienia pierwszego problemu – aktualizacji lokalnej pamięci podręcznej Cloud Firestore.

Sieć

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

Z tymi zmianami my teraz zapewnić, że lokalny stan Chmura Firestore zawsze odzwierciedlają stan online / offline urządzenia. W ten sposób można słuchać /status/{uid} dokumentu i wykorzystywać dane zmienić UI odzwierciedlać stan połączenia.

Sieć

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

Globalna aktualizacja Cloud Firestore

Chociaż nasza aplikacja poprawnie zgłasza sobie obecność online, ten stan nie będzie jeszcze dokładny w innych aplikacjach Cloud Firestore, ponieważ nasz stan „offline” jest zapisywany tylko lokalnie i nie zostanie zsynchronizowany po przywróceniu połączenia. Aby temu przeciwdziałać, użyjemy funkcji Chmura która oglądający status/{uid} ścieżkę w czasie rzeczywistym Database. Gdy zmieni się wartość Bazy danych czasu rzeczywistego, zostanie ona zsynchronizowana z Cloud Firestore, dzięki czemu stany wszystkich użytkowników będą prawidłowe.

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

Po wdrożeniu tej funkcji będziesz mieć kompletny system obecności działający w Cloud Firestore. Poniżej znajduje się przykład monitoring dla wszystkich użytkowników, którzy przychodzą online lub przejść do trybu offline przy użyciu where() zapytanie.

Sieć

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

Ograniczenia

Używanie Bazy danych czasu rzeczywistego do dodawania obecności do aplikacji Cloud Firestore jest skalowalne i skuteczne, ale ma pewne ograniczenia:

  • Debouncing - podczas słuchania w czasie rzeczywistym zmian w Cloud FireStore, to rozwiązanie może wywołać wiele zmian. Jeśli te zmiany wywołają więcej zdarzeń, niż chcesz, ręcznie odrzuć zdarzenia Cloud Firestore.
  • Łączność - to łączność środki wykonawcze do Realtime Database, nie do Cloud FireStore. Jeśli stan połączenia z każdą bazą danych nie jest taki sam, to rozwiązanie może zgłosić nieprawidłowy stan obecności.
  • Android - Android, Realtime rozłącza bazy danych z backend po 60 sekundach bezczynności. Brak aktywności oznacza brak otwartych słuchaczy lub oczekujących operacji. Aby utrzymać otwarte połączenie, zalecamy dodać detektor zdarzeń wartość do ścieżki oprócz .info/connected . Na przykład można zrobić FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() na początku każdej sesji. Aby uzyskać więcej informacji, zobacz Wykrywanie połączenia państwo .