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

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

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

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

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

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

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

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

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

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

หากใช้ออบเจ็กต์ 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);

อ่านข้อมูล

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

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

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

คุณสามารถใช้เมธอด onDataChange() เพื่ออ่านสแนปชอตแบบภาพนิ่งของ เนื้อหาในเส้นทางที่ระบุ ตามที่มีอยู่ ณ เวลาที่เกิดเหตุการณ์ วิธีนี้ จะทริกเกอร์ครั้งเดียวเมื่อมีการแนบ Listener และอีกครั้งทุกครั้งที่ข้อมูล รวมทั้งเด็ก การเปลี่ยนแปลงต่างๆ Callback ของเหตุการณ์จะส่งผ่านสแนปชอตที่มี ข้อมูลทั้งหมดในตำแหน่งนั้น รวมถึงข้อมูลย่อยด้วย หากไม่มีข้อมูล ระบบจะ สแนปชอตจะแสดง 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);

Listener จะได้รับ DataSnapshot ที่มีข้อมูลตามที่ระบุ ในฐานข้อมูล ณ เวลาที่เกิดเหตุการณ์ กำลังโทรหา getValue() บน จะแสดงการแสดงออบเจ็กต์ 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 ตำแหน่ง การอัปเดตพร้อมกันในลักษณะนี้ เป็นแบบอะตอมมิก นั่นคือการอัปเดตทั้งหมดสำเร็จหรือล้มเหลว

เพิ่ม Callback ที่เสร็จสมบูรณ์

หากต้องการทราบว่าข้อมูลของคุณถูกคอมมิตแล้วเมื่อใด คุณสามารถเพิ่ม Listener การเล่นจนจบ ทั้ง setValue() และ updateChildren() จะเลือกเข้าร่วมได้ 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 ครั้งเดียว

ปลดผู้ฟังออก

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

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

การเรียกใช้ 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);
        }
    });
}

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

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

ในกรณีการใช้งานด้านบน เราจะเขียนค่า 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 จะซิงค์ข้อมูลดังกล่าวกับ เซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอ็นต์อื่นๆ อย่าง "ดีที่สุด" พื้นฐาน

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

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

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

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