Tworzenie obecności w Cloud Firestore

W zależności od typu tworzonej aplikacji może Ci się przydać wykrywanie użytkowników lub urządzeń, którzy aktywnie korzystają z internetu. Nazywamy to wykrywaniem „obecności”.

Jeśli na przykład tworzysz aplikację taką jak sieć społecznościowa lub wdrażasz całą flotę urządzeń IoT, możesz na podstawie tych informacji wyświetlić listę znajomych, którzy są online i którzy mogą rozmawiać z Tobą na czacie, lub posortować urządzenia IoT według „ostatnio widzianych”.

Cloud Firestore nie obsługuje natywnie obecności, ale możesz użyć innych usług Firebase do utworzenia systemu obecności.

Rozwiązanie: funkcje w Cloud Functions 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.

Za pomocą Bazy danych czasu rzeczywistego możesz raportować stan połączenia, a następnie za pomocą Cloud Functions zduplikować 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.

Internet

// 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 przedstawia kompletny system obecności w bazie danych czasu rzeczywistego. Obsługuje wielokrotne rozłączenia, awarie itp.

Łączę 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 utrzymywania synchronizacji bazy danych czasu rzeczywistego z Cloud Firestore.

Dodaj do swojego projektu Bazę danych czasu rzeczywistego (jeśli jeszcze jej nie masz) i dołącz powyższe rozwiązanie do wykrywania obecności.

Następnie zsynchronizuj stan obecności z Cloud Firestore za pomocą tych metod:

  1. Lokalnie – do pamięci podręcznej Cloud Firestore urządzenia offline, aby aplikacja wiedziała, że jest ona offline.
  2. Ogólnie rzecz biorąc, korzystając z funkcji w 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 rozwiązania pierwszego problemu, czyli aktualizacji lokalnej pamięci podręcznej Cloud Firestore.

Internet

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

Dzięki tym zmianom lokalny stan w Cloud Firestore zawsze będzie odzwierciedlał stan online/offline urządzenia. Oznacza to, że możesz odsłuchać dokument /status/{uid} i użyć tych danych, aby zmienić interfejs odpowiednio do stanu połączenia.

Internet

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

Globalne aktualizowanie Cloud Firestore

Chociaż nasza aplikacja prawidłowo zgłasza swoją obecność online, ten stan nie będzie jeszcze dokładny w innych aplikacjach Cloud Firestore, ponieważ zapis stanu „offline” ma miejsce tylko lokalnie i nie zostanie zsynchronizowany po przywróceniu połączenia. Aby temu zapobiec, użyjemy funkcji w Cloud Functions, która monitoruje ścieżkę status/{uid} w bazie danych czasu rzeczywistego. Gdy wartość w Bazie danych czasu rzeczywistego się zmieni, wartość zostanie zsynchronizowana z Cloud Firestore, aby stan wszystkich użytkowników był prawidłowy.

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

Po wdrożeniu tej funkcji w Cloud Firestore będzie działać kompletny system obecności. Poniżej znajdziesz przykład monitorowania użytkowników, którzy przechodzą do trybu online lub offline za pomocą zapytania where().

Internet

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życie Bazy danych czasu rzeczywistego w celu zwiększenia obecności aplikacji Cloud Firestore zapewnia skalowalność i skuteczność, ale podlega pewnym ograniczeniom:

  • Odbijanie – w przypadku nasłuchiwania zmian w czasie rzeczywistym w Cloud Firestore to rozwiązanie prawdopodobnie wywoła wiele zmian. Jeśli te zmiany aktywują więcej zdarzeń, niż potrzebujesz, ręcznie odbij zdarzenia Cloud Firestore.
  • Połączenia – ta implementacja mierzy połączenia z Bazą danych czasu rzeczywistego, a nie z Cloud Firestore. Jeśli stan połączenia z każdą bazą danych nie jest taki sam, rozwiązanie może zgłosić nieprawidłowy stan obecności.
  • Android – na Androidzie baza danych czasu rzeczywistego rozłącza się z backendem po 60 sekundach braku aktywności. Brak aktywności oznacza brak otwartych detektorów i oczekujących operacji. Aby połączenie pozostawało otwarte, zalecamy dodanie detektora zdarzeń wartości do ścieżki innej niż .info/connected. Na przykład możesz wykonać polecenie FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() na początku każdej sesji. Więcej informacji znajdziesz w sekcji Wykrywanie stanu połączenia.