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 np. tworzysz aplikację do obsługi sieci społecznościowej lub wdrażasz flotę urządzeń IoT, możesz użyć tych informacji, aby wyświetlić listę znajomych, którzy są online i mogą rozmawiać, lub posortować urządzenia IoT według „ostatniego zobaczenia”.

Cloud Firestore natywnie nie obsługuje 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ć usługę 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łaszać stan połączenia, a potem użyj funkcji Cloud Functions, aby odzwierciedlić te dane w Cloud Firestore.

Korzystanie z obecności w BDB czasu rzeczywistego

Najpierw zastanów się, jak działa tradycyjny system obecności w BDB.

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 BDB. Obsługuje ona 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 następnie użyj Cloud Functions do synchronizowania Bazy danych czasu rzeczywistego i Cloud Firestore.

Jeśli jeszcze tego nie zrobiono, dodaj do projektu Bazę danych czasu rzeczywistego i uwzględnij opisane wyżej rozwiązanie dotyczące obecności.

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

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

Aktualizuję lokalną pamięć podręczną użytkownika Cloud Firestore

Przyjrzyjmy się zmianom wymaganym do rozwiązania pierwszego problemu, czyli 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);
    });
});

Dzięki tym zmianom stan Cloud Firestore local zawsze będzie odzwierciedlał stan online/offline urządzenia. Oznacza to, że możesz odsłuchać dokument /status/{uid} i wykorzystać dane do zmiany interfejsu, aby odzwierciedlał stan połączenia.

Sieć

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

Aktualizuję Cloud Firestore globalnie

Chociaż nasza aplikacja prawidłowo informuje o obecności online, ten stan nie będzie jeszcze prawidłowy w innych aplikacjach Cloud Firestore, ponieważ zapis stanu „offline” jest tylko lokalny i nie zostanie zsynchronizowany po przywróceniu połączenia. Aby temu zapobiec, użyjemy funkcji Cloud Functions, która monitoruje ścieżkę status/{uid} w Baza danych czasu rzeczywistego. Gdy zmieni się wartość w Baza danych w czasie rzeczywistym, zostanie ona zsynchronizowana z wartością 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 pełny system obecności będzie działać za pomocą Cloud Firestore. Poniżej znajdziesz przykład monitorowania użytkowników, którzy są 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

Korzystanie z Bazy danych czasu rzeczywistego do dodawania funkcji obecności w aplikacji Cloud Firestore jest skalowalne i skuteczne, ale ma pewne ograniczenia:

  • Debouncing (odstęp czasowy) – podczas słuchania zmian w czasie rzeczywistym w Cloud Firestore to rozwiązanie może wywołać wiele zmian. Jeśli te zmiany powodują, że zdarzeń jest więcej, niż chcesz, ręcznie wyłącz detekcję zdarzeń 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 jest inny, to rozwiązanie może zgłosić nieprawidłowy stan obecności.
  • Android – na Androidzie baza danych w czasie rzeczywistym 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 pozostało otwarte, zalecamy dodanie do ścieżki poza .info/connected odbiornika zdarzenia wartości. Na przykład możesz wykonać tę czynność FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced()na początku każdej sesji. Więcej informacji znajdziesz w artykule Wykrywanie stanu połączenia.