Tworzenie obecności w Cloud Firestore

W zależności od typu tworzonej aplikacji może być przydatne wykrywanie, którzy użytkownicy lub urządzenia są aktywnie online, czyli wykrywanie „obecności”.

Jeśli na przykład 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 dostępni do rozmowy, lub posortować urządzenia IoT według daty „ostatniego użycia”.

Cloud Firestore nie obsługuje natywnie funkcji obecności, ale możesz wykorzystać inne usługi Firebase do zbudowania systemu obecności.

Rozwiązanie: Cloud Functions z bazą danych czasu rzeczywistego

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

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

Korzystanie z funkcji 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 wiele rozłączeń, awarii itp.

Łączę z: Cloud Firestore

Aby wdrożyć podobne rozwiązanie w Cloud Firestore, użyj tego samego kodu Bazy danych czasu rzeczywistego, a potem użyj Cloud Functions, aby zsynchronizować Bazę danych czasu rzeczywistego i Cloud Firestore.

Jeśli nie masz jeszcze w projekcie Bazy danych czasu rzeczywistego, dodaj ją i uwzględnij powyższe rozwiązanie dotyczące 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 offline.
  2. Globalnie, za pomocą funkcji Cloud Function, 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 użytkownika Cloud Firestore

Przyjrzyjmy się zmianom, które należy wprowadzić, aby rozwiązać pierwszy problem – zaktualizować pamięć podręczną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);
    });
});

Dzięki tym zmianom stan lokalny Cloud Firestore zawsze będzie odzwierciedlać stan urządzenia (online/offline). Oznacza to, że możesz nasłuchiwać dokumentu /status/{uid} i używać danych do zmiany interfejsu, aby odzwierciedlał stan połączenia.

Sieć

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

Aktualizowanie Cloud Firestore na całym świecie

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

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 będziesz mieć kompletny system obecności działający z Cloud Firestore. Poniżej znajdziesz przykład monitorowania użytkowników, którzy przechodzą w tryb online lub offline, za pomocą zapytania where().

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

Dodawanie informacji o obecności do aplikacji Cloud Firestore za pomocą Bazy danych czasu rzeczywistego jest skalowalne i skuteczne, ale ma pewne ograniczenia:

  • Debouncing – podczas nasłuchiwania zmian w czasie rzeczywistym w przypadku Cloud Firestore to rozwiązanie prawdopodobnie wywoła wiele zmian. Jeśli te zmiany wywołują więcej zdarzeń, niż chcesz, ręcznie usuń powtórzenia zdarzeń Cloud Firestore.
  • Łączność – ta implementacja mierzy łączność z bazą danych czasu rzeczywistego, a nie z Cloud Firestore. Jeśli stan połączenia z poszczególnymi bazami danych nie jest taki sam, to rozwiązanie może zgłaszać nieprawidłowy stan obecności.
  • Android – na urządzeniach z Androidem baza danych czasu rzeczywistego rozłącza się z backendem po 60 sekundach braku aktywności. Brak aktywności oznacza brak otwartych odbiorników lub oczekujących operacji. Aby utrzymać otwarte połączenie, zalecamy dodanie odbiornika zdarzeń wartości do ścieżki innej niż .info/connected. Możesz na przykład wykonać FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() na początku każdej sesji. Więcej informacji znajdziesz w artykule Wykrywanie stanu połączenia.