(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย 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 ที่ใช้ได้มีดังนี้
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
เพื่ออ่านข้อมูลในเส้นทางที่กำหนด
ตามที่มีอยู่ในเวลาที่เกิดเหตุการณ์ ระบบจะเรียกใช้วิธีนี้ 1 ครั้งเมื่อแนบ
เครื่องมือฟัง และอีกครั้งทุกครั้งที่ข้อมูล รวมถึงข้อมูลขององค์ประกอบย่อยมีการเปลี่ยนแปลง
ระบบจะส่ง 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; // ... }];
ผู้ฟังจะได้รับ 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()
โดยไม่จำเป็นอาจเพิ่มการใช้แบนด์วิดท์และทำให้ประสิทธิภาพลดลง
ซึ่งป้องกันได้โดยใช้เครื่องมือตรวจสอบแบบเรียลไทม์ตามที่แสดง
ด้านบน
อ่านข้อมูลครั้งเดียวด้วย 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 เพื่อระบุสาเหตุที่ทำให้เกิดข้อผิดพลาด
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
ผู้สังเกตการณ์จะไม่หยุดซิงค์ข้อมูลโดยอัตโนมัติเมื่อคุณออกจาก
ViewController
หากไม่ได้นำ Observer ออกอย่างถูกต้อง Observer จะยังคงซิงค์ข้อมูลไปยังหน่วยความจำในเครื่องต่อไป เมื่อไม่ต้องการใช้ Observer อีกต่อไป ให้นำออกโดยส่ง FIRDatabaseHandle
ที่เกี่ยวข้องไปยังเมธอด removeObserverWithHandle
เมื่อเพิ่มบล็อกการโทรกลับในการอ้างอิง ระบบจะแสดงผล FIRDatabaseHandle
คุณใช้แฮนเดิลเหล่านี้เพื่อนำการบล็อกการเรียกกลับออกได้
หากมีการเพิ่ม Listener หลายรายการลงในการอ้างอิงฐานข้อมูล ระบบจะเรียกใช้ Listener แต่ละรายการเมื่อมีการเรียกเหตุการณ์ หากต้องการหยุดซิงค์ข้อมูลในตำแหน่งนั้น
คุณต้องนำผู้สังเกตการณ์ทั้งหมดในตำแหน่งออกโดยเรียกใช้เมธอด 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 จะซิงโครไนซ์ข้อมูลดังกล่าวกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกล และกับไคลเอ็นต์อื่นๆ โดยพยายามอย่างเต็มที่
ด้วยเหตุนี้ การเขียนทั้งหมดไปยังฐานข้อมูลจึงทําให้เกิดเหตุการณ์ในเครื่องทันที ก่อนที่จะมีการเขียนข้อมูลใดๆ ไปยังเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปจะยังคงตอบสนองได้ไม่ว่าเครือข่ายจะมีเวลาในการตอบสนองหรือการเชื่อมต่อเป็นอย่างไร
เมื่อมีการเชื่อมต่ออีกครั้ง แอปจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอ็นต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบันโดยไม่ต้องเขียนโค้ดที่กำหนดเอง
เราจะพูดถึงพฤติกรรมออฟไลน์เพิ่มเติมในส่วนดูข้อมูลเพิ่มเติมเกี่ยวกับความสามารถออนไลน์และออฟไลน์