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

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

ข้อมูล Firebase เขียนไปยังข้อมูลอ้างอิง FirebaseDatabase และดึงข้อมูลโดย การแนบ Listener แบบไม่พร้อมกันลงในข้อมูลอ้างอิง ทริกเกอร์ Listener แล้ว อีกครั้งเป็นสถานะเริ่มต้นของข้อมูล และอีกครั้งเมื่อข้อมูลเปลี่ยนแปลง

(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย 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 Platform SDK ตามปกติ หรือใช้ Realtime Database REST API

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

รับ DatabaseReference

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

Kotlin+KTX

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 ที่กําหนดเอง หากคลาสที่กําหนดออบเจ็กต์มีคอนสตรัคเตอร์เริ่มต้นที่ไม่รับอาร์กิวเมนต์ และมี Gettier แบบสาธารณะสําหรับพร็อพเพอร์ตี้ที่จะกําหนด

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

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

}

คุณเพิ่มผู้ใช้ที่มี setValue() ได้โดยทำดังนี้

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

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

Kotlin+KTX

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

Java

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

อ่านข้อมูล

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

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

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

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

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

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

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

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

อ่านข้อมูลครั้งเดียว

อ่านเพียงครั้งเดียวโดยใช้ get()

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

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

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

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

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

อ่านครั้งเดียวโดยใช้ Listener

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

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

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

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

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

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

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

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

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

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

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

เพิ่มการเรียกกลับเมื่อดำเนินการเสร็จสมบูรณ์

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

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

ลบข้อมูล

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

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

แยก Listener

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

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

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

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

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

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

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

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

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

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

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

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

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

ใช้งานข้อมูลแบบออฟไลน์

หากลูกค้าสูญเสียการเชื่อมต่อเครือข่าย แอปจะยังคงทำงานต่อไป อย่างถูกต้อง

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

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

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

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

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