อ่านและเขียนข้อมูลบน Android

เอกสารนี้ครอบคลุมข้อมูลพื้นฐานเกี่ยวกับการอ่านและการเขียนข้อมูล Firebase

ระบบจะเขียนข้อมูล Firebase ลงในการอ้างอิง FirebaseDatabase และดึงข้อมูลโดยแนบ Listener แบบอะซิงโครนัสกับการอ้างอิง ระบบจะทริกเกอร์ Listener 1 ครั้งสำหรับสถานะเริ่มต้นของข้อมูล และอีกครั้งทุกครั้งที่ข้อมูลมีการเปลี่ยนแปลง

(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย Firebase Local Emulator Suite

ก่อนจะพูดถึงวิธีที่แอปอ่านและเขียนข้อมูลลงใน Realtime Database เรามาแนะนำชุดเครื่องมือที่คุณใช้สร้างต้นแบบและทดสอบฟังก์ชันการทำงานของ Realtime Database ได้ นั่นคือ Firebase Local Emulator Suite หากคุณกำลังลองใช้โมเดลข้อมูลต่างๆ เพิ่มประสิทธิภาพกฎความปลอดภัย หรือพยายามหาวิธีที่มีประสิทธิภาพสูงสุดในการโต้ตอบกับแบ็กเอนด์ การทำงานในเครื่องโดยไม่ต้องติดตั้งใช้งานบริการจริงอาจเป็นความคิดที่ดี

โปรแกรมจำลอง Realtime Database เป็นส่วนหนึ่งของ Local Emulator Suite ซึ่งช่วยให้แอปโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลที่จำลอง รวมถึงทรัพยากรโปรเจ็กต์ที่จำลอง (ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎความปลอดภัย) ได้ด้วย (ไม่บังคับ)

การใช้โปรแกรมจำลอง Realtime Database มีขั้นตอนเพียงไม่กี่ขั้นตอน ดังนี้

  1. เพิ่มบรรทัดโค้ดลงในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
  2. เรียกใช้ firebase emulators:start จากไดเรกทอรีรากของโปรเจ็กต์ที่อยู่ในเครื่อง
  3. เรียกจากโค้ดต้นแบบของแอปโดยใช้ Realtime Database แพลตฟอร์ม SDK ตามปกติ หรือใช้ Realtime Database REST API

มีคำแนะนำโดยละเอียดเกี่ยวกับการใช้Realtime DatabaseและCloud Functions นอกจากนี้ คุณควรดูข้อมูลเบื้องต้นเกี่ยวกับ Local Emulator Suite ด้วย

รับ DatabaseReference

หากต้องการอ่านหรือเขียนข้อมูลจากฐานข้อมูล คุณต้องมีอินสแตนซ์ของ DatabaseReference ดังนี้

Kotlin

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

Java

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

เขียนข้อมูล

การดำเนินการเขียนพื้นฐาน

สำหรับการดำเนินการเขียนพื้นฐาน คุณสามารถใช้ setValue() เพื่อบันทึกข้อมูลลงในการอ้างอิงที่ระบุ โดยจะแทนที่ข้อมูลที่มีอยู่ที่เส้นทางนั้น คุณสามารถใช้วิธีนี้เพื่อดำเนินการต่อไปนี้

  • ส่งประเภทที่สอดคล้องกับประเภท JSON ที่มีอยู่ ดังนี้
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • ส่งออบเจ็กต์ Java ที่กำหนดเอง หากคลาสที่กำหนดออบเจ็กต์นั้นมีเครื่องมือสร้างเริ่มต้นที่ไม่รับอาร์กิวเมนต์และมี Getter สาธารณะสำหรับพร็อพเพอร์ตี้ที่จะกำหนด

หากคุณใช้ออบเจ็กต์ Java ระบบจะแมปเนื้อหาของออบเจ็กต์กับตำแหน่งย่อยในลักษณะซ้อนกันโดยอัตโนมัติ โดยปกติแล้วการใช้ออบเจ็กต์ Java ยังช่วยให้โค้ดอ่านง่ายขึ้นและดูแลรักษาง่ายขึ้นด้วย ตัวอย่างเช่น หากคุณมีแอปที่มีโปรไฟล์ผู้ใช้พื้นฐาน ออบเจ็กต์ User อาจมีลักษณะดังนี้

Kotlin

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

}

คุณสามารถเพิ่มผู้ใช้ด้วย setValue() ได้ดังนี้

Kotlin

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

การใช้ setValue() ในลักษณะนี้จะเขียนทับข้อมูลในตำแหน่งที่ระบุ รวมถึงโหนดลูก อย่างไรก็ตาม คุณยังอัปเดตลูกได้โดยไม่ต้องเขียนออบเจ็กต์ทั้งหมดใหม่ หากต้องการอนุญาตให้ผู้ใช้อัปเดตโปรไฟล์ คุณสามารถอัปเดตชื่อผู้ใช้ได้ดังนี้

Kotlin

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

Java

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

อ่านข้อมูล

อ่านข้อมูลด้วย Listener แบบถาวร

หากต้องการอ่านข้อมูลในเส้นทางและฟังการเปลี่ยนแปลง ให้ใช้ addValueEventListener() เมธอดเพื่อเพิ่ม ValueEventListener ลงใน DatabaseReference

Listener Callback ของเหตุการณ์ การใช้งานทั่วไป
ValueEventListener onDataChange() อ่านและฟังการเปลี่ยนแปลงเนื้อหาทั้งหมดของเส้นทาง

คุณสามารถใช้เมธอด onDataChange() เพื่ออ่านสแนปชอตแบบคงที่ของเนื้อหาในเส้นทางที่กำหนด ซึ่งเป็นเนื้อหาที่อยู่ในเส้นทางนั้น ณ เวลาที่เกิดเหตุการณ์ ระบบจะทริกเกอร์เมธอดนี้ 1 ครั้งเมื่อแนบ Listener และอีกครั้งทุกครั้งที่ข้อมูลมีการเปลี่ยนแปลง รวมถึงข้อมูลลูก ระบบจะส่งสแนปชอตที่มีข้อมูลทั้งหมดในตำแหน่งนั้น รวมถึงข้อมูลลูก ไปยัง Callback ของเหตุการณ์ หากไม่มีข้อมูล สแนปชอตจะแสดงผล false เมื่อคุณเรียก exists() และแสดงผล null เมื่อคุณเรียก getValue()

ตัวอย่างต่อไปนี้แสดงแอปพลิเคชันบล็อกโซเชียลที่ดึงรายละเอียดของโพสต์จากฐานข้อมูล

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

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

Listener จะได้รับ DataSnapshot ที่มีข้อมูลในตำแหน่งที่ระบุในฐานข้อมูล ณ เวลาที่เกิดเหตุการณ์ การเรียก getValue() ในสแนปชอตจะแสดงผลการแสดงข้อมูลเป็นออบเจ็กต์ Java หากไม่มีข้อมูลในตำแหน่งนั้น การเรียก getValue() จะแสดงผล null

ในตัวอย่างนี้ ValueEventListener ยังกำหนดเมธอด onCancelled() ที่จะเรียกหากมีการยกเลิกการอ่าน เช่น การอ่านอาจถูกยกเลิกหากไคลเอ็นต์ไม่มีสิทธิ์อ่านจากตำแหน่งฐานข้อมูล Firebase ระบบจะส่งออบเจ็กต์ DatabaseError ที่ระบุสาเหตุที่ทำให้เกิดข้อผิดพลาดไปยังเมธอดนี้

อ่านข้อมูล 1 ครั้ง

อ่าน 1 ครั้งโดยใช้ get()

SDK ได้รับการออกแบบมาเพื่อจัดการการโต้ตอบกับเซิร์ฟเวอร์ฐานข้อมูลไม่ว่าแอปของคุณจะออนไลน์หรือออฟไลน์

โดยทั่วไป คุณควรใช้เทคนิค ValueEventListener ที่อธิบายไว้ข้างต้นเพื่ออ่านข้อมูลเพื่อรับการแจ้งเตือนเกี่ยวกับการอัปเดตข้อมูลจากแบ็กเอนด์ เทคนิค Listener จะช่วยลดการใช้งานและการเรียกเก็บเงิน และได้รับการเพิ่มประสิทธิภาพเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ดีที่สุดเมื่อออนไลน์และออฟไลน์

หากต้องการข้อมูลเพียงครั้งเดียว คุณสามารถใช้ get() เพื่อรับสแนปชอตของข้อมูลจากฐานข้อมูล หาก get() ไม่สามารถแสดงผลค่าเซิร์ฟเวอร์ได้ไม่ว่าจะด้วยเหตุผลใดก็ตาม ไคลเอ็นต์จะตรวจสอบแคชที่เก็บข้อมูลในเครื่องและแสดงผลข้อผิดพลาดหากยังไม่พบค่า

การใช้ get() โดยไม่จำเป็นอาจเพิ่มการใช้แบนด์วิดท์และทำให้ประสิทธิภาพลดลง ซึ่งป้องกันได้โดยใช้ Listener แบบเรียลไทม์ตามที่แสดงไว้ข้างต้น

Kotlin

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

อ่าน 1 ครั้งโดยใช้ Listener

ในบางกรณี คุณอาจต้องการให้ระบบแสดงผลค่าจากแคชในเครื่องทันทีแทนที่จะตรวจสอบค่าที่อัปเดตในเซิร์ฟเวอร์ ในกรณีดังกล่าว คุณสามารถใช้ addListenerForSingleValueEvent เพื่อรับข้อมูลจากแคชดิสก์ในเครื่องได้ทันที

วิธีนี้มีประโยชน์สำหรับข้อมูลที่ต้องโหลดเพียงครั้งเดียวและคาดว่าจะไม่เปลี่ยนแปลงบ่อยนักหรือต้องมีการฟังที่ใช้งานอยู่ เช่น แอปบล็อกในตัวอย่างก่อนหน้านี้ใช้วิธีนี้เพื่อโหลดโปรไฟล์ของผู้ใช้เมื่อผู้ใช้เริ่มเขียนโพสต์ใหม่

การอัปเดตหรือลบข้อมูล

อัปเดตช่องที่เฉพาะเจาะจง

หากต้องการเขียนข้อมูลลงในลูกที่เฉพาะเจาะจงของโหนดพร้อมกันโดยไม่เขียนทับโหนดลูกอื่นๆ ให้ใช้เมธอด updateChildren()

เมื่อเรียก updateChildren() คุณจะอัปเดตค่าลูกระดับล่างได้โดย ระบุเส้นทางสำหรับคีย์ หากจัดเก็บข้อมูลไว้ในหลายตำแหน่งเพื่อปรับขนาดให้ดีขึ้น คุณสามารถอัปเดตอินสแตนซ์ทั้งหมดของข้อมูลนั้นได้โดยใช้การกระจายข้อมูล ตัวอย่างเช่น แอปบล็อกโซเชียลอาจมีคลาส Post ดังนี้

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

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

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

ตัวอย่างนี้ใช้ push() เพื่อสร้างโพสต์ในโหนดที่มีโพสต์ของผู้ใช้ทั้งหมดที่ /posts/$postid และดึงคีย์ด้วย getKey() พร้อมกัน จากนั้นคุณสามารถใช้คีย์เพื่อสร้างรายการที่ 2 ในโพสต์ของผู้ใช้ที่ /user-posts/$userid/$postid

การใช้เส้นทางเหล่านี้ช่วยให้คุณอัปเดตหลายตำแหน่งในแผนผัง JSON พร้อมกันได้ด้วยการเรียก updateChildren() เพียงครั้งเดียว เช่น วิธีที่ตัวอย่างนี้สร้างโพสต์ใหม่ในทั้ง 2 ตำแหน่ง การอัปเดตพร้อมกันที่ทำในลักษณะนี้จะเป็นแบบอะตอมมิก กล่าวคือ การอัปเดตทั้งหมดจะสำเร็จหรือล้มเหลวทั้งหมด

เพิ่ม Callback การดำเนินการให้เสร็จสมบูรณ์

หากต้องการทราบว่าข้อมูลได้รับการคอมมิตแล้วเมื่อใด คุณสามารถเพิ่ม Listener การดำเนินการให้เสร็จสมบูรณ์ได้ ทั้ง setValue() และ updateChildren() จะใช้ Listener การดำเนินการให้เสร็จสมบูรณ์ที่ไม่บังคับ ซึ่งจะเรียกเมื่อมีการคอมมิตการเขียนลงในฐานข้อมูลเรียบร้อยแล้ว หากการเรียกไม่สำเร็จ ระบบจะส่งออบเจ็กต์ข้อผิดพลาดที่ระบุสาเหตุที่ทำให้เกิดข้อผิดพลาดไปยัง Listener

Kotlin

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

ลบข้อมูล

วิธีที่ง่ายที่สุดในการลบข้อมูลคือการเรียก removeValue() ในการอ้างอิงตำแหน่งของข้อมูลนั้น

นอกจากนี้ คุณยังลบได้โดยระบุ null เป็นค่าสำหรับการดำเนินการเขียนอื่นๆ เช่น setValue() หรือ updateChildren() คุณสามารถใช้เทคนิคนี้กับ updateChildren() เพื่อลบลูกหลายรายการในการเรียก API ครั้งเดียว

ยกเลิกการแนบ Listener

ระบบจะนำ Callback ออกโดยเรียกเมธอด removeEventListener() ในการอ้างอิงฐานข้อมูล Firebase

หากมีการเพิ่ม Listener ลงในตำแหน่งข้อมูลหลายครั้ง ระบบจะเรียก Listener หลายครั้งสำหรับแต่ละเหตุการณ์ และคุณต้องปลดออก Listener จำนวนครั้งเท่ากันเพื่อนำ Listener ออกอย่างสมบูรณ์

การเรียก removeEventListener() ใน Listener หลักจะไม่นำ Listener ที่ลงทะเบียนในโหนดลูกออกโดยอัตโนมัติ คุณต้องเรียก removeEventListener() ใน Listener ลูกด้วยเพื่อนำ Callback ออก

บันทึกข้อมูลเป็นธุรกรรม

เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน เช่น ตัวนับแบบเพิ่มค่า คุณสามารถใช้การ ดำเนินการธุรกรรมได้ การดำเนินการนี้ต้องใช้อาร์กิวเมนต์ 2 รายการ ได้แก่ ฟังก์ชันอัปเดตและ Callback การดำเนินการให้เสร็จสมบูรณ์ที่ไม่บังคับ ฟังก์ชันอัปเดตจะใช้สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์และแสดงผลสถานะใหม่ที่ต้องการเขียน หากไคลเอ็นต์อื่นเขียนลงในตำแหน่งก่อนที่จะเขียนค่าใหม่สำเร็จ ระบบจะเรียกฟังก์ชันอัปเดตอีกครั้งด้วยค่าปัจจุบันใหม่ และลองเขียนอีกครั้ง

ตัวอย่างเช่น ในแอปบล็อกโซเชียล คุณสามารถอนุญาตให้ผู้ใช้ติดดาวและยกเลิกการติดดาวโพสต์ รวมถึงติดตามจำนวนดาวที่โพสต์ได้รับได้ดังนี้

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

การใช้ธุรกรรมจะป้องกันไม่ให้จำนวนดาวไม่ถูกต้องหากผู้ใช้หลายรายติดดาวโพสต์เดียวกันพร้อมกันหรือไคลเอ็นต์มีข้อมูลที่ล้าสมัย หากระบบปฏิเสธธุรกรรม เซิร์ฟเวอร์จะแสดงผลค่าปัจจุบันไปยังไคลเอ็นต์ ซึ่งจะเรียกใช้ธุรกรรมอีกครั้งด้วยค่าที่อัปเดต การดำเนินการนี้จะทำซ้ำจนกว่าระบบจะยอมรับธุรกรรมหรือมีการพยายามมากเกินไป

การเพิ่มค่าฝั่งเซิร์ฟเวอร์แบบอะตอมมิก

ใน Use Case ข้างต้น เราจะเขียนค่า 2 ค่าลงในฐานข้อมูล ได้แก่ รหัสของผู้ใช้ที่ติดดาว/ยกเลิกการติดดาวโพสต์ และจำนวนดาวที่เพิ่มขึ้น หากทราบอยู่แล้วว่าผู้ใช้กำลังติดดาวโพสต์ เราสามารถใช้การดำเนินการเพิ่มค่าแบบอะตอมมิกแทนธุรกรรมได้

Kotlin

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

โค้ดนี้ไม่ได้ใช้การดำเนินการธุรกรรม ดังนั้นระบบจะไม่เรียกใช้โค้ดนี้อีกครั้งโดยอัตโนมัติหากมีการอัปเดตที่ขัดแย้งกัน อย่างไรก็ตาม เนื่องจากมีการดำเนินการเพิ่มค่าในเซิร์ฟเวอร์ฐานข้อมูลโดยตรง จึงไม่มีโอกาสที่จะเกิดความขัดแย้ง

หากต้องการตรวจหาและปฏิเสธความขัดแย้งที่เฉพาะเจาะจงของแอปพลิเคชัน เช่น ผู้ใช้ติดดาวโพสต์ที่เคยติดดาวไปแล้ว คุณควรเขียนกฎความปลอดภัยที่กำหนดเองสำหรับ Use Case นั้น

ทำงานกับข้อมูลแบบออฟไลน์

หากไคลเอ็นต์ขาดการเชื่อมต่อเครือข่าย แอปของคุณจะยังคงทำงานได้อย่างถูกต้อง

ไคลเอ็นต์ทุกรายการที่เชื่อมต่อกับฐานข้อมูล Firebase จะเก็บเวอร์ชันภายในของข้อมูลที่ใช้ Listener หรือมีการตั้งค่าสถานะให้ซิงค์กับเซิร์ฟเวอร์ เมื่อมีการอ่านหรือเขียนข้อมูล ระบบจะใช้ข้อมูลเวอร์ชันในเครื่องนี้ก่อน จากนั้นไคลเอ็นต์ Firebase จะซิงค์ข้อมูลนั้นกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอ็นต์อื่นๆ ตามความสามารถที่ดีที่สุด

ด้วยเหตุนี้ การเขียนข้อมูลทั้งหมดลงในฐานข้อมูลจึงทริกเกอร์เหตุการณ์ในเครื่องทันทีก่อนที่จะมีการโต้ตอบกับเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปของคุณจะยังคงตอบสนองได้ไม่ว่าจะมีเวลาในการตอบสนองของเครือข่ายหรือการเชื่อมต่อเป็นอย่างไร

เมื่อมีการเชื่อมต่ออีกครั้ง แอปของคุณจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้องเขียนโค้ดที่กำหนดเอง

เราจะพูดถึงลักษณะการทำงานแบบออฟไลน์เพิ่มเติมใน ดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถแบบออนไลน์และออฟไลน์

ขั้นตอนถัดไป