เอกสารนี้อธิบายข้อมูลเบื้องต้นเกี่ยวกับการอ่านและเขียนข้อมูล 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 บรรทัดลงในการกําหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจําลอง
- จากรูทของไดเรกทอรีโปรเจ็กต์ในเครื่อง ให้เรียกใช้
firebase emulators:start
- การเรียกใช้จากโค้ดโปรโตไทป์ของแอปโดยใช้ 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);
อ่านข้อมูล
อ่านข้อมูลด้วยโปรแกรมฟังถาวร
หากต้องการอ่านข้อมูลในเส้นทางและรอการเปลี่ยนแปลง ให้ใช้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); } }); }
การใช้ธุรกรรมจะช่วยป้องกันไม่ให้จำนวนดาวไม่ถูกต้องหากผู้ใช้หลายคนติดดาวโพสต์เดียวกันในเวลาเดียวกันหรือไคลเอ็นต์มีข้อมูลที่ล้าสมัย หากธุรกรรมถูกปฏิเสธ เซิร์ฟเวอร์จะแสดงผลค่าปัจจุบันไปยังไคลเอ็นต์ ซึ่งจะเรียกใช้ธุรกรรมอีกครั้งด้วยค่าที่อัปเดต ระบบจะดำเนินการซ้ำจนกว่าธุรกรรมจะได้รับการยอมรับหรือมีการพยายามดำเนินการมากเกินไป
การเพิ่มฝั่งเซิร์ฟเวอร์แบบอะตอม
ใน 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 จะเก็บข้อมูลเวอร์ชันภายในของตนเองเกี่ยวกับข้อมูลที่ใช้ตัวรับฟังหรือข้อมูลที่ได้รับแจ้งให้ซิงค์กับเซิร์ฟเวอร์ เมื่ออ่านหรือเขียนข้อมูล ระบบจะใช้ข้อมูลเวอร์ชันในเครื่องนี้ก่อน จากนั้นไคลเอ็นต์ Firebase จะซิงค์ข้อมูลดังกล่าวกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอ็นต์อื่นๆ โดยพยายามทําให้ดีที่สุด
ด้วยเหตุนี้ การเขียนทั้งหมดในฐานข้อมูลจึงทริกเกอร์เหตุการณ์ในเครื่องทันทีก่อนที่จะมีการโต้ตอบกับเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปจะยังคงตอบสนองอยู่ไม่ว่าจะมีเวลาในการตอบสนองหรือการเชื่อมต่อเครือข่ายอย่างไรก็ตาม
เมื่อเชื่อมต่ออีกครั้ง แอปของคุณจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันได้โดยไม่ต้องเขียนโค้ดที่กําหนดเอง
เราจะพูดถึงลักษณะการทํางานแบบออฟไลน์เพิ่มเติมในหัวข้อดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถแบบออนไลน์และออฟไลน์