Join us for Firebase Summit on November 10, 2021. Tune in to learn how Firebase can help you accelerate app development, release with confidence, and scale with ease. Register

Membaca dan Menulis Data di Android

Dokumen ini membahas dasar-dasar membaca dan menulis data Firebase.

Data Firebase dituliskan ke referensi FirebaseDatabase dan diambil dengan menambahkan pemroses asinkron ke referensi tersebut. Pemroses dipicu satu kali untuk status awal data, dan dipicu lagi setiap kali data berubah.

(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 teremulasi, serta secara opsional dengan resource project teremulasi (fungsi, database lain, dan aturan keamanan).

Anda hanya perlu beberapa langkah untuk menggunakan emulator Realtime Database:

  1. Menambahkan baris kode ke konfigurasi pengujian aplikasi untuk terhubung ke emulator.
  2. Menjalankan firebase emulators:start dari root direktori project lokal Anda.
  3. Melakukan panggilan dari kode prototipe aplikasi Anda menggunakan SDK platform Realtime Database seperti biasa, atau menggunakan REST API Realtime Database.

Panduan mendetail yang mencakup Realtime Database dan Cloud Functions telah tersedia. Sebaiknya baca juga Pengantar Local Emulator Suite.

Mendapatkan DatabaseReference

Untuk membaca atau menulis data dari database, Anda memerlukan instance DatabaseReference:

Java

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

Kotlin+KTX

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Menulis data

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:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Meneruskan objek Java kustom, jika class yang menentukannya memiliki konstruktor default yang tidak membutuhkan argumen, dan memiliki pengambil publik untuk properti yang akan ditetapkan.

Jika Anda menggunakan objek Java, konten objek akan otomatis dipetakan ke lokasi turunan secara bertingkat. Penggunaan objek Java biasanya juga membuat kode Anda lebih mudah dibaca dan dipelihara. Misalnya, jika Anda memiliki aplikasi dengan profil pengguna dasar, objek User mungkin akan terlihat sebagai berikut:

Java

@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

}

Kotlin+KTX

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Anda dapat menambahkan pengguna dengan setValue() sebagai berikut:

Java

public void writeNewUser(String userId, String name, String email) {
    User user = new User(name, email);

    mDatabase.child("users").child(userId).setValue(user);
}

Kotlin+KTX

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Penggunaan setValue() dengan cara 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 profilnya, Anda dapat memperbarui nama pengguna sebagai berikut:

Java

mDatabase.child("users").child(userId).child("username").setValue(name);

Kotlin+KTX

database.child("users").child(userId).child("username").setValue(name)

Membaca data

Membaca data dengan pemroses persisten

Untuk membaca data di suatu jalur dan memproses perubahan, gunakan metode addValueEventListener() untuk menambahkan ValueEventListener ke DatabaseReference.

Pemroses Callback peristiwa Penggunaan standar
ValueEventListener onDataChange() Membaca dan memproses perubahan pada seluruh konten di sebuah jalur.

Anda dapat menggunakan metode onDataChange() untuk membaca snapshot statis konten di jalur tertentu, sebagaimana adanya konten tersebut ketika peristiwa terjadi. Metode ini terpicu satu kali ketika pemroses ditambahkan 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 getValue() dipanggil pada snapshot tersebut.

Contoh berikut menampilkan aplikasi blogging sosial yang mengambil detail suatu postingan dari database:

Java

ValueEventListener postListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // Get Post object and use the values to update the UI
        Post post = dataSnapshot.getValue(Post.class);
        // ..
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
    }
};
mPostReference.addValueEventListener(postListener);

Kotlin+KTX

val postListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        // Get Post object and use the values to update the UI
        val post = dataSnapshot.getValue<Post>()
        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
    }
}
postReference.addValueEventListener(postListener)

Pemroses akan menerima DataSnapshot yang berisi data di lokasi yang ditentukan dalam database saat peristiwa terjadi. Pemanggilan getValue() pada snapshot akan menampilkan perwakilan objek Java data tersebut. Jika tidak ada data di lokasi, null akan ditampilkan saat getValue() dipanggil.

Dalam contoh ini, ValueEventListener juga menentukan metode onCancelled() yang dipanggil jika operasi baca dibatalkan. Misalnya, proses baca dapat dibatalkan jika klien tidak memiliki izin untuk membaca dari lokasi database Firebase. Metode ini mendapatkan objek DatabaseError yang menunjukkan alasan terjadinya kegagalan.

Membaca data sekali

Membaca sekali menggunakan get()

SDK dirancang untuk mengelola interaksi dengan server database, baik saat aplikasi Anda online maupun offline.

Biasanya, Anda harus menggunakan teknik ValueEventListener yang dijelaskan di atas untuk membaca data agar mendapatkan notifikasi terkait perubahan 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 tidak ditemukan.

Penggunaan get() yang tidak perlu dapat meningkatkan penggunaan bandwidth dan menyebabkan penurunan performa, yang dapat dicegah menggunakan pemroses realtime seperti yang ditunjukkan di atas.

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

Kotlin+KTX

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Membaca sekali menggunakan pemroses

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 addListenerForSingleValueEvent untuk segera 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 pengguna mulai membuat postingan baru.

Memperbarui atau menghapus data

Memperbarui kolom tertentu

Untuk menulis secara simultan ke turunan tertentu sebuah node tanpa menimpa node turunan yang lain, gunakan metode updateChildren().

Saat memanggil updateChildren(), Anda dapat memperbarui nilai turunan pada level yang lebih rendah dengan menetapkan 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, sebuah aplikasi blogging sosial mungkin memiliki class Post seperti ini:

Java

@IgnoreExtraProperties
public class Post {

    public String uid;
    public String author;
    public String title;
    public String body;
    public int starCount = 0;
    public Map<String, Boolean> stars = new HashMap<>();

    public Post() {
        // Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

    public Post(String uid, String author, String title, String body) {
        this.uid = uid;
        this.author = author;
        this.title = title;
        this.body = body;
    }

    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("author", author);
        result.put("title", title);
        result.put("body", body);
        result.put("starCount", starCount);
        result.put("stars", stars);

        return result;
    }
}

Kotlin+KTX

@IgnoreExtraProperties
data class Post(
    var uid: String? = "",
    var author: String? = "",
    var title: String? = "",
    var body: String? = "",
    var starCount: Int = 0,
    var stars: MutableMap<String, Boolean> = HashMap()
) {

    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
                "uid" to uid,
                "author" to author,
                "title" to title,
                "body" to body,
                "starCount" to starCount,
                "stars" to stars
        )
    }
}

Untuk membuat postingan dan memperbaruinya ke feed aktivitas terbaru sekaligus ke feed aktivitas pengguna yang memposting, aplikasi blogging tersebut menggunakan kode seperti ini:

Java

private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

Kotlin+KTX

private fun writeNewPost(userId: String, username: String, title: String, body: String) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    val key = database.child("posts").push().key
    if (key == null) {
        Log.w(TAG, "Couldn't get push key for posts")
        return
    }

    val post = Post(userId, username, title, body)
    val postValues = post.toMap()

    val childUpdates = hashMapOf<String, Any>(
            "/posts/$key" to postValues,
            "/user-posts/$userId/$key" to postValues
    )

    database.updateChildren(childUpdates)
}

Contoh ini menggunakan push() untuk membuat postingan dalam node yang berisi postingan bagi 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 bersamaan ke beberapa lokasi di pohon JSON dengan sekali panggilan ke updateChildren(), 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 tahu kapan data telah di-commit, Anda bisa menambahkan pemroses penyelesaian. setValue() dan updateChildren() membutuhkan pemroses penyelesaian opsional yang dipanggil ketika operasi tulis telah berhasil di-commit ke database. Jika panggilan tidak berhasil, pemroses akan diberi objek error yang menunjukkan penyebab kegagalannya.

Java

mDatabase.child("users").child(userId).setValue(user)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Write was successful!
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Write failed
                // ...
            }
        });

Kotlin+KTX

database.child("users").child(userId).setValue(user)
        .addOnSuccessListener {
            // Write was successful!
            // ...
        }
        .addOnFailureListener {
            // Write failed
            // ...
        }

Menghapus data

Cara termudah untuk menghapus data adalah dengan memanggil removeValue() pada referensi ke lokasi data tersebut.

Penghapusan juga dapat dilakukan dengan menentukan null sebagai nilai untuk operasi tulis lainnya, seperti setValue() atau updateChildren(). Teknik ini dapat digunakan dengan updateChildren() untuk menghapus beberapa turunan dengan satu panggilan API.

Melepas pemroses

Callback dihapus dengan memanggil metode removeEventListener() pada referensi database Firebase.

Jika telah ditambahkan beberapa kali ke lokasi data, pemroses akan dipanggil beberapa kali untuk setiap peristiwa, dan Anda harus melepasnya dalam jumlah yang sama seperti saat menambahkannya agar terhapus semuanya.

Memanggil removeEventListener() pada pemroses induk tidak akan menghapus pemroses yang terdaftar pada node turunannya secara otomatis. removeEventListener() 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 inkremental, Anda dapat menggunakan operasi transaksi. Operasi ini menggunakan dua argumen: fungsi pembaruan dan callback penyelesaian opsional. Fungsi pembaruan mengambil status data saat ini sebagai argumen, dan akan menampilkan status baru yang diinginkan untuk Anda tulis. Jika ada klien lain yang melakukan penulisan ke lokasi sebelum nilai baru Anda berhasil ditulis, fungsi pembaruan Anda akan dipanggil lagi dengan nilai baru saat ini, dan penulisan 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:

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @Override
        public Transaction.Result doTransaction(MutableData mutableData) {
            Post p = mutableData.getValue(Post.class);
            if (p == null) {
                return Transaction.success(mutableData);
            }

            if (p.stars.containsKey(getUid())) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1;
                p.stars.remove(getUid());
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1;
                p.stars.put(getUid(), true);
            }

            // Set value and report transaction success
            mutableData.setValue(p);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean committed,
                               DataSnapshot currentData) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

Kotlin+KTX

private fun onStarClicked(postRef: DatabaseReference) {
    // ...
    postRef.runTransaction(object : Transaction.Handler {
        override fun doTransaction(mutableData: MutableData): Transaction.Result {
            val p = mutableData.getValue(Post::class.java)
                    ?: return Transaction.success(mutableData)

            if (p.stars.containsKey(uid)) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1
                p.stars.remove(uid)
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1
                p.stars[uid] = true
            }

            // Set value and report transaction success
            mutableData.value = p
            return Transaction.success(mutableData)
        }

        override fun onComplete(
                databaseError: DatabaseError?,
                committed: Boolean,
                currentData: DataSnapshot?
        ) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
        }
    })
}

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 tidak berlaku. Jika transaksi ditolak, server akan menampilkan nilai saat ini ke klien, yang akan menjalankan lagi transaksi tersebut dengan nilai yang telah diperbarui. Proses ini akan berulang hingga transaksi diterima atau ada terlalu banyak percobaan yang 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 tersebut memberi bintang pada postingan, kita dapat menggunakan operasi pertambahan inkremental atomik, bukan transaksi.

Java

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

Kotlin+KTX

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = HashMap()
    updates["posts/$key/stars/$uid"] = true
    updates["posts/$key/starCount"] = ServerValue.increment(1)
    updates["user-posts/$uid/$key/stars/$uid"] = true
    updates["user-posts/$uid/$key/starCount"] = ServerValue.increment(1)
    database.updateChildren(updates)
}

Kode ini tidak menggunakan operasi transaksi, sehingga tidak otomatis dijalankan ulang jika ada perubahan 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 mempertahankan setiap data versi internalnya sendiri yang menggunakan pemroses atau yang ditandai agar tetap sinkron dengan server. Saat data dibaca atau ditulis, versi lokal data ini digunakan terlebih dahulu. Selanjutnya, klien Firebase menyinkronkan data tersebut dengan server database di tempat lain, dan dengan klien lain berdasarkan "upaya terbaik".

Akibatnya, semua operasi tulis ke database akan segera memicu peristiwa lokal, sebelum ada interaksi dengan server. Artinya, aplikasi Anda akan tetap responsif, apa pun kondisi latensi atau konektivitas jaringannya.

Setelah terhubung kembali ke jaringan, aplikasi Anda akan menerima kumpulan peristiwa yang sesuai, sehingga klien dapat menyinkronkannya dengan status server saat ini, tanpa harus menulis kode kustom.

Kita akan membahas lebih lanjut perilaku offline dalam artikel Mempelajari lebih lanjut kemampuan online dan offline.

Langkah berikutnya