Membangun kehadiran di Cloud Firestore

Bergantung jenis aplikasi yang Anda buat, fitur ini dapat digunakan untuk mendeteksi pengguna atau perangkat yang sedang online — fitur ini dikenal sebagai pendeteksi "kehadiran".

Misalnya, jika Anda sedang membangun aplikasi seperti jaringan sosial atau sedang menerapkan beberapa perangkat IoT, Anda dapat menggunakan informasi ini untuk menampilkan daftar teman yang sedang online dan bisa melakukan chat atau mengurutkan perangkat IoT Anda berdasarkan "terakhir kali terlihat."

Cloud Firestore tidak mendukung kehadiran secara bawaan, namun Anda dapat memanfaatkan produk Firebase lainnya untuk membangun sistem kehadiran.

Solusi: Menghubungkan Cloud Functions dengan Realtime Database

Untuk menghubungkan fitur kehadiran bawaan Cloud Firestore ke Firebase Realtime Database, gunakan Cloud Functions.

Gunakan Realtime Database untuk melaporkan status koneksi, lalu gunakan Cloud Functions untuk menduplikasi data tersebut ke Cloud Firestore.

Menggunakan kehadiran di Realtime Database

Pertama-tama, perhatikan cara kerja sistem kehadiran tradisional di 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);
    });
});

Berikut ini adalah contoh sistem kehadiran Realtime Database yang lengkap. Sistem ini menangani beberapa pemutusan jaringan, error, dan sebagainya.

Menghubungkan ke Cloud Firestore

Untuk menerapkan solusi serupa di Cloud Firestore, gunakan kode Realtime Database yang sama, lalu gunakan Cloud Functions agar Realtime Database dan Cloud Firestore tetap sinkron.

Tambahkan Realtime Database ke project dan sertakan solusi kehadiran di atas jika Anda belum melakukannya.

Kemudian, sinkronkan status kehadiran ke Cloud Firestore melalui metode berikut:

  • Secara lokal, sinkronkan ke cache Cloud Firestore dari perangkat yang offline agar aplikasi tersebut mengetahui bahwa perangkat sedang offline.
  • Secara global, sinkronkan dengan Cloud Function agar semua perangkat lain yang sedang mengakses Cloud Firestore mengetahui bahwa perangkat ini sedang offline.
  • Mengupdate cache lokal Cloud Firestore

    Mari kita lihat perubahan yang diperlukan untuk mengatasi masalah pertama - mengupdate cache lokal Cloud Firestore.

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

    Dengan perubahan ini, sekarang kita berhasil memastikan bahwa status Cloud Firestore lokal akan selalu mencerminkan status perangkat ketika sedang online/offline. Artinya, Anda dapat mendeteksi dokumen /status/{uid} dan menggunakan data untuk mengubah UI agar mencerminkan status koneksi.

    Web

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

    Mengupdate Cloud Firestore secara global

    Meskipun aplikasi kami melaporkan kehadiran online perangkat dengan tepat, tetapi status ini tidak akan akurat di aplikasi Cloud Firestore lainnya. Penyebabnya? Karena status "offline" hanya dituliskan secara lokal dan tidak akan disinkronkan saat sambungan dipulihkan. Untuk mengatasi hal ini, kami menggunakan Cloud Function untuk melihat lokasi status/{uid} di Realtime Database. Setiap perubahan terhadap nilai Realtime Database akan disinkronkan ke Cloud Firestore sehingga semua status pengguna akan menjadi tepat.

    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* Cloud 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();
          console.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);
        });
    

    Begitu fungsi ini di-deploy, sistem kehadiran lengkap akan berjalan dengan Cloud Firestore. Di bawah ini adalah contoh pemantauan bagi pengguna yang baru online atau offline dengan menggunakan kueri 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);
                    // ...
                }
            });
        });
    

    Batasan

    Menggunakan Realtime Database untuk menambahkan kehadiran ke aplikasi Cloud Firestore Anda merupakan langkah yang skalabel dan efektif tetapi memiliki beberapa keterbatasan:

    • Sekali tekan - saat memproses perubahan realtime di Cloud Firestore, solusi ini mungkin akan memicu banyak perubahan. Jika perubahan ini memicu lebih banyak peristiwa daripada yang Anda inginkan, tekan sekali peristiwa Cloud Firestore secara manual.
    • Konektivitas - implementasi ini mengukur konektivitas ke Realtime Database, bukan ke Cloud Firestore. Jika status koneksi ke setiap database tidak sama, solusi ini mungkin melaporkan kondisi kehadiran yang salah.
    • Android - di Android, Realtime Database terputus dari backend setelah tidak aktif selama 60 detik. Tidak aktif berarti tidak ada pemroses terbuka atau operasi yang tertunda. Agar koneksi tetap terbuka, sebaiknya tambahkan pemroses peristiwa nilai ke lokasi selain .info/connected. Misalnya, Anda bisa melakukan FirebaseDatabase.getInstance().getReference((new Date()).toString()).keepSynced() di awal setiap sesi. Untuk mengetahui informasi lebih lanjut, baca Mendeteksi Status Koneksi.