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