(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย Firebase Local Emulator Suite
ก่อนที่จะพูดถึงวิธีที่แอปอ่านและเขียนลงในฐานข้อมูลเรียลไทม์ เราขอแนะนำชุดเครื่องมือที่คุณสามารถใช้สร้างต้นแบบและทดสอบฟังก์ชันการทำงานของฐานข้อมูลเรียลไทม์อย่าง Firebase Local Emulator Suite หากคุณกำลังลองใช้โมเดลข้อมูลที่แตกต่างกัน เพิ่มประสิทธิภาพกฎการรักษาความปลอดภัย หรือหาวิธีที่คุ้มค่าที่สุดในการโต้ตอบกับระบบแบ็กเอนด์ การทำงานในองค์กรโดยไม่ต้องทำให้บริการแบบสดใช้งานได้เป็นความคิดที่ดี
โปรแกรมจำลอง Realtime Database เป็นส่วนหนึ่งของชุดโปรแกรมจำลองภายใน ซึ่งจะช่วยให้แอปโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลที่จำลอง ตลอดจนทรัพยากรของโปรเจ็กต์ที่จำลองขึ้นมา (เช่น ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎความปลอดภัย)
การใช้โปรแกรมจำลอง Realtime Database ประกอบด้วยไม่กี่ขั้นตอนดังนี้
- การเพิ่มบรรทัดโค้ดลงในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
- จากรูทของไดเรกทอรีโปรเจ็กต์ในเครื่องโดยเรียกใช้
firebase emulators:start
- การเรียกใช้จากโค้ดต้นแบบของแอปโดยใช้ SDK ของแพลตฟอร์ม Realtime Database ตามปกติ หรือใช้ Realtime Database REST API
โปรดดูคำแนะนำแบบทีละขั้นเกี่ยวกับ Realtime Database และ Cloud Functions โดยละเอียด นอกจากนี้คุณควรดูข้อมูลที่ข้อมูลเบื้องต้นเกี่ยวกับชุดโปรแกรมจำลองในเครื่องด้วย
รับ 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 ที่ใช้ได้มีดังนี้
NSString
NSNumber
NSDictionary
NSArray
เช่น เพิ่มผู้ใช้ด้วย 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
เพื่ออ่านข้อมูลในเส้นทางที่ระบุได้ ตามที่มีอยู่ ณ เวลาที่เกิดเหตุการณ์ ระบบจะทริกเกอร์วิธีการนี้ครั้งเดียวเมื่อมีการแนบผู้ฟัง และอีกครั้งทุกครั้งที่มีการเปลี่ยนแปลงข้อมูลรวมถึงรายการย่อย Callback ของเหตุการณ์จะส่งผ่าน 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
อ่านข้อมูลครั้งเดียว
อ่านครั้งเดียวโดยใช้ getData()
SDK ออกแบบมาเพื่อจัดการการโต้ตอบกับเซิร์ฟเวอร์ฐานข้อมูลไม่ว่าแอปของคุณจะออนไลน์หรือออฟไลน์
โดยทั่วไป คุณควรใช้เทคนิคเหตุการณ์มูลค่าที่อธิบายไว้ข้างต้นในการอ่านข้อมูลและรับการแจ้งเตือนการอัปเดตข้อมูลจากแบ็กเอนด์ เทคนิคดังกล่าวจะลดการใช้งานและการเรียกเก็บเงินของคุณ พร้อมเพิ่มประสิทธิภาพเพื่อให้ผู้ใช้ได้รับประสบการณ์ที่ดีที่สุดเมื่อออนไลน์และออฟไลน์
หากต้องการข้อมูลเพียงครั้งเดียว คุณจะใช้ getData()
เพื่อรับสแนปชอตของข้อมูลจากฐานข้อมูลได้ หาก getData()
แสดงผลค่าเซิร์ฟเวอร์ไม่ได้ด้วยเหตุผลใดก็ตาม ไคลเอ็นต์จะตรวจสอบแคชพื้นที่เก็บข้อมูลในเครื่องและแสดงผลข้อผิดพลาดหากยังไม่พบค่า
ตัวอย่างต่อไปนี้แสดงการดึงข้อมูลชื่อผู้ใช้ที่เปิดเผยต่อสาธารณะของผู้ใช้จากฐานข้อมูลเพียงครั้งเดียว
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 แบบเรียลไทม์ดังที่แสดงด้านบน
อ่านข้อมูลครั้งเดียวกับผู้สังเกตการณ์
ในบางกรณี คุณอาจต้องการให้ระบบส่งกลับค่าจากแคชในเครื่องทันที แทนที่จะตรวจสอบค่าที่อัปเดตแล้วในเซิร์ฟเวอร์ ในกรณีเหล่านั้น คุณจะใช้ 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 จะส่งผ่านออบเจ็กต์ข้อผิดพลาดที่ระบุสาเหตุของความล้มเหลวขึ้น
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 เดียวได้
ปลดผู้ฟังออก
ผู้สังเกตการณ์จะไม่หยุดซิงค์ข้อมูลโดยอัตโนมัติเมื่อคุณออกจาก ViewController
หากไม่ได้นำผู้สังเกตการณ์ออกอย่างถูกต้อง ผู้สังเกตการณ์จะยังซิงค์ข้อมูลไปยังหน่วยความจำในเครื่องต่อไป เมื่อไม่จำเป็นต้องใช้ผู้สังเกตการณ์แล้ว ให้นำผู้สังเกตการณ์ออกโดยการส่ง FIRDatabaseHandle
ที่เกี่ยวข้องไปยังเมธอด removeObserverWithHandle
เมื่อคุณเพิ่มบล็อก Callback ไปยังข้อมูลอ้างอิง ระบบจะแสดงผล FIRDatabaseHandle
คุณใช้แฮนเดิลเหล่านี้เพื่อนำการบล็อก Callback ออกได้
หากมีการเพิ่ม Listener หลายรายการไปยังการอ้างอิงฐานข้อมูล ระบบจะเรียก Listener แต่ละคนเมื่อเกิดเหตุการณ์ขึ้น หากต้องการหยุดซิงค์ข้อมูลที่ตำแหน่งนั้น คุณต้องนำผู้สังเกตการณ์ทั้งหมดออกจากตำแหน่งหนึ่งๆ ด้วยการเรียกใช้เมธอด removeAllObservers
การเรียกใช้ removeObserverWithHandle
หรือ removeAllObservers
บน Listener จะไม่นำ Listener ที่ลงทะเบียนในโหนดย่อยออกโดยอัตโนมัติ คุณต้องติดตามข้อมูลอ้างอิงหรือแฮนเดิลเหล่านั้นด้วย เพื่อนำออก
บันทึกข้อมูลเป็นธุรกรรม
เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน เช่น ตัวนับที่เพิ่มขึ้น คุณสามารถใช้การดำเนินการธุรกรรมได้ คุณให้อาร์กิวเมนต์ 2 รายการนี้สำหรับการดำเนินการนี้ ได้แก่ ฟังก์ชันอัปเดต และ Callback สำหรับการเติมข้อมูลที่ไม่บังคับ ฟังก์ชันอัปเดตจะใช้สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์ และแสดงผลสถานะใหม่ที่ต้องการเขียน
เช่น ในตัวอย่างแอปการเขียนบล็อกโซเชียล คุณสามารถอนุญาตให้ผู้ใช้ติดดาวและยกเลิกการติดดาวโพสต์ และติดตามจำนวนดาวที่โพสต์ได้รับ ดังนี้
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 จะซิงค์ข้อมูลนั้นกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอ็นต์อื่นๆ อย่าง "ดีที่สุด"
ด้วยเหตุนี้ การเขียนทั้งหมดไปยังฐานข้อมูลจะทริกเกอร์เหตุการณ์ในเครื่องทันที ก่อนที่จะมีการเขียนข้อมูลไปยังเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปจะยังคงตอบสนองอยู่เสมอไม่ว่าเครือข่ายจะใช้เวลาในการตอบสนองหรือการเชื่อมต่อเป็นอย่างไร
เมื่อมีการเชื่อมต่ออีกครั้ง แอปจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้องเขียนโค้ดที่กำหนดเองใดๆ
เราจะพูดคุยเพิ่มเติมเกี่ยวกับพฤติกรรมแบบออฟไลน์ในดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถทางออนไลน์และออฟไลน์