Membangun kehadiran di aplikasi dengan Cloud Functions

Berdasarkan jenis aplikasi yang Anda build, fitur yang dikenal sebagai pendeteksi "kehadiran" ini berguna untuk mendeteksi pengguna atau perangkat yang sedang online.

Misalnya, jika sedang mem-build aplikasi seperti jaringan sosial atau men-deploy 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 dilihat".

Firestore tidak mendukung kehadiran secara bawaan, tetapi Anda dapat memanfaatkan produk Firebase lainnya untuk mem-build sistem kehadiran.

Solusi: Cloud Functions dengan Realtime Database

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

Gunakan Realtime Database untuk melaporkan status koneksi, lalu gunakan Cloud Functions untuk menduplikasi data tersebut ke 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);
    });
});

Contoh ini adalah sistem kehadiran Realtime Database yang lengkap. Sistem ini menangani beberapa pemutusan koneksi, error, dan sebagainya.

Menghubungkan ke Firestore

Untuk mengimplementasikan solusi serupa di Firestore, gunakan kode Realtime Database yang sama, kemudian gunakan Cloud Functions untuk menjaga agar Realtime Database dan Firestore tetap sinkron.

Jika belum melakukannya, tambahkan Realtime Database ke project dan sertakan solusi kehadiran di atas.

Berikutnya, sinkronkan status kehadiran ke Firestore melalui metode berikut:

  1. Secara lokal, sinkronkan ke cache Firestore perangkat yang offline, agar aplikasi tersebut mengetahui jika perangkat sedang offline.
  2. Secara global, sinkronkan menggunakan fungsi Cloud Function agar semua perangkat lain yang sedang mengakses Firestore mengetahui bahwa perangkat tertentu ini sedang offline.

Memperbarui cache lokal Firestore

Mari kita lihat perubahan yang diperlukan untuk mengatasi masalah pertama - memperbarui cache lokal 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, dapat dipastikan bahwa status Firestore lokal akan selalu mencerminkan status online/offline perangkat. Artinya, Anda dapat memproses dokumen /status/{uid} dan menggunakan datanya untuk mengubah UI agar mencerminkan status koneksi.

Web

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

Mengupdate Firestore secara global

Meskipun aplikasi kita melaporkan kehadiran online perangkat dengan tepat kepada dirinya sendiri, status ini belum akan akurat di aplikasi Firestore lainnya karena status "offline" hanya dituliskan secara lokal dan tidak akan disinkronkan saat koneksi dipulihkan. Untuk mengatasi hal ini, kita menggunakan fungsi Cloud Function yang mengawasi jalur status/{uid} di Realtime Database. Ketika mengalami perubahan, nilai Realtime Database akan disinkronkan ke Firestore sehingga status semua pengguna akan menjadi tepat.

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

Begitu fungsi ini di-deploy, sistem kehadiran lengkap akan berjalan dengan Firestore. Di bawah ini 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 Firestore Anda merupakan langkah yang skalabel dan efektif, tetapi memiliki beberapa keterbatasan:

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