Buka konsol
Coba Cloud Firestore: Lihat database yang skalabel dan fleksibel dari Firebase dan Google Cloud Platform. Pelajari Cloud Firestore lebih lanjut.

Membaca dan Menulis Data di Android

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

Data Firebase ditulis ke referensi FirebaseDatabase dan diambil dengan memasang listener asinkron ke referensi. Listener terpicu sekali untuk status awal data dan terpicu lagi setiap kali data berubah.

Mendapatkan DatabaseReference

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

Java

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

Kotlin

private lateinit var database: DatabaseReference// ...
database = FirebaseDatabase.getInstance().reference

Membaca dan 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 dapat 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 getter publik untuk properti yang akan ditetapkan.

Jika Anda menggunakan objek Java, konten objek Anda akan otomatis dipetakan ke lokasi turunan secara bertingkat. Bila objek Java digunakan, kode Anda biasanya jadi lebih mudah dibaca dan dipertahankan. Misalnya, jika Anda memiliki aplikasi dengan profil pengguna dasar, objek User Anda mungkin akan terlihat seperti 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

@IgnoreExtraProperties
data class User(
    var username: String? = "",
    var email: String? = ""
)

Anda dapat menambahkan pengguna dengan setValue() seperti berikut:

Java

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

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

Kotlin

private 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 node turunan. Namun, Anda masih dapat mengupdate turunan tanpa menulis ulang seluruh objek. Jika ingin mengizinkan pengguna memperbarui profilnya, Anda dapat memperbarui nama pengguna seperti berikut:

Java

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

Kotlin

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

Mendeteksi peristiwa nilai

Untuk membaca data di lokasi dan mendeteksi perubahan, gunakan metode addValueEventListener() atau addListenerForSingleValueEvent() untuk menambahkan ValueEventListener ke DatabaseReference.

Listener Callback peristiwa Penggunaan standar
ValueEventListener onDataChange() Membaca dan mendeteksi perubahan pada seluruh konten di sebuah lokasi.

Anda dapat menggunakan metode onDataChange() untuk membaca snapshot statis konten pada lokasi tertentu, saat konten tersebut ada saat peristiwa terjadi. Metode ini terpicu satu kali ketika listener 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 Anda memanggil exists(), dan null ketika Anda memanggil getValue() pada snapshot tersebut.

Contoh berikut menunjukkan 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

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::class.java)
        // ...
    }

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

Listener menerima DataSnapshot yang berisi data di lokasi yang ditentukan dalam database pada saat peristiwa terjadi. Memanggil getValue() pada snapshot akan menampilkan perwakilan objek Java dari data. Jika di lokasi tidak terdapat data, memanggil getValue() akan menampilkan null.

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

Membaca data sekali

Dalam beberapa kasus, Anda mungkin ingin agar callback dipanggil satu kali kemudian segera dihapus, seperti ketika melakukan inisialisasi elemen UI yang tidak diharapkan berubah. Anda dapat menggunakan metode addListenerForSingleValueEvent() untuk menyederhanakan skenario ini: metode ini dipicu satu kali dan tidak dipicu lagi.

Ini berguna untuk data yang hanya perlu dimuat satu kali dan tidak diharapkan untuk sering berubah atau harus mendeteksi secara aktif. Misalnya, aplikasi blogging pada contoh sebelumnya menggunakan metode ini untuk memuat profil pengguna ketika mereka mulai membuat postingan baru:

Mengupdate atau menghapus data

Mengupdate kolom tertentu

Untuk menulis ke node turunan tertentu secara bersamaan tanpa mengganti node turunan yang lain, gunakan metode updateChildren().

Ketika memanggil updateChildren(), Anda dapat memperbarui nilai turunan di tingkat yang lebih rendah dengan menentukan lokasi untuk kunci tersebut. Jika data disimpan dalam beberapa lokasi agar lebih seimbang, Anda dapat mengupdate semua instance data tersebut menggunakan data fan-out. Misalnya, sebuah aplikasi blog sosial mungkin memiliki kelas 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

@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 sekaligus memperbaruinya ke feed aktivitas terbaru serta memposting feed aktivitas pengguna, aplikasi blog 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

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 = HashMap<String, Any>()
    childUpdates["/posts/$key"] = postValues
    childUpdates["/user-posts/$userId/$key"] = postValues

    database.updateChildren(childUpdates)
}

Contoh ini menggunakan push() guna membuat postingan dalam node yang berisi postingan untuk semua pengguna di /posts/$postid, dan sekaligus mengambil kunci dengan getKey(). Selanjutnya, kunci tersebut digunakan untuk membuat entri kedua dalam postingan pengguna di /user-posts/$userid/$postid.

Dengan menggunakan lokasi ini, Anda dapat melakukan update secara simultan ke beberapa lokasi di pohon JSON dengan panggilan tunggal ke updateChildren(), sebagaimana contoh ini membuat postingan baru di kedua lokasi. Update simultan menjadikan proses ini berjalan menyeluruh: entah semua update berhasil atau semua update gagal.

Menambahkan Callback Penyelesaian

Jika Anda ingin tahu kapan data diubah, Anda bisa menambahkan listener penyelesaian. setValue() dan updateChildren() membawa listener penyelesaian opsional yang dipanggil ketika operasi tulis telah berhasil diterapkan pada database. Jika panggilan tidak berhasil, listener akan diberikan objek error yang menunjukkan penyebab terjadinya kegagalan.

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

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.

Anda juga dapat menghapus dengan menentukan null sebagai nilai operasi tulis lainnya seperti setValue() atau updateChildren(). Anda dapat menggunakan teknik ini dengan updateChildren() untuk menghapus beberapa turunan dalam satu panggilan API.

Melepas listener

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

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

Memanggil removeEventListener() pada listener induk tidak akan otomatis menghapus listener yang terdaftar pada node turunannya; removeEventListener() juga harus dipanggil pada setiap listener turunan untuk menghapus callback.

Menyimpan data sebagai transaksi

Ketika bekerja dengan data yang bisa rusak karena perubahan serentak, seperti penghitung tambahan, Anda dapat menggunakan operasi transaksi. Operasi ini menggunakan dua argumen: fungsi update dan callback penyelesaian opsional. Fungsi update mengambil kondisi data saat ini sebagai argumen, dan akan menampilkan kondisi baru yang diinginkan untuk Anda tulis. Jika klien lainnya melakukan penulisan ke lokasi sebelum nilai baru Anda berhasil ditulis, fungsi update Anda akan dipanggil lagi dengan nilai baru saat ini, dan proses tulis akan dicoba ulang.

Misalnya, pada contoh aplikasi blogging sosial ini, Anda dapat mengizinkan pengguna memberi atau menghapus bintang pada postingan, serta mengetahui 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 b,
                               DataSnapshot dataSnapshot) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

Kotlin

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?,
            b: Boolean,
            dataSnapshot: 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 usang. Jika transaksi ditolak, server akan menampilkan nilai saat ini ke klien, yang akan menjalankan lagi transaksi tersebut dengan nilai yang diupdate. Proses ini berulang hingga transaksi diterima atau terlalu banyak percobaan dilakukan.

Menulis 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 dari setiap data aktif. Ketika ditulis, data akan dituliskan ke versi lokal ini 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 data yang dituliskan ke server. Ini berarti 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 kondisi server saat ini, tanpa harus menulis kode khusus.

Langkah berikutnya