(Opsional) Membuat prototipe dan melakukan pengujian dengan Firebase Local Emulator Suite
Sebelum membahas cara aplikasi Anda membaca dari dan menulis ke Realtime Database, kami akan memperkenalkan serangkaian alat yang dapat digunakan untuk membuat prototipe dan menguji fungsionalitas Realtime Database: Firebase Local Emulator Suite. Jika Anda sedang mencoba berbagai model data, mengoptimalkan aturan keamanan, atau berupaya menemukan cara yang paling hemat untuk berinteraksi dengan backend, kemampuan untuk bekerja secara lokal tanpa men-deploy layanan langsung dapat sangat bermanfaat.
Emulator Realtime Database adalah bagian dari Local Emulator Suite yang memungkinkan aplikasi berinteraksi dengan konfigurasi dan konten database yang diemulasi, serta secara opsional dengan resource project yang diemulasi (fungsi, database lain, dan aturan keamanan).
Anda hanya perlu beberapa langkah untuk menggunakan emulator Realtime Database:
- Menambahkan satu baris kode ke konfigurasi pengujian aplikasi untuk terhubung ke emulator.
- Menjalankan
firebase emulators:start
dari root direktori project lokal Anda. - Melakukan panggilan dari kode prototipe aplikasi Anda menggunakan SDK platform Realtime Database seperti biasa, atau menggunakan Realtime Database REST API.
Panduan mendetail yang mencakup Realtime Database dan Cloud Functions telah tersedia. Sebaiknya baca juga Pengantar Local Emulator Suite.
Mendapatkan referensi database
Untuk membaca atau menulis data dari database, Anda memerlukan instance firebase.database.Reference
:
API modular web
import { getDatabase } from "firebase/database"; const database = getDatabase();
API dengan namespace web
var database = firebase.database();
Menulis data
Dokumen ini mencakup dasar-dasar pengambilan data, serta cara mengurutkan dan memfilter data Firebase.
Data Firebase diambil dengan menambahkan pemroses asinkron ke firebase.database.Reference
. Pemroses dipicu sekali untuk status awal data, dan dipicu kembali setiap kali data berubah.
Operasi tulis dasar
Untuk operasi tulis dasar, Anda dapat menggunakan set()
untuk menyimpan data ke referensi yang ditentukan, sehingga menggantikan data yang ada di jalur tersebut. Misalnya, aplikasi blogging sosial dapat menambahkan pengguna dengan set()
seperti berikut:
API modular web
import { getDatabase, ref, set } from "firebase/database"; function writeUserData(userId, name, email, imageUrl) { const db = getDatabase(); set(ref(db, 'users/' + userId), { username: name, email: email, profile_picture : imageUrl }); }
API dengan namespace web
function writeUserData(userId, name, email, imageUrl) { firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }); }
Jika set()
digunakan, data di lokasi yang ditentukan akan ditimpa, termasuk semua node turunan.
Membaca data
Memproses peristiwa nilai
Untuk membaca data di suatu jalur dan memproses perubahan, gunakan onValue()
untuk mengamati peristiwa. Anda dapat menggunakan peristiwa ini untuk membaca snapshot statis konten di jalur tertentu, sesuai keberadaan konten tersebut saat peristiwa terjadi. Metode ini terpicu satu kali ketika pemroses terpasang, dan terpicu lagi setiap kali terjadi perubahan pada data, termasuk pada setiap turunannya. Callback peristiwa mendapatkan snapshot yang berisi semua data di lokasi tersebut, termasuk data turunan. Jika tidak ada data, snapshot akan menampilkan false
ketika exists()
dipanggil, serta menampilkan null
ketika val()
dipanggil pada snapshot tersebut.
Contoh berikut menunjukkan aplikasi blogging sosial yang mengambil jumlah bintang suatu postingan dari database:
API modular web
import { getDatabase, ref, onValue } from "firebase/database"; const db = getDatabase(); const starCountRef = ref(db, 'posts/' + postId + '/starCount'); onValue(starCountRef, (snapshot) => { const data = snapshot.val(); updateStarCount(postElement, data); });
API dengan namespace web
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount'); starCountRef.on('value', (snapshot) => { const data = snapshot.val(); updateStarCount(postElement, data); });
Pemroses akan menerima snapshot
yang berisi data di lokasi yang ditentukan dalam database saat peristiwa terjadi. Anda dapat mengambil data dalam snapshot
dengan metode val()
.
Membaca data sekali
Membaca data sekali dengan get()
SDK ini didesain untuk mengelola interaksi dengan server database, baik saat aplikasi Anda online maupun offline.
Biasanya, Anda harus menggunakan teknik peristiwa nilai yang dijelaskan di atas untuk membaca data agar mendapatkan notifikasi terkait pembaruan data dari backend. Teknik pemroses mengurangi penggunaan dan penagihan Anda, serta dioptimalkan untuk memberikan pengalaman terbaik kepada pengguna saat mereka online dan offline.
Jika hanya memerlukan data satu kali, Anda dapat menggunakan get()
untuk mendapatkan snapshot data dari database. Jika karena alasan apa pun get()
tidak dapat menampilkan nilai server, klien akan menyelidiki cache penyimpanan lokal dan menampilkan error jika nilainya masih belum ditemukan.
Penggunaan get()
yang tidak perlu dapat meningkatkan penggunaan bandwidth dan menyebabkan penurunan performa. Ini dapat dicegah menggunakan pemroses realtime seperti yang ditunjukkan di atas.
API modular web
import { getDatabase, ref, child, get } from "firebase/database"; const dbRef = ref(getDatabase()); get(child(dbRef, `users/${userId}`)).then((snapshot) => { if (snapshot.exists()) { console.log(snapshot.val()); } else { console.log("No data available"); } }).catch((error) => { console.error(error); });
API dengan namespace web
const dbRef = firebase.database().ref(); dbRef.child("users").child(userId).get().then((snapshot) => { if (snapshot.exists()) { console.log(snapshot.val()); } else { console.log("No data available"); } }).catch((error) => { console.error(error); });
Membaca data sekali dengan observer
Dalam beberapa kasus, Anda mungkin menginginkan nilai dari cache lokal langsung ditampilkan, daripada memeriksa nilai yang diperbarui di server. Dalam kasus tersebut, Anda dapat menggunakan once()
untuk langsung mendapatkan data dari cache disk lokal.
Cara ini berguna untuk data yang hanya perlu dimuat sekali, dan tidak diharapkan untuk sering berubah atau memerlukan pemrosesan aktif. Misalnya, aplikasi blogging pada contoh sebelumnya menggunakan metode ini untuk memuat profil pengguna ketika mulai membuat postingan baru:
API modular web
import { getDatabase, ref, onValue } from "firebase/database"; import { getAuth } from "firebase/auth"; const db = getDatabase(); const auth = getAuth(); const userId = auth.currentUser.uid; return onValue(ref(db, '/users/' + userId), (snapshot) => { const username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... }, { onlyOnce: true });
API dengan namespace web
var userId = firebase.auth().currentUser.uid; return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => { var username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... });
Memperbarui atau menghapus data
Memperbarui kolom tertentu
Untuk menulis secara simultan ke turunan tertentu sebuah node tanpa menimpa node turunan yang lain, gunakan metode update()
.
Saat memanggil update()
, Anda dapat memperbarui nilai turunan di level yang lebih rendah dengan menentukan jalur untuk kunci. Jika data disimpan dalam beberapa lokasi agar dapat melakukan penskalaan yang lebih baik, Anda dapat memperbarui semua instance data tersebut menggunakan fan-out data.
Misalnya, aplikasi blogging sosial dapat membuat postingan dan mengupdatenya secara bersamaan ke feed aktivitas terbaru dan feed aktivitas pengguna pengirim postingan menggunakan kode seperti ini:
API modular web
import { getDatabase, ref, child, push, update } from "firebase/database"; function writeNewPost(uid, username, picture, title, body) { const db = getDatabase(); // A post entry. const postData = { author: username, uid: uid, body: body, title: title, starCount: 0, authorPic: picture }; // Get a key for a new Post. const newPostKey = push(child(ref(db), 'posts')).key; // Write the new post's data simultaneously in the posts list and the user's post list. const updates = {}; updates['/posts/' + newPostKey] = postData; updates['/user-posts/' + uid + '/' + newPostKey] = postData; return update(ref(db), updates); }
API dengan namespace web
function writeNewPost(uid, username, picture, title, body) { // A post entry. var postData = { author: username, uid: uid, body: body, title: title, starCount: 0, authorPic: picture }; // Get a key for a new Post. var newPostKey = firebase.database().ref().child('posts').push().key; // Write the new post's data simultaneously in the posts list and the user's post list. var updates = {}; updates['/posts/' + newPostKey] = postData; updates['/user-posts/' + uid + '/' + newPostKey] = postData; return firebase.database().ref().update(updates); }
Contoh ini menggunakan push()
untuk membuat postingan dalam node yang berisi postingan bagi semua pengguna di /posts/$postid
dan sekaligus mengambil kunci. Selanjutnya, kunci tersebut dapat digunakan untuk membuat entri kedua di postingan pengguna pada /user-posts/$userid/$postid
.
Dengan menggunakan jalur tersebut, Anda dapat menjalankan pembaruan simultan ke beberapa lokasi di hierarki JSON dengan satu panggilan ke update()
, seperti yang digunakan pada contoh ini untuk membuat postingan baru di kedua lokasi. Pembaruan simultan yang dilakukan dengan cara ini bersifat atomik: semuanya akan berhasil atau semuanya akan gagal.
Menambahkan Callback Penyelesaian
Jika ingin mengetahui kapan data di-commit, Anda bisa menambahkan callback penyelesaian. set()
dan update()
menggunakan callback penyelesaian opsional yang akan dipanggil ketika operasi tulis telah di-commit ke database. Jika panggilan tidak berhasil, objek error akan diteruskan ke callback untuk menunjukkan penyebab kegagalan.
API modular web
import { getDatabase, ref, set } from "firebase/database"; const db = getDatabase(); set(ref(db, 'users/' + userId), { username: name, email: email, profile_picture : imageUrl }) .then(() => { // Data saved successfully! }) .catch((error) => { // The write failed... });
API dengan namespace web
firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }, (error) => { if (error) { // The write failed... } else { // Data saved successfully! } });
Menghapus data
Cara termudah untuk menghapus data adalah dengan memanggil remove()
pada referensi ke lokasi data tersebut.
Penghapusan juga dapat dilakukan dengan menentukan null
sebagai nilai untuk operasi tulis lainnya, seperti set()
atau update()
. Teknik ini dapat digunakan dengan update()
untuk menghapus beberapa turunan dengan satu panggilan API.
Menerima Promise
Untuk mengetahui kapan data di-commit ke server Firebase Realtime Database, Anda dapat menggunakan Promise
.
set()
dan update()
dapat menampilkan Promise
yang dapat Anda gunakan untuk mengetahui kapan penulisan di-commit ke database.
Melepas pemroses
Callback akan dihapus dengan memanggil metode off()
pada referensi database Firebase.
Anda dapat menghapus sebuah pemroses dengan meneruskannya sebagai parameter ke off()
.
Memanggil off()
pada lokasi tanpa argumen akan menghapus semua pemroses yang ada di lokasi tersebut.
Memanggil off()
pada pemroses induk tidak akan otomatis menghapus pemroses yang terdaftar pada node turunannya. off()
juga harus dipanggil pada pemroses turunan mana pun untuk menghapus callback.
Menyimpan data sebagai transaksi
Ketika menangani data yang bisa rusak karena perubahan serentak, seperti penghitung pertambahan inkremental, Anda dapat menggunakan operasi transaksi. Anda dapat memberi operasi ini fungsi pembaruan dan callback penyelesaian opsional. Fungsi pembaruan mengambil status data saat ini sebagai argumen, dan akan menampilkan status baru yang ingin Anda tulis. Jika klien lain melakukan operasi tulis ke lokasi ini sebelum nilai baru Anda berhasil ditulis, fungsi pembaruan Anda akan dipanggil lagi dengan nilai saat ini yang baru, dan operasi tulis akan dicoba ulang.
Misalnya, pada contoh aplikasi blogging sosial, Anda dapat mengizinkan pengguna memberi atau menghapus bintang pada postingan, serta memantau jumlah bintang yang telah diterima suatu postingan dengan cara berikut ini:
API modular web
import { getDatabase, ref, runTransaction } from "firebase/database"; function toggleStar(uid) { const db = getDatabase(); const postRef = ref(db, '/posts/foo-bar-123'); runTransaction(postRef, (post) => { if (post) { if (post.stars && post.stars[uid]) { post.starCount--; post.stars[uid] = null; } else { post.starCount++; if (!post.stars) { post.stars = {}; } post.stars[uid] = true; } } return post; }); }
API dengan namespace web
function toggleStar(postRef, uid) { postRef.transaction((post) => { if (post) { if (post.stars && post.stars[uid]) { post.starCount--; post.stars[uid] = null; } else { post.starCount++; if (!post.stars) { post.stars = {}; } post.stars[uid] = true; } } return post; }); }
Penggunaan transaksi dapat mencegah kesalahan penghitungan jumlah bintang jika beberapa pengguna memberi bintang pada postingan yang sama secara bersamaan, atau jika klien memiliki data yang sudah usang. Jika transaksi ditolak, server akan menampilkan nilai saat ini ke klien, yang akan mengulangi transaksi tersebut dengan nilai yang telah diperbarui. Proses ini akan berulang sampai transaksi diterima atau Anda membatalkan transaksi.
Pertambahan inkremental atomik sisi server
Dalam kasus penggunaan di atas, kita menulis dua nilai ke database: ID pengguna yang memberi/menghapus bintang pada postingan, dan pertambahan inkremental jumlah bintang. Jika sudah mengetahui bahwa pengguna memberi bintang pada postingan, kita dapat menggunakan operasi pertambahan inkremental atomik, bukan transaksi.
API modular web
function addStar(uid, key) { import { getDatabase, increment, ref, update } from "firebase/database"; const dbRef = ref(getDatabase()); const updates = {}; updates[`posts/${key}/stars/${uid}`] = true; updates[`posts/${key}/starCount`] = increment(1); updates[`user-posts/${key}/stars/${uid}`] = true; updates[`user-posts/${key}/starCount`] = increment(1); update(dbRef, updates); }
API dengan namespace web
function addStar(uid, key) { const updates = {}; updates[`posts/${key}/stars/${uid}`] = true; updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); updates[`user-posts/${key}/stars/${uid}`] = true; updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); firebase.database().ref().update(updates); }
Kode ini tidak menggunakan operasi transaksi, sehingga tidak otomatis dijalankan ulang jika ada pembaruan yang bertentangan. Namun, karena operasi pertambahan inkremental terjadi langsung di server database, tidak ada kemungkinan konflik.
Jika ingin mendeteksi dan menolak konflik khusus aplikasi, misalnya pengguna memberi bintang pada postingan yang sebelumnya telah dibintanginya, Anda harus menulis aturan keamanan khusus untuk kasus penggunaan tersebut.
Menangani data secara offline
Jika koneksi jaringan klien terputus, aplikasi Anda akan tetap berfungsi dengan baik.
Setiap klien yang terhubung ke database Firebase menyimpan versi internalnya sendiri untuk setiap data aktif. Ketika ditulis, data akan dituliskan ke versi lokal ini terlebih dahulu. Selanjutnya, klien Firebase menyinkronkan data tersebut dengan server remote database, dan dengan klien lain berdasarkan "upaya terbaik".
Akibatnya, semua operasi tulis ke database akan langsung memicu peristiwa lokal, sebelum ada data yang dituliskan ke server. Ini berarti aplikasi Anda akan tetap responsif, apa pun kondisi konektivitas atau latensi jaringannya.
Setelah terhubung kembali ke jaringan, aplikasi Anda akan menerima kumpulan peristiwa yang sesuai agar klien melakukan sinkronisasi dengan kondisi server saat ini, tanpa harus menulis kode khusus.
Kita akan membahas lebih lanjut perilaku offline dalam artikel Mempelajari lebih lanjut kemampuan online dan offline.
Langkah berikutnya
- Menangani daftar data
- Mempelajari cara membuat struktur data
- Mempelajari lebih lanjut kemampuan online dan offline