Aplikasi Firebase tetap berfungsi bahkan ketika aplikasi Anda kehilangan koneksi jaringan untuk sementara. Selain itu, Firebase menyediakan alat untuk mempertahankan data secara lokal, mengelola kehadiran, dan menangani latensi.
Persistensi Disk
Aplikasi Firebase secara otomatis menangani gangguan sementara pada jaringan. Data yang tersimpan di cache akan tersedia saat offline, dan Firebase akan mengirim ulang setiap operasi tulis saat terhubung kembali ke jaringan.
Jika persistensi disk diaktifkan, aplikasi Anda akan menulis data secara lokal ke perangkat agar dapat mempertahankan status saat offline, bahkan saat aplikasi dimulai ulang oleh pengguna atau sistem operasi.
Anda dapat mengaktifkan persistensi disk hanya dengan satu baris kode.
Kotlin
Firebase.database.setPersistenceEnabled(true)
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
Perilaku Persistensi
Dengan mengaktifkan persistensi, data apa pun yang disinkronkan oleh klien Firebase Realtime Database saat online akan dipertahankan di disk dan tersedia secara offline, bahkan saat aplikasi dimulai ulang oleh pengguna atau sistem operasi. Artinya, aplikasi Anda berfungsi seperti halnya ketika online dengan menggunakan data lokal yang disimpan dalam cache. Callback pemroses akan terus aktif untuk pembaruan lokal.
Klien Firebase Realtime Database secara otomatis menyimpan antrean semua operasi tulis yang dijalankan saat aplikasi sedang offline. Saat persistensi diaktifkan, antrean ini juga akan dipertahankan di disk sehingga semua operasi tulis akan tersedia saat aplikasi dimulai ulang oleh pengguna atau sistem operasi. Setelah aplikasi kembali terhubung, semua operasi tersebut akan dikirim ke server Firebase Realtime Database.
Jika aplikasi Anda menggunakan Firebase Authentication, klien Firebase Realtime Database akan mempertahankan token autentikasi pengguna setiap kali aplikasi dimulai ulang. Jika masa berlaku token autentikasi habis selagi aplikasi offline, klien akan menjeda operasi tulis hingga aplikasi Anda mengautentikasi ulang pengguna. Jika tidak, operasi tulis mungkin akan gagal akibat aturan keamanan.
Menjaga Data Tetap Baru
Firebase Realtime Database menyinkronkan dan menyimpan salinan lokal data untuk pemroses aktif. Selain itu, Anda dapat terus menyinkronkan lokasi tertentu.
Kotlin
val scoresRef = Firebase.database.getReference("scores") scoresRef.keepSynced(true)
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.keepSynced(true);
Klien Firebase Realtime Database akan otomatis mendownload data di lokasi tersebut dan terus menyinkronkannya meskipun referensi tidak memiliki pemroses aktif. Anda dapat kembali menonaktifkan sinkronisasi dengan baris kode berikut.
Kotlin
scoresRef.keepSynced(false)
Java
scoresRef.keepSynced(false);
Secara default, data sebesar 10 MB yang telah disinkronkan sebelumnya akan disimpan ke dalam cache. Jumlah ini tentunya cukup untuk sebagian besar aplikasi. Jika ukuran cache melebihi ukuran yang dikonfigurasi, Firebase Realtime Database akan menghapus permanen data yang paling lama tidak digunakan. Data yang terus disinkronkan tidak akan dihapus secara permanen dari cache.
Memproses Kueri Data secara Offline
Firebase Realtime Database menyimpan data yang ditampilkan dari kueri untuk digunakan saat offline. Untuk kueri yang dibuat saat offline, Firebase Realtime Database akan terus berfungsi untuk data yang dimuat sebelumnya. Jika data yang diminta belum dimuat, Firebase Realtime Database akan memuat data dari cache lokal. Setelah koneksi jaringan kembali tersedia, data akan dimuat dan mencerminkan kueri tersebut.
Misalnya, kode berikut memproses kueri untuk empat item terakhir dalam Firebase Realtime Database skor
Kotlin
val scoresRef = Firebase.database.getReference("scores") scoresRef.orderByValue().limitToLast(4).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
Asumsikan pengguna kehilangan koneksi, statusnya menjadi offline, dan memulai ulang aplikasi. Selagi masih offline, aplikasi akan memproses kueri untuk dua item terakhir dari lokasi yang sama. Kueri ini akan berhasil menampilkan dua item terakhir karena keempat item telah dimuat dalam kueri di atas.
Kotlin
scoresRef.orderByValue().limitToLast(2).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
Java
scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
Pada contoh sebelumnya, klien Firebase Realtime Database memunculkan peristiwa 'child added' untuk dua dinosaurus dengan skor tertinggi lewat cache yang disimpan. Namun, klien tidak akan memunculkan peristiwa 'value', karena aplikasi tidak pernah menjalankan kueri tersebut selagi online.
Jika ingin meminta keenam item terakhir selagi offline, aplikasi akan langsung mendapatkan peristiwa 'child added' untuk keempat item yang disimpan dalam cache. Saat perangkat kembali online, klien Firebase Realtime Database akan melakukan sinkronisasi dengan server dan mendapatkan dua peristiwa 'child added' dan 'value' terakhir untuk aplikasi.
Menangani Transaksi saat Offline
Setiap transaksi yang dijalankan sewaktu aplikasi offline akan dimasukkan ke dalam antrean. Setelah aplikasi kembali online, transaksi akan dikirim ke server Realtime Database.
Mengelola Kehadiran
Pada aplikasi real-time, mendeteksi kapan klien membuat dan memutus koneksi sering kali bermanfaat. Misalnya, mungkin Anda ingin menandai pengguna sebagai 'offline' saat kliennya memutus koneksi.
Klien Firebase Database menyediakan sejumlah primitif sederhana yang dapat Anda gunakan untuk menulis ke database saat suatu klien memutuskan koneksi ke server Firebase Database. Update ini akan terjadi, terlepas dari apakah klien memutuskan koneksi secara bersih atau tidak, sehingga Anda dapat mengandalkan update ini untuk membersihkan data, meskipun koneksi terputus atau klien mengalami error. Semua operasi tulis, termasuk penyetelan, update, dan penghapusan, dapat dilakukan setelah koneksi terputus.
Berikut adalah contoh sederhana penulisan data setelah koneksi terputus menggunakan primitif onDisconnect
:
Kotlin
val presenceRef = Firebase.database.getReference("disconnectmessage") // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!")
Java
DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!");
Cara Kerja onDisconnect
Saat Anda membuat operasi onDisconnect()
, operasi tersebut akan berlangsung di server Firebase Realtime Database. Server akan memeriksa keamanan untuk memastikan
bahwa pengguna dapat menjalankan peristiwa tulis yang diminta, dan memberi tahu
aplikasi Anda jika operasi tersebut tidak valid. Selanjutnya, server akan
memantau koneksinya. Jika waktu koneksi habis, atau koneksi ditutup secara aktif
oleh klien Realtime Database, server akan memeriksa keamanan
sekali lagi (untuk memastikan operasi masih valid), kemudian memanggil
peristiwa tersebut.
Aplikasi Anda dapat menggunakan callback di operasi tulis untuk memastikan onDisconnect
terpasang dengan benar:
Kotlin
presenceRef.onDisconnect().removeValue { error, reference -> error?.let { Log.d(TAG, "could not establish onDisconnect event: ${error.message}") } }
Java
presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, @NonNull DatabaseReference reference) { if (error != null) { Log.d(TAG, "could not establish onDisconnect event:" + error.getMessage()); } } });
Peristiwa onDisconnect
juga dapat dibatalkan dengan memanggil .cancel()
:
Kotlin
val onDisconnectRef = presenceRef.onDisconnect() onDisconnectRef.setValue("I disconnected") // ... // some time later when we change our minds // ... onDisconnectRef.cancel()
Java
OnDisconnect onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.setValue("I disconnected"); // ... // some time later when we change our minds // ... onDisconnectRef.cancel();
Mendeteksi Status Koneksi
Untuk banyak fitur yang terkait dengan kehadiran, ada baiknya jika aplikasi mengetahui
apakah status koneksi sedang online atau offline. Firebase Realtime Database
menyediakan lokasi khusus di /.info/connected
yang
diperbarui setiap kali status koneksi klien Firebase Realtime Database
berubah. Berikut ini contohnya:
Kotlin
val connectedRef = Firebase.database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue(Boolean::class.java) ?: false if (connected) { Log.d(TAG, "connected") } else { Log.d(TAG, "not connected") } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
Java
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { Log.d(TAG, "connected"); } else { Log.d(TAG, "not connected"); } } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
/.info/connected
adalah nilai boolean yang tidak disinkronkan antarklien Realtime Database karena nilainya bergantung pada status klien. Dengan kata lain, jika satu klien membaca /.info/connected
sebagai 'false', tidak ada jaminan bahwa klien lain juga akan membacanya sebagai 'false'.
Pada Android, Firebase otomatis mengelola status koneksi untuk mengurangi penggunaan baterai dan bandwidth. Jika klien tidak memiliki pemroses yang aktif, tidak memiliki operasi tulis atau operasi onDisconnect
yang tertunda, dan tidak diputus secara eksplisit oleh metode goOffline
, Firebase akan menutup koneksi jika setelah 60 detik tidak ada aktivitas.
Menangani Latensi
Stempel Waktu Server
Server Firebase Realtime Database memberikan mekanisme
untuk menyisipkan stempel waktu yang dibuat di server sebagai data. Fitur ini, bersama dengan onDisconnect
, memberikan cara mudah dan andal untuk mencatat kapan koneksi klien Realtime Database diputus:
Kotlin
val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline") userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)
Java
DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
Clock Skew
Meskipun firebase.database.ServerValue.TIMESTAMP
jauh lebih akurat
dan lebih cocok untuk sebagian besar operasi baca/tulis,
terkadang ada baiknya memperkirakan clock skew klien
terhadap server Firebase Realtime Database. Anda
dapat menambahkan callback ke lokasi /.info/serverTimeOffset
untuk mendapatkan nilai, dalam milidetik, yang ditambahkan oleh klien Firebase Realtime Database
ke waktu lokal yang dilaporkan (waktu epoch dalam milidetik) untuk memperkirakan
waktu server. Perlu diperhatikan bahwa akurasi selisih ini dapat dipengaruhi oleh latensi jaringan, sehingga hanya bermanfaat untuk menemukan selisih waktu jam yang besar (> 1 detik).
Kotlin
val offsetRef = Firebase.database.getReference(".info/serverTimeOffset") offsetRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val offset = snapshot.getValue(Double::class.java) ?: 0.0 val estimatedServerTimeMs = System.currentTimeMillis() + offset } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
Java
DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset"); offsetRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { double offset = snapshot.getValue(Double.class); double estimatedServerTimeMs = System.currentTimeMillis() + offset; } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
Contoh Aplikasi Kehadiran
Dengan menggabungkan operasi pemutusan koneksi dengan pemantauan status koneksi dan stempel waktu server, Anda dapat membuat suatu sistem kehadiran pengguna. Dalam sistem ini, setiap pengguna menyimpan data di lokasi basis data untuk menunjukkan apakah klien Realtime Database sedang online atau tidak. Klien akan menetapkan lokasi ini ke benar (true) ketika mulai online dan ke stempel waktu ketika memutuskan koneksi. Stempel waktu ini menunjukkan kapan terakhir kali pengguna tersebut online.
Perlu diperhatikan bahwa aplikasi Anda harus mengantrekan operasi pemutusan koneksi sebelum pengguna ditandai sebagai online, untuk menghindari kondisi race yang dapat terjadi jika koneksi jaringan klien terputus sebelum kedua perintah dapat dikirimkan ke server.
Berikut adalah contoh sederhana dari sistem kehadiran pengguna:
Kotlin
// Since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline val database = Firebase.database val myConnectionsRef = database.getReference("users/joe/connections") // Stores the timestamp of my last disconnect (the last time I was seen online) val lastOnlineRef = database.getReference("/users/joe/lastOnline") val connectedRef = database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue<Boolean>() ?: false if (connected) { val con = myConnectionsRef.push() // When this device disconnects, remove it con.onDisconnect().removeValue() // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP) // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(java.lang.Boolean.TRUE) } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled at .info/connected") } })
Java
// Since I can connect from multiple devices, we store each connection instance separately // any time that connectionsRef's value is null (i.e. has no children) I am offline final FirebaseDatabase database = FirebaseDatabase.getInstance(); final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections"); // Stores the timestamp of my last disconnect (the last time I was seen online) final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline"); final DatabaseReference connectedRef = database.getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { DatabaseReference con = myConnectionsRef.push(); // When this device disconnects, remove it con.onDisconnect().removeValue(); // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP); // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(Boolean.TRUE); } } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled at .info/connected"); } });