(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย Firebase Local Emulator Suite
ก่อนจะพูดถึงวิธีที่แอปอ่านและเขียนข้อมูลลงใน Realtime Database เรามาแนะนำชุดเครื่องมือที่คุณใช้สร้างต้นแบบและทดสอบ Realtime Database ฟังก์ชันการทำงานได้ นั่นก็คือ Firebase Local Emulator Suite หากคุณกำลังลองใช้โมเดลข้อมูลต่างๆ ปรับกฎความปลอดภัยให้เหมาะสม หรือพยายามหาวิธีที่มีประสิทธิภาพสูงสุดในการโต้ตอบกับแบ็กเอนด์ การทำงานในเครื่องโดยไม่ต้องติดตั้งใช้งานบริการจริงอาจเป็นความคิดที่ดี
โปรแกรมจำลอง Realtime Database เป็นส่วนหนึ่งของ Local Emulator Suite ซึ่งช่วยให้แอปโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลที่จำลอง รวมถึงทรัพยากรโปรเจ็กต์ที่จำลอง (ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎความปลอดภัย) ได้ด้วย
การใช้โปรแกรมจำลอง Realtime Database มีขั้นตอนเพียงไม่กี่ขั้นตอน ดังนี้
- เพิ่มบรรทัดโค้ดลงในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
- เรียกใช้
firebase emulators:startจากรูทของไดเรกทอรีโปรเจ็กต์ที่อยู่ในเครื่อง - เรียกจากโค้ดต้นแบบของแอปโดยใช้ Realtime Database แพลตฟอร์ม SDK ตามปกติ หรือใช้ Realtime Database REST API
ดูคำแนะนำโดยละเอียดเกี่ยวกับการใช้ และ Realtime Database และ Cloud Functions นอกจากนี้ คุณควรดูข้อมูลเบื้องต้นเกี่ยวกับ Local Emulator Suite ด้วย
รับ FIRDatabaseReference
หากต้องการอ่านหรือเขียนข้อมูลจากฐานข้อมูล คุณต้องมีอินสแตนซ์ของ FIRDatabaseReference
Swift
var ref: DatabaseReference! ref = Database.database().reference()
Objective-C
@property (strong, nonatomic) FIRDatabaseReference *ref; self.ref = [[FIRDatabase database] reference];
เขียนข้อมูล
เอกสารนี้ครอบคลุมข้อมูลพื้นฐานเกี่ยวกับการอ่านและการเขียนข้อมูล Firebase
ระบบจะเขียนข้อมูล Firebase ลงในการอ้างอิง Database และดึงข้อมูลโดยแนบ Listener แบบไม่พร้อมกันกับการอ้างอิง ระบบจะทริกเกอร์ Listener 1 ครั้งสำหรับสถานะเริ่มต้นของข้อมูล และอีกครั้งทุกครั้งที่ข้อมูลมีการเปลี่ยนแปลง
การดำเนินการเขียนพื้นฐาน
สำหรับการดำเนินการเขียนพื้นฐาน คุณสามารถใช้ setValue เพื่อบันทึกข้อมูลลงในการอ้างอิงที่ระบุ โดยจะแทนที่ข้อมูลที่มีอยู่ที่เส้นทางนั้น คุณสามารถใช้วิธีนี้เพื่อทำสิ่งต่อไปนี้
- ส่งประเภทที่สอดคล้องกับประเภท JSON ที่มีอยู่ ดังนี้
NSStringNSNumberNSDictionaryNSArray
ตัวอย่างเช่น คุณสามารถเพิ่มผู้ใช้ด้วย setValue ได้ดังนี้
Swift
self.ref.child("users").child(user.uid).setValue(["username": username])
Objective-C
[[[self.ref child:@"users"] child:authResult.user.uid] setValue:@{@"username": username}];
การใช้ setValue ในลักษณะนี้จะเขียนทับข้อมูลในตำแหน่งที่ระบุ รวมถึงโหนดลูก อย่างไรก็ตาม คุณยังอัปเดตลูกได้โดยไม่ต้องเขียนออบเจ็กต์ทั้งหมดใหม่ หากต้องการอนุญาตให้ผู้ใช้อัปเดตโปรไฟล์ คุณสามารถอัปเดตชื่อผู้ใช้ได้ดังนี้
Swift
self.ref.child("users/\(user.uid)/username").setValue(username)
Objective-C
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];
อ่านข้อมูล
อ่านข้อมูลโดยการรอรับเหตุการณ์ค่า
หากต้องการอ่านข้อมูลที่เส้นทางหนึ่งและรอรับการเปลี่ยนแปลง ให้ใช้ observeEventType:withBlock ของ FIRDatabaseReference เพื่อรอรับเหตุการณ์ FIRDataEventTypeValue
| ประเภทเหตุการณ์ | การใช้งานทั่วไป |
|---|---|
FIRDataEventTypeValue |
อ่านและรอรับการเปลี่ยนแปลงเนื้อหาทั้งหมดของเส้นทาง |
คุณสามารถใช้เหตุการณ์ FIRDataEventTypeValue เพื่ออ่านข้อมูลที่เส้นทางหนึ่งๆ ตามที่ข้อมูลมีอยู่ ณ เวลาที่เกิดเหตุการณ์ ระบบจะทริกเกอร์วิธีนี้ 1 ครั้งเมื่อแนบ Listener และอีกครั้งทุกครั้งที่ข้อมูลมีการเปลี่ยนแปลง รวมถึงข้อมูลลูก ระบบจะส่ง snapshot ที่มีข้อมูลทั้งหมดในตำแหน่งนั้น รวมถึงข้อมูลลูก ไปยังการเรียกกลับของเหตุการณ์ หากไม่มีข้อมูล สแนปช็อตจะแสดงผล false เมื่อคุณเรียก exists() และ nil เมื่อคุณอ่านพร็อพเพอร์ตี้ value
ตัวอย่างต่อไปนี้แสดงแอปพลิเคชันบล็อกโซเชียลที่ดึงรายละเอียดของโพสต์จากฐานข้อมูล
Swift
refHandle = postRef.observe(DataEventType.value, with: { snapshot in // ... })
Objective-C
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { NSDictionary *postDict = snapshot.value; // ... }];
Listener จะได้รับ FIRDataSnapshot ที่มีข้อมูลในตำแหน่งที่ระบุในฐานข้อมูล ณ เวลาที่เกิดเหตุการณ์ในพร็อพเพอร์ตี้ value คุณสามารถกำหนดค่าให้กับประเภทดั้งเดิมที่เหมาะสม เช่น NSDictionary
หากไม่มีข้อมูลในตำแหน่งนั้น value จะเป็น nil
อ่านข้อมูล 1 ครั้ง
อ่าน 1 ครั้งโดยใช้ getData()
SDK ได้รับการออกแบบมาเพื่อจัดการการโต้ตอบกับเซิร์ฟเวอร์ฐานข้อมูลไม่ว่าแอปของคุณจะออนไลน์หรือออฟไลน์
โดยทั่วไป คุณควรใช้เทคนิคเหตุการณ์ค่าที่อธิบายไว้ข้างต้นเพื่ออ่านข้อมูลเพื่อรับการแจ้งเตือนเกี่ยวกับการอัปเดตข้อมูลจากแบ็กเอนด์ เทคนิคดังกล่าวจะช่วยลดการใช้งานและการเรียกเก็บเงิน และได้รับการปรับให้เหมาะสมเพื่อมอบประสบการณ์การใช้งานที่ดีที่สุดแก่ผู้ใช้เมื่อออนไลน์และออฟไลน์
หากต้องการข้อมูลเพียงครั้งเดียว คุณสามารถใช้ getData() เพื่อรับสแนปช็อตของข้อมูลจากฐานข้อมูล หาก getData() ไม่สามารถแสดงผลค่าเซิร์ฟเวอร์ได้ไม่ว่าด้วยเหตุผลใดก็ตาม ไคลเอ็นต์จะตรวจสอบแคชพื้นที่เก็บข้อมูลในเครื่องและแสดงผลข้อผิดพลาดหากยังไม่พบค่า
ตัวอย่างต่อไปนี้แสดงการดึงชื่อผู้ใช้ที่แสดงต่อสาธารณะของผู้ใช้ 1 ครั้งจากฐานข้อมูล
Swift
do { let snapshot = try await ref.child("users/\(uid)/username").getData() let userName = snapshot.value as? String ?? "Unknown" } catch { print(error) }
Objective-C
NSString *userPath = [NSString stringWithFormat:@"users/%@/username", uid]; [[ref child:userPath] getDataWithCompletionBlock:^(NSError * _Nullable error, FIRDataSnapshot * _Nonnull snapshot) { if (error) { NSLog(@"Received an error %@", error); return; } NSString *userName = snapshot.value; }];
การใช้ getData() โดยไม่จำเป็นอาจเพิ่มการใช้แบนด์วิดท์และทำให้ประสิทธิภาพลดลง ซึ่งป้องกันได้โดยใช้ Listener แบบเรียลไทม์ตามที่แสดงไว้ข้างต้น
อ่านข้อมูล 1 ครั้งด้วย Observer
ในบางกรณี คุณอาจต้องการให้ระบบแสดงผลค่าจากแคชในเครื่องทันทีแทนที่จะตรวจสอบค่าที่อัปเดตในเซิร์ฟเวอร์ ในกรณีดังกล่าว คุณสามารถใช้ observeSingleEventOfType เพื่อรับข้อมูลจากแคชดิสก์ในเครื่องได้ทันที
วิธีนี้มีประโยชน์สำหรับข้อมูลที่ต้องโหลดเพียงครั้งเดียวและไม่คาดว่าจะมีการเปลี่ยนแปลงบ่อยครั้งหรือต้องมีการรอรับข้อมูลอย่างต่อเนื่อง ตัวอย่างเช่น แอปบล็อกในตัวอย่างก่อนหน้านี้ใช้วิธีนี้เพื่อโหลดโปรไฟล์ของผู้ใช้เมื่อผู้ใช้เริ่มเขียนโพสต์ใหม่
Swift
let userID = Auth.auth().currentUser?.uid ref.child("users").child(userID!).observeSingleEvent(of: .value, with: { snapshot in // Get user value let value = snapshot.value as? NSDictionary let username = value?["username"] as? String ?? "" let user = User(username: username) // ... }) { error in print(error.localizedDescription) }
Objective-C
NSString *userID = [FIRAuth auth].currentUser.uid; [[[_ref child:@"users"] child:userID] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { // Get user value User *user = [[User alloc] initWithUsername:snapshot.value[@"username"]]; // ... } withCancelBlock:^(NSError * _Nonnull error) { NSLog(@"%@", error.localizedDescription); }];
การอัปเดตหรือลบข้อมูล
อัปเดตช่องที่เฉพาะเจาะจง
หากต้องการเขียนข้อมูลลงในลูกที่เฉพาะเจาะจงของโหนดพร้อมกันโดยไม่เขียนทับโหนดลูกอื่นๆ ให้ใช้วิธี updateChildValues
เมื่อเรียก updateChildValues คุณสามารถอัปเดตค่าลูกระดับล่างได้โดยระบุเส้นทางสำหรับคีย์ หากจัดเก็บข้อมูลไว้ในหลายตำแหน่งเพื่อปรับขนาดให้ดีขึ้น คุณสามารถอัปเดตอินสแตนซ์ทั้งหมดของข้อมูลนั้นได้โดยใช้การกระจายข้อมูล ตัวอย่างเช่น แอปบล็อกโซเชียลอาจต้องการสร้างโพสต์และอัปเดตโพสต์นั้นลงในฟีดกิจกรรมล่าสุดและฟีดกิจกรรมของผู้ใช้ที่โพสต์พร้อมกัน แอปพลิเคชันบล็อกจะใช้โค้ดลักษณะนี้เพื่อทำเช่นนั้น
Swift
guard let key = ref.child("posts").childByAutoId().key else { return } let post = ["uid": userID, "author": username, "title": title, "body": body] let childUpdates = ["/posts/\(key)": post, "/user-posts/\(userID)/\(key)/": post] ref.updateChildValues(childUpdates)
Objective-C
NSString *key = [[_ref child:@"posts"] childByAutoId].key; NSDictionary *post = @{@"uid": userID, @"author": username, @"title": title, @"body": body}; NSDictionary *childUpdates = @{[@"/posts/" stringByAppendingString:key]: post, [NSString stringWithFormat:@"/user-posts/%@/%@/", userID, key]: post}; [_ref updateChildValues:childUpdates];
ตัวอย่างนี้ใช้ childByAutoId เพื่อสร้างโพสต์ในโหนดที่มีโพสต์ของผู้ใช้ทั้งหมดที่ /posts/$postid และดึงคีย์ด้วย getKey() พร้อมกัน จากนั้นคุณสามารถใช้คีย์เพื่อสร้างรายการที่ 2 ในโพสต์ของผู้ใช้ที่ /user-posts/$userid/$postid
การใช้เส้นทางเหล่านี้จะช่วยให้คุณอัปเดตหลายตำแหน่งในแผนผัง JSON พร้อมกันได้ด้วยการเรียก updateChildValues เพียงครั้งเดียว เช่น วิธีที่ตัวอย่างนี้สร้างโพสต์ใหม่ในทั้ง 2 ตำแหน่ง การอัปเดตพร้อมกันที่ทำในลักษณะนี้จะเป็นแบบอะตอมมิก กล่าวคือ การอัปเดตทั้งหมดจะสำเร็จหรือล้มเหลวทั้งหมด
เพิ่มบล็อกการดำเนินการให้เสร็จสมบูรณ์
หากต้องการทราบว่าข้อมูลได้รับการคอมมิตแล้วเมื่อใด คุณสามารถเพิ่มบล็อกการดำเนินการให้เสร็จสมบูรณ์ได้ ทั้ง setValue และ updateChildValues จะใช้บล็อกการดำเนินการให้เสร็จสมบูรณ์ที่ไม่บังคับ ซึ่งจะเรียกเมื่อมีการคอมมิตการเขียนลงในฐานข้อมูล Listener นี้มีประโยชน์สำหรับการติดตามข้อมูลที่บันทึกไว้และข้อมูลที่ยังคงซิงค์อยู่ หากการเรียกไม่สำเร็จ ระบบจะส่งออบเจ็กต์ข้อผิดพลาดไปยัง Listener เพื่อระบุสาเหตุที่ทำให้เกิดข้อผิดพลาด
Swift
do { try await ref.child("users").child(user.uid).setValue(["username": username]) print("Data saved successfully!") } catch { print("Data could not be saved: \(error).") }
Objective-C
[[[_ref child:@"users"] child:user.uid] setValue:@{@"username": username} withCompletionBlock:^(NSError *error, FIRDatabaseReference *ref) { if (error) { NSLog(@"Data could not be saved: %@", error); } else { NSLog(@"Data saved successfully."); } }];
ลบข้อมูล
วิธีที่ง่ายที่สุดในการลบข้อมูลคือการเรียก removeValue ในการอ้างอิงไปยังตำแหน่งของข้อมูลนั้น
นอกจากนี้ คุณยังลบได้โดยระบุ nil เป็นค่าสำหรับการดำเนินการเขียนอื่นๆ เช่น setValue หรือ updateChildValues คุณสามารถใช้เทคนิคนี้กับ updateChildValues เพื่อลบลูกหลายรายการในการเรียก API ครั้งเดียว
ยกเลิกการแนบ Listener
Observer จะไม่หยุดซิงค์ข้อมูลโดยอัตโนมัติเมื่อคุณออกจาก ViewController หากไม่ได้นำ Observer ออกอย่างถูกต้อง Observer จะซิงค์ข้อมูลกับหน่วยความจำในเครื่องต่อไป เมื่อไม่จำเป็นต้องใช้ Observer อีกต่อไป ให้นำออกโดยส่ง FIRDatabaseHandle ที่เชื่อมโยงไปยังเมธอด removeObserverWithHandle
เมื่อคุณเพิ่มบล็อกการเรียกกลับลงในการอ้างอิง ระบบจะแสดงผล FIRDatabaseHandle
คุณสามารถใช้แฮนเดิลเหล่านี้เพื่อนำบล็อกการเรียกกลับออก
หากมีการเพิ่ม Listener หลายรายการลงในการอ้างอิงฐานข้อมูล ระบบจะเรียก Listener แต่ละรายการเมื่อมีการยกเหตุการณ์ หากต้องการหยุดซิงค์ข้อมูลในตำแหน่งนั้น คุณต้องนำ Observer ทั้งหมดในตำแหน่งนั้นออกโดยเรียกเมธอด removeAllObservers
การเรียก removeObserverWithHandle หรือ removeAllObservers ใน Listener จะไม่นำ Listener ที่ลงทะเบียนในโหนดลูกออกโดยอัตโนมัติ คุณต้องติดตามการอ้างอิงหรือแฮนเดิลเหล่านั้นเพื่อนำออกด้วย
บันทึกข้อมูลเป็นธุรกรรม
เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน เช่น ตัวนับแบบเพิ่มค่า คุณสามารถใช้การ ดำเนินการธุรกรรมได้ คุณต้องระบุอาร์กิวเมนต์ 2 รายการสำหรับการดำเนินการนี้ ได้แก่ ฟังก์ชันการอัปเดตและการเรียกกลับการดำเนินการให้เสร็จสมบูรณ์ที่ไม่บังคับ ฟังก์ชันการอัปเดตจะใช้สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์และแสดงผลสถานะใหม่ที่ต้องการเขียน
ตัวอย่างเช่น ในแอปบล็อกโซเชียล คุณสามารถอนุญาตให้ผู้ใช้ติดดาวและยกเลิกการติดดาวโพสต์ รวมถึงติดตามจำนวนดาวที่โพสต์ได้รับได้ดังนี้
Swift
ref.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in if var post = currentData.value as? [String: AnyObject], let uid = Auth.auth().currentUser?.uid { var stars: [String: Bool] stars = post["stars"] as? [String: Bool] ?? [:] var starCount = post["starCount"] as? Int ?? 0 if let _ = stars[uid] { // Unstar the post and remove self from stars starCount -= 1 stars.removeValue(forKey: uid) } else { // Star the post and add self to stars starCount += 1 stars[uid] = true } post["starCount"] = starCount as AnyObject? post["stars"] = stars as AnyObject? // Set value and report transaction success currentData.value = post return TransactionResult.success(withValue: currentData) } return TransactionResult.success(withValue: currentData) }) { error, committed, snapshot in if let error = error { print(error.localizedDescription) } }
Objective-C
[ref runTransactionBlock:^FIRTransactionResult * _Nonnull(FIRMutableData * _Nonnull currentData) { NSMutableDictionary *post = currentData.value; if (!post || [post isEqual:[NSNull null]]) { return [FIRTransactionResult successWithValue:currentData]; } NSMutableDictionary *stars = post[@"stars"]; if (!stars) { stars = [[NSMutableDictionary alloc] initWithCapacity:1]; } NSString *uid = [FIRAuth auth].currentUser.uid; int starCount = [post[@"starCount"] intValue]; if (stars[uid]) { // Unstar the post and remove self from stars starCount--; [stars removeObjectForKey:uid]; } else { // Star the post and add self to stars starCount++; stars[uid] = @YES; } post[@"stars"] = stars; post[@"starCount"] = @(starCount); // Set value and report transaction success currentData.value = post; return [FIRTransactionResult successWithValue:currentData]; } andCompletionBlock:^(NSError * _Nullable error, BOOL committed, FIRDataSnapshot * _Nullable snapshot) { // Transaction completed if (error) { NSLog(@"%@", error.localizedDescription); } }];
การใช้ธุรกรรมจะป้องกันไม่ให้จำนวนดาวไม่ถูกต้องหากผู้ใช้หลายรายติดดาวโพสต์เดียวกันพร้อมกันหรือไคลเอ็นต์มีข้อมูลเก่า ค่าที่อยู่ในคลาส FIRMutableData จะเป็นค่าล่าสุดที่ไคลเอ็นต์ทราบสำหรับเส้นทางนั้น หรือ nil หากไม่มีค่า เซิร์ฟเวอร์จะเปรียบเทียบค่าเริ่มต้นกับค่าปัจจุบันและยอมรับธุรกรรมหากค่าตรงกัน หรือปฏิเสธธุรกรรม หากปฏิเสธธุรกรรม เซิร์ฟเวอร์จะแสดงผลค่าปัจจุบันไปยังไคลเอ็นต์ ซึ่งจะเรียกใช้ธุรกรรมอีกครั้งด้วยค่าที่อัปเดต กระบวนการนี้จะทำซ้ำจนกว่าจะยอมรับธุรกรรมหรือมีการพยายามมากเกินไป
การเพิ่มค่าฝั่งเซิร์ฟเวอร์แบบอะตอมมิก
ในกรณีการใช้งานข้างต้น เราจะเขียนค่า 2 ค่าลงในฐานข้อมูล ได้แก่ รหัสของผู้ใช้ที่ติดดาว/นำดาวออกจากโพสต์ และจำนวนดาวที่เพิ่มขึ้น หากทราบอยู่แล้วว่าผู้ใช้กำลังติดดาวโพสต์ เราสามารถใช้การดำเนินการเพิ่มค่าแบบอะตอมมิกแทนธุรกรรมได้
Swift
let updates = [ "posts/\(postID)/stars/\(userID)": true, "posts/\(postID)/starCount": ServerValue.increment(1), "user-posts/\(postID)/stars/\(userID)": true, "user-posts/\(postID)/starCount": ServerValue.increment(1) ] as [String : Any] Database.database().reference().updateChildValues(updates)
Objective-C
NSDictionary *updates = @{[NSString stringWithFormat: @"posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"posts/%@/starCount", postID]: [FIRServerValue increment:@1], [NSString stringWithFormat: @"user-posts/%@/stars/%@", postID, userID]: @TRUE, [NSString stringWithFormat: @"user-posts/%@/starCount", postID]: [FIRServerValue increment:@1]}; [[[FIRDatabase database] reference] updateChildValues:updates];
โค้ดนี้ไม่ได้ใช้การดำเนินการธุรกรรม จึงไม่เรียกใช้ซ้ำโดยอัตโนมัติหากมีการอัปเดตที่ขัดแย้ง อย่างไรก็ตาม เนื่องจากมีการดำเนินการเพิ่มค่าในเซิร์ฟเวอร์ฐานข้อมูลโดยตรง จึงไม่มีโอกาสที่จะเกิดความขัดแย้ง
หากต้องการตรวจหาและปฏิเสธความขัดแย้งที่เฉพาะเจาะจงกับแอปพลิเคชัน เช่น ผู้ใช้ติดดาวโพสต์ที่เคยติดดาวไปแล้ว คุณควรเขียนกฎความปลอดภัยที่กำหนดเองสำหรับกรณีการใช้งานนั้น
ทำงานกับข้อมูลแบบออฟไลน์
หากไคลเอ็นต์ขาดการเชื่อมต่อเครือข่าย แอปของคุณจะยังคงทำงานได้อย่างถูกต้อง
ไคลเอ็นต์ทุกรายการที่เชื่อมต่อกับฐานข้อมูล Firebase จะเก็บรักษาข้อมูลเวอร์ชันภายในของตนเองสำหรับข้อมูลที่ใช้งานอยู่ เมื่อมีการเขียนข้อมูล ระบบจะเขียนข้อมูลลงในเวอร์ชันในเครื่องนี้ก่อน จากนั้นไคลเอ็นต์ Firebase จะซิงค์ข้อมูลนั้นกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอ็นต์อื่นๆ ตามความสามารถที่ดีที่สุด
ด้วยเหตุนี้ การเขียนข้อมูลทั้งหมดลงในฐานข้อมูลจึงทริกเกอร์เหตุการณ์ในเครื่องทันทีก่อนที่จะมีการเขียนข้อมูลลงในเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปของคุณจะยังคงตอบสนองได้ไม่ว่าจะมีเวลาในการรับส่งข้อมูลผ่านเครือข่ายหรือการเชื่อมต่อเป็นอย่างไร
เมื่อมีการเชื่อมต่ออีกครั้ง แอปของคุณจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้องเขียนโค้ดที่กำหนดเอง
เราจะพูดถึงลักษณะการทำงานแบบออฟไลน์เพิ่มเติมใน ดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถแบบออนไลน์และออฟไลน์