Ikuti semua informasi yang diumumkan di Firebase Summit, dan pelajari bagaimana Firebase dapat membantu Anda mempercepat pengembangan aplikasi dan menjalankan aplikasi dengan percaya diri. Pelajari Lebih Lanjut

Baca dan Tulis Data di Android

Tetap teratur dengan koleksi Simpan dan kategorikan konten berdasarkan preferensi Anda.

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

Data Firebase ditulis ke referensi FirebaseDatabase dan diambil dengan melampirkan pemroses asinkron ke referensi tersebut. Pemroses dipicu sekali untuk status awal data dan dipicu lagi setiap kali data berubah.

(Opsional) Membuat prototipe dan menguji dengan Firebase Local Emulator Suite

Sebelum berbicara tentang bagaimana aplikasi Anda membaca dari dan menulis ke Realtime Database, mari perkenalkan seperangkat alat yang dapat Anda gunakan untuk membuat prototipe dan menguji fungsionalitas Realtime Database: Firebase Local Emulator Suite. Jika Anda mencoba model data yang berbeda, mengoptimalkan aturan keamanan, atau berupaya menemukan cara yang paling hemat biaya untuk berinteraksi dengan back-end, dapat bekerja secara lokal tanpa menggunakan layanan langsung bisa menjadi ide bagus.

Emulator Realtime Database adalah bagian dari Local Emulator Suite, yang memungkinkan aplikasi Anda berinteraksi dengan konten dan konfigurasi database yang diemulasikan, serta secara opsional sumber daya proyek yang diemulasikan (fungsi, database lain, dan aturan keamanan).

Menggunakan emulator Realtime Database hanya memerlukan beberapa langkah:

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

Panduan mendetail yang melibatkan Realtime Database dan Cloud Functions tersedia. Anda juga harus melihat pengenalan Local Emulator Suite .

Dapatkan Referensi Basis Data

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

Kotlin+KTX

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

Java

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

Menulis data

Operasi tulis dasar

Untuk operasi tulis dasar, Anda dapat menggunakan setValue() untuk menyimpan data ke referensi tertentu, menggantikan data yang ada di jalur tersebut. Anda dapat menggunakan metode ini untuk:

  • Jenis penerusan yang sesuai dengan jenis JSON yang tersedia sebagai berikut:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • Lewati objek Java kustom, jika kelas yang mendefinisikannya memiliki konstruktor default yang tidak memerlukan argumen dan memiliki getter publik untuk properti yang akan ditetapkan.

Jika Anda menggunakan objek Java, konten objek Anda secara otomatis dipetakan ke lokasi anak dengan cara bersarang. Menggunakan objek Java juga biasanya membuat kode Anda lebih mudah dibaca dan lebih mudah dipelihara. Misalnya, jika Anda memiliki aplikasi dengan profil pengguna dasar, objek User Anda mungkin terlihat seperti berikut:

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.
}

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;
    }

}

Anda dapat menambahkan pengguna dengan setValue() sebagai berikut:

Kotlin+KTX

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

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

Java

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

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

Menggunakan setValue() dengan cara ini akan menimpa data di lokasi yang ditentukan, termasuk simpul anak mana pun. Namun, Anda masih dapat memperbarui anak tanpa menulis ulang seluruh objek. Jika Anda ingin mengizinkan pengguna memperbarui profil mereka, Anda dapat memperbarui nama pengguna sebagai berikut:

Kotlin+KTX

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

Java

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

Membaca data

Membaca data dengan pendengar yang gigih

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

Pendengar Panggilan balik acara Penggunaan tipikal
ValueEventListener onDataChange() Baca dan dengarkan perubahan pada seluruh konten jalur.

Anda bisa menggunakan metode onDataChange() untuk membaca snapshot statis konten di jalur tertentu, seperti yang ada pada saat kejadian. Metode ini dipicu sekali saat pemroses dilampirkan dan dipicu lagi setiap kali data, termasuk turunan, berubah. Panggilan balik peristiwa diteruskan snapshot yang berisi semua data di lokasi itu, termasuk data anak. Jika tidak ada data, snapshot akan mengembalikan false saat Anda memanggil exists() dan null saat Anda memanggil getValue() di atasnya.

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

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)

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);

Pendengar menerima DataSnapshot yang berisi data di lokasi yang ditentukan dalam database pada saat kejadian. Memanggil getValue() pada snapshot mengembalikan representasi objek Java dari data. Jika tidak ada data di lokasi, pemanggilan getValue() mengembalikan null .

Dalam contoh ini, ValueEventListener juga mendefinisikan metode onCancelled() yang dipanggil jika pembacaan dibatalkan. Misalnya, pembacaan dapat dibatalkan jika klien tidak memiliki izin untuk membaca dari lokasi database Firebase. Metode ini melewati objek DatabaseError yang menunjukkan mengapa kegagalan terjadi.

Membaca data sekali

Baca sekali menggunakan get()

SDK dirancang untuk mengelola interaksi dengan server database apakah aplikasi Anda sedang online atau offline.

Umumnya, Anda harus menggunakan teknik ValueEventListener yang dijelaskan di atas untuk membaca data guna mendapatkan pemberitahuan tentang pembaruan data dari backend. Teknik pemroses mengurangi penggunaan dan penagihan Anda, dan dioptimalkan untuk memberikan pengalaman terbaik kepada pengguna Anda saat mereka online dan offline.

Jika Anda memerlukan data hanya sekali, Anda dapat menggunakan get() untuk mendapatkan cuplikan data dari database. Jika karena alasan apa pun get() tidak dapat mengembalikan nilai server, klien akan menyelidiki cache penyimpanan lokal dan mengembalikan kesalahan jika nilainya masih belum ditemukan.

Penggunaan get() yang tidak perlu dapat meningkatkan penggunaan bandwidth dan menyebabkan hilangnya kinerja, yang dapat dicegah dengan menggunakan pendengar waktu nyata seperti yang ditunjukkan di atas.

Kotlin+KTX

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

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()));
        }
    }
});

Baca sekali menggunakan pendengar

Dalam beberapa kasus, Anda mungkin ingin nilai dari cache lokal segera dikembalikan, alih-alih memeriksa nilai yang diperbarui di server. Dalam kasus tersebut, Anda dapat menggunakan addListenerForSingleValueEvent untuk segera mendapatkan data dari cache disk lokal.

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

Memperbarui atau menghapus data

Perbarui bidang tertentu

Untuk secara bersamaan menulis ke node anak tertentu tanpa menimpa node anak lainnya, gunakan metode updateChildren() .

Saat memanggil updateChildren() , Anda bisa memperbarui nilai turunan tingkat rendah dengan menentukan jalur untuk kunci. Jika data disimpan di beberapa lokasi untuk menskalakan dengan lebih baik, Anda dapat memperbarui semua contoh data tersebut menggunakan data fan-out . Misalnya, aplikasi blogging sosial mungkin memiliki kelas Post seperti ini:

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
        )
    }
}

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;
    }
}

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

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)
}

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);
}

Contoh ini menggunakan push() untuk membuat postingan di node yang berisi postingan untuk semua pengguna di /posts/$postid dan secara bersamaan mengambil kunci dengan getKey() . Kunci tersebut kemudian dapat digunakan untuk membuat entri kedua di postingan pengguna di /user-posts/$userid/$postid .

Dengan menggunakan jalur ini, Anda dapat melakukan pembaruan simultan ke beberapa lokasi di pohon JSON dengan satu panggilan ke updateChildren() , seperti bagaimana contoh ini membuat postingan baru di kedua lokasi. Pembaruan simultan yang dilakukan dengan cara ini bersifat atomik: semua pembaruan berhasil atau semua pembaruan gagal.

Tambahkan Callback Penyelesaian

Jika Anda ingin mengetahui kapan data Anda telah di-commit, Anda dapat menambahkan listener penyelesaian. Baik setValue() dan updateChildren() mengambil pendengar penyelesaian opsional yang dipanggil saat penulisan telah berhasil dilakukan ke database. Jika panggilan tidak berhasil, pendengar diberikan objek kesalahan yang menunjukkan mengapa kegagalan terjadi.

Kotlin+KTX

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

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
                // ...
            }
        });

Hapus 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 untuk operasi tulis lainnya seperti setValue() atau updateChildren() . Anda dapat menggunakan teknik ini dengan updateChildren() untuk menghapus beberapa turunan dalam satu panggilan API.

Pisahkan pendengar

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

Jika pendengar telah ditambahkan beberapa kali ke lokasi data, maka akan dipanggil beberapa kali untuk setiap kejadian, dan Anda harus melepaskannya dengan jumlah yang sama untuk menghapusnya sepenuhnya.

Memanggil removeEventListener() pada pendengar induk tidak secara otomatis menghapus pendengar yang terdaftar pada node anaknya; removeEventListener() juga harus dipanggil pada setiap anak pendengar untuk menghapus callback.

Simpan data sebagai transaksi

Saat bekerja dengan data yang dapat dirusak oleh modifikasi bersamaan, seperti penghitung inkremental, Anda dapat menggunakan operasi transaksi . Anda memberikan operasi ini dua argumen: fungsi update dan callback penyelesaian opsional. Fungsi pembaruan mengambil keadaan data saat ini sebagai argumen dan mengembalikan keadaan baru yang diinginkan yang ingin Anda tulis. Jika klien lain menulis ke lokasi sebelum nilai baru Anda berhasil ditulis, fungsi pembaruan Anda dipanggil lagi dengan nilai baru saat ini, dan penulisan dicoba lagi.

Misalnya, dalam contoh aplikasi blog sosial, Anda dapat mengizinkan pengguna untuk memberi bintang dan menghapus postingan serta melacak jumlah bintang yang diterima postingan sebagai berikut:

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!!)
        }
    })
}

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull 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);
        }
    });
}

Menggunakan transaksi mencegah kesalahan jumlah bintang jika banyak pengguna membintangi posting yang sama pada waktu yang sama atau klien memiliki data basi. Jika transaksi ditolak, server mengembalikan nilai saat ini ke klien, yang menjalankan transaksi lagi dengan nilai yang diperbarui. Ini berulang sampai transaksi diterima atau terlalu banyak upaya telah dilakukan.

Penambahan sisi server atomik

Dalam kasus penggunaan di atas, kami menulis dua nilai ke database: ID pengguna yang memberi bintang/menghapus bintang pada kiriman, dan jumlah bintang yang bertambah. Jika kita sudah mengetahui bahwa pengguna membintangi postingan tersebut, kita dapat menggunakan operasi peningkatan atom alih-alih transaksi.

Kotlin+KTX

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

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);
}

Kode ini tidak menggunakan operasi transaksi, sehingga tidak dijalankan kembali secara otomatis jika ada pembaruan yang bertentangan. Namun, karena operasi penambahan terjadi langsung di server basis data, tidak ada kemungkinan konflik.

Jika Anda ingin mendeteksi dan menolak konflik khusus aplikasi, seperti pengguna yang membintangi kiriman yang telah mereka bintangi sebelumnya, Anda harus menulis aturan keamanan khusus untuk kasus penggunaan tersebut.

Bekerja dengan data luring

Jika klien kehilangan koneksi jaringannya, aplikasi Anda akan terus berfungsi dengan benar.

Setiap klien yang terhubung ke database Firebase mempertahankan versi internalnya sendiri dari setiap data yang sedang digunakan pendengar atau yang ditandai untuk tetap disinkronkan dengan server. Saat data dibaca atau ditulis, versi lokal dari data ini digunakan terlebih dahulu. Klien Firebase kemudian menyinkronkan data tersebut dengan server basis data jarak jauh dan dengan klien lain berdasarkan "upaya terbaik".

Akibatnya, semua penulisan ke database segera memicu peristiwa lokal, sebelum interaksi apa pun dengan server. Ini berarti aplikasi Anda tetap responsif terlepas dari latensi atau konektivitas jaringan.

Setelah konektivitas dibangun kembali, aplikasi Anda menerima rangkaian peristiwa yang sesuai sehingga klien menyinkronkan dengan status server saat ini, tanpa harus menulis kode khusus apa pun.

Kami akan berbicara lebih banyak tentang perilaku offline di Pelajari lebih lanjut tentang kemampuan online dan offline .

Langkah selanjutnya