(Opsional) Membuat prototipe dan melakukan pengujian dengan Firebase Local Emulator Suite
Sebelum membahas cara aplikasi Anda membaca dari dan menulis ke Realtime Database, kenali Firebase Local Emulator Suite yang merupakan serangkaian alat yang dapat Anda gunakan untuk membuat prototipe dan menguji fungsi Realtime Database. 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 Anda berinteraksi dengan konten dan konfigurasi database yang diemulasi, serta, jika diinginkan, dengan resource project yang diemulasi (fungsi, database lain, dan aturan keamanan).
Hanya diperlukan 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 FIRDatabaseReference
Untuk membaca atau menulis data dari database, Anda memerlukan instance FIRDatabaseReference
:
Swift
var ref: DatabaseReference! ref = Database.database().reference()
Objective-C
@property (strong, nonatomic) FIRDatabaseReference *ref; self.ref = [[FIRDatabase database] reference];
Menulis data
Dokumen ini membahas dasar-dasar dalam membaca dan menulis data Firebase.
Data Firebase dituliskan ke referensi Database
dan diambil dengan menambahkan pemroses asinkron ke referensi tersebut. Pemroses dipicu satu kali untuk status awal data, dan dipicu lagi setiap kali data berubah.
Operasi tulis dasar
Untuk operasi tulis dasar, Anda dapat menggunakan setValue
untuk menyimpan data ke referensi yang ditentukan, sehingga menggantikan data yang ada di jalur tersebut. Anda bisa menggunakan metode ini untuk:
- Meneruskan jenis yang cocok dengan jenis JSON yang tersedia berikut ini:
NSString
NSNumber
NSDictionary
NSArray
Misalnya, Anda dapat menambahkan pengguna dengan setValue
seperti berikut:
Swift
self.ref.child("users").child(user.uid).setValue(["username": username])
Objective-C
[[[self.ref child:@"users"] child:authResult.user.uid] setValue:@{@"username": username}];
Penggunaan setValue
seperti ini akan menimpa data di lokasi yang ditentukan, termasuk semua node turunan. Namun, Anda masih dapat memperbarui turunan tanpa menulis ulang seluruh objek. Jika ingin mengizinkan pengguna memperbarui profil mereka, Anda dapat memperbarui nama pengguna seperti berikut:
Swift
self.ref.child("users/\(user.uid)/username").setValue(username)
Objective-C
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];
Membaca data
Membaca data dengan mendeteksi peristiwa nilai
Untuk membaca data di jalur dan mendeteksi perubahan, gunakan observeEventType:withBlock
dari FIRDatabaseReference
untuk mengamati peristiwa FIRDataEventTypeValue
.
Jenis peristiwa | Penggunaan standar |
---|---|
FIRDataEventTypeValue |
Membaca dan memproses perubahan di seluruh konten jalur. |
Anda dapat menggunakan peristiwa FIRDataEventTypeValue
untuk membaca data di jalur tertentu, sesuai kondisi pada saat peristiwa terjadi. Metode ini dipicu satu kali saat pemroses ditambahkan dan dipicu lagi setiap kali terjadi perubahan pada data, termasuk pada setiap turunannya. Callback peristiwa diberikan snapshot
yang berisi semua data di jalur tersebut, termasuk data turunan. Jika tidak ada data, snapshot akan menampilkan false
ketika Anda memanggil exists()
dan nil
ketika Anda membaca properti value
miliknya.
Contoh berikut menunjukkan aplikasi blogging sosial yang mengambil detail suatu postingan dari database:
Swift
refHandle = postRef.observe(DataEventType.value, with: { snapshot in // ... })
Objective-C
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { NSDictionary *postDict = snapshot.value; // ... }];
Pemroses menerima FIRDataSnapshot
yang memuat data di lokasi yang ditentukan dalam database saat terjadi peristiwa di properti value
miliknya. Anda dapat menetapkan nilai ke jenis bawaan yang sesuai, seperti NSDictionary
.
Jika tidak ada data di lokasi tersebut, value
adalah nil
.
Membaca data sekali
Membaca sekali menggunakan getData()
SDK 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 tersebut mengurangi penggunaan dan penagihan Anda, serta dioptimalkan untuk memberikan pengalaman terbaik kepada pengguna saat mereka sedang online dan offline.
Jika hanya memerlukan data satu kali, Anda dapat menggunakan getData()
untuk mendapatkan snapshot data dari database. Jika karena alasan apa pun getData()
tidak dapat menampilkan nilai server, klien akan memeriksa cache penyimpanan lokal dan menampilkan error jika nilainya masih tidak ditemukan.
Contoh berikut menunjukkan pengambilan nama pengguna yang dapat dilihat publik satu kali dari database:
Swift
do { let snapshot = try await ref.child("users/\(uid)/username").getData() let userName = snapshot.value as? String ?? "Unknown" } catch { print(error) }
Objective-C
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid]; [[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) { if (error) { NSLog(@"Received an error %@", error); return; } NSString *userName = snapshot.value; }];
Penggunaan getData()
yang tidak perlu dapat meningkatkan penggunaan bandwidth dan menyebabkan penurunan performa. Ini dapat dicegah menggunakan pemroses realtime seperti yang ditunjukkan di atas.
Membaca data sekali dengan observer
Dalam beberapa kasus, Anda mungkin menginginkan nilai dari cache lokal segera ditampilkan, daripada memeriksa nilai yang diperbarui di server. Dalam kasus tersebut, Anda dapat menggunakan observeSingleEventOfType
untuk langsung mendapatkan data dari cache disk lokal.
Cara ini berguna untuk data yang hanya perlu dimuat sekali, dan tidak diharapkan sering berubah atau memerlukan pemroses aktif. Misalnya, aplikasi blogging pada contoh sebelumnya menggunakan metode ini untuk memuat profil pengguna ketika mulai membuat postingan baru:
Swift
let userID = Auth.auth().currentUser?.uid ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in // Get user value let value = snapshot.value as? NSDictionary let username = value?["username"] as? String ?? "" let user = User(username: username) // ... }) { error in print(error.localizedDescription) }
Objective-C
NSString *userID = [FIRAuth auth].currentUser.uid; [[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { // Get user value User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]]; // ... } withCancelBlock:^(NSError * _Nonnull error) { NSLog(@"%@", error.localizedDescription); }];
Memperbarui atau menghapus data
Memperbarui kolom tertentu
Untuk menulis secara simultan ke turunan tertentu dari sebuah node tanpa menimpa node turunan yang lain, gunakan metode updateChildValues
.
Saat memanggil updateChildValues
, Anda dapat memperbarui nilai turunan di level yang lebih rendah dengan menetapkan jalur untuk kunci tersebut. 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, sebuah aplikasi blogging sosial mungkin ingin membuat postingan sekaligus memperbaruinya ke feed aktivitas terbaru dan feed aktivitas pembuat postingan. Untuk melakukannya, aplikasi blogging tersebut menggunakan kode seperti ini:
Swift
guard let key = ref.child("posts").childByAutoId().key else { return } let post = ["uid": userID, "author": username, "title": title, "body": body] let childUpdates = ["/posts/\(key)": post, "/user-posts/\(userID)/\(key)/": post] ref.updateChildValues(childUpdates)
Objective-C
NSString *key = [[_ref child:@"posts"] childByAutoId].key; NSDictionary *post = @{@"uid": userID, @"author": username, @"title": title, @"body": body}; NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post, [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post}; [_ref updateChildValues:childUpdates];
Contoh ini menggunakan childByAutoId
guna membuat postingan dalam node yang berisi postingan untuk semua pengguna di /posts/$postid
, sekaligus mengambil kunci dengan getKey()
. 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 updateChildValues
, 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 Blok Penyelesaian
Jika Anda ingin mengetahui kapan data di-commit, Anda bisa menambahkan blok penyelesaian. setValue
dan updateChildValues
memerlukan blok penyelesaian opsional yang dipanggil ketika operasi tulis telah di-commit ke database. Pemroses ini dapat berguna untuk memantau data mana yang sudah disimpan dan data mana yang masih disinkronkan. Jika panggilan tidak berhasil, pemroses akan diberi objek error yang menunjukkan penyebab kegagalan tersebut.
Swift
do { try await ref.child("users").child(user.uid).setValue(["username": username]) print("Data saved successfully!") } catch { print("Data could not be saved: \(error).") }
Objective-C
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { if (error) { NSLog(@"Data could not be saved: %@", error); } else { NSLog(@"Data saved successfully."); } }];
Menghapus data
Cara termudah untuk menghapus data adalah dengan memanggil removeValue
pada referensi ke lokasi data tersebut.
Penghapusan juga dapat dilakukan dengan menentukan nil
sebagai nilai untuk operasi tulis lainnya, seperti setValue
atau updateChildValues
. Teknik ini dapat digunakan dengan updateChildValues
untuk menghapus beberapa turunan dengan satu panggilan API.
Melepas pemroses
Observer tidak akan otomatis menghentikan sinkronisasi data saat Anda meninggalkan ViewController
. Jika tidak dihapus dengan benar, observer akan terus menyinkronkan data ke memori lokal. Jika tidak lagi diperlukan, hapus observer dengan meneruskan FIRDatabaseHandle
terkait ke metode removeObserverWithHandle
.
Saat pemblokiran callback ditambahkan ke referensi, FIRDatabaseHandle
akan ditampilkan.
Handle ini dapat digunakan untuk menghapus pemblokiran callback.
Jika beberapa pemroses ditambahkan ke referensi database, setiap pemroses akan dipanggil ketika terjadi peristiwa. Untuk menghentikan sinkronisasi data di lokasi tersebut, Anda harus menghapus semua observer di sebuah lokasi dengan memanggil metode removeAllObservers
.
Memanggil removeObserverWithHandle
atau removeAllObservers
pada pemroses tidak akan otomatis menghapus pemroses yang terdaftar pada node turunannya. Anda juga harus tetap melacak referensi atau handle tersebut untuk menghapusnya.
Menyimpan data sebagai transaksi
Ketika menangani data yang bisa rusak karena perubahan serentak, seperti penghitung pertambahan inkremental, Anda dapat menggunakan operasi transaksi. Operasi ini menggunakan dua argumen: fungsi pembaruan dan callback penyelesaian opsional. Fungsi pembaruan mengambil kondisi data saat ini sebagai argumen dan menampilkan kondisi baru yang ingin Anda tuliskan.
Misalnya, pada contoh aplikasi blogging sosial ini, Anda dapat mengizinkan pengguna memberi atau menghapus bintang pada postingan, serta mengetahui berapa banyak bintang yang telah diterima suatu postingan dengan cara berikut ini:
Swift
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in if var post = currentData.value as? [String: AnyObject], let uid = Auth.auth().currentUser?.uid { var stars: [String: Bool] stars = post["stars"] as? [String: Bool] ?? [:] var starCount = post["starCount"] as? Int ?? 0 if let _ = stars[uid] { // Unstar the post and remove self from stars starCount -= 1 stars.removeValue(forKey: uid) } else { // Star the post and add self to stars starCount += 1 stars[uid] = true } post["starCount"] = starCount as AnyObject? post["stars"] = stars as AnyObject? // Set value and report transaction success currentData.value = post return TransactionResult.success(withValue: currentData) } return TransactionResult.success(withValue: currentData) }) { error, committed, snapshot in if let error = error { print(error.localizedDescription) } }
Objective-C
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) { NSMutableDictionary *post = currentData.value; if (!post || [post isEqual:[NSNull null]]) { return [FIRTransactionResult successWithValue:currentData]; } NSMutableDictionary *stars = post[@"stars"]; if (!stars) { stars = [[NSMutableDictionary alloc] initWithCapacity:1]; } NSString *uid = [FIRAuth auth].currentUser.uid; int starCount = [post[@"starCount"] intValue]; if (stars[uid]) { // Unstar the post and remove self from stars starCount--; [stars removeObjectForKey:uid]; } else { // Star the post and add self to stars starCount++; stars[uid] = @YES; } post[@"stars"] = stars; post[@"starCount"] = @(starCount); // Set value and report transaction success currentData.value = post; return [FIRTransactionResult successWithValue:currentData]; } andCompletionBlock:^(NSError * _Nullable error, BOOL committed, FIRDataSnapshot * _Nullable snapshot) { // Transaction completed if (error) { NSLog(@"%@", error.localizedDescription); } }];
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. Nilai yang dimuat pada class FIRMutableData
pada awalnya adalah nilai terakhir klien yang diketahui untuk jalur tersebut, atau nil
jika nilainya tidak ada. Server membandingkan nilai awal tersebut dengan nilai saat ini dan akan menerima transaksi jika nilainya cocok, atau menolaknya. Jika transaksi ditolak, server akan menampilkan nilai saat ini ke klien, yang akan mengulangi transaksi tersebut dengan nilai yang telah diperbarui. Proses ini berulang hingga transaksi diterima atau ada terlalu banyak percobaan yang telah dilakukan.
Pertambahan inkremental sisi server atomik
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.
Swift
let updates = [ "posts/\(postID)/stars/\(userID)": true, "posts/\(postID)/starCount": ServerValue.increment(1), "user-posts/\(postID)/stars/\(userID)": true, "user-posts/\(postID)/starCount": ServerValue.increment(1) ] as [String : Any] Database.database().reference().updateChildValues(updates)
Objective-C
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1], [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]}; [[[FIRDatabase database] reference] updateChildValues: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, sehingga klien dapat menyinkronkannya dengan kondisi server saat ini, tanpa harus menulis kode khusus.
Kita akan membahas lebih lanjut perilaku offline di bagian Mempelajari lebih lanjut kemampuan online dan offline.
Langkah Berikutnya
- Menangani daftar data
- Mempelajari cara membuat struktur data
- Mempelajari lebih lanjut kemampuan online dan offline