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

เอกสารนี้อธิบายข้อมูลเบื้องต้นเกี่ยวกับการอ่านและเขียนข้อมูล Firebase

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

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

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

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

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

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

หากคุณใช้ออบเจ็กต์ 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() ในสแนปชอตจะแสดงการนำเสนอออบเจ็กต์ Java ของข้อมูล หากไม่มีข้อมูลที่ตำแหน่งนั้น การเรียกใช้ getValue() จะแสดงผล null

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

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

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

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

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

หากต้องการข้อมูลเพียงครั้งเดียว คุณสามารถใช้ 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

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

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

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

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

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

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

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