(ไม่บังคับ) สร้างต้นแบบและทดสอบด้วย Firebase Local Emulator Suite
ก่อนที่จะพูดถึงวิธีที่แอปของคุณอ่านและเขียนไปยัง Realtime Database เราขอแนะนำชุดเครื่องมือที่คุณสามารถใช้เพื่อสร้างต้นแบบและทดสอบการทำงานของฐานข้อมูลเรียลไทม์: Firebase Local Emulator Suite หากคุณกำลังลองใช้โมเดลข้อมูลต่างๆ ปรับกฎความปลอดภัยให้เหมาะสม หรือหาวิธีโต้ตอบกับแบ็คเอนด์ที่คุ้มค่าที่สุด ความสามารถในการทำงานแบบโลคัลโดยไม่ต้องปรับใช้บริการที่ใช้งานจริงอาจเป็นแนวคิดที่ดี
โปรแกรมจำลองฐานข้อมูลเรียลไทม์เป็นส่วนหนึ่งของ Local Emulator Suite ซึ่งช่วยให้แอปของคุณสามารถโต้ตอบกับเนื้อหาและการกำหนดค่าฐานข้อมูลจำลองของคุณ ตลอดจนเลือกทรัพยากรโครงการจำลองของคุณ (ฟังก์ชัน ฐานข้อมูลอื่นๆ และกฎความปลอดภัย)
การใช้โปรแกรมจำลองฐานข้อมูลเรียลไทม์เกี่ยวข้องกับขั้นตอนเพียงไม่กี่ขั้นตอน:
- การเพิ่มบรรทัดโค้ดในการกำหนดค่าการทดสอบของแอปเพื่อเชื่อมต่อกับโปรแกรมจำลอง
- จากรูทของไดเร็กทอรีโปรเจ็กต์ในเครื่องของคุณ ให้รัน
firebase emulators:start
- ทำการเรียกจากโค้ดต้นแบบของแอปโดยใช้ SDK ของแพลตฟอร์ม Realtime Database ตามปกติ หรือใช้ Realtime Database REST API
คำแนะนำโดยละเอียดเกี่ยวกับฐานข้อมูลเรียลไทม์และฟังก์ชันคลาวด์ มีให้ใช้งาน คุณควรดูที่ การแนะนำ Local Emulator Suite
รับ FIRDatabaseReference
ในการอ่านหรือเขียนข้อมูลจากฐานข้อมูล คุณต้องมีอินสแตนซ์ของ FIRDatabaseReference
:
สวิฟต์
var ref: DatabaseReference! ref = Database.database().reference()
วัตถุประสงค์-C
@property (strong, nonatomic) FIRDatabaseReference *ref; self.ref = [[FIRDatabase database] reference];
เขียนข้อมูล
เอกสารนี้ครอบคลุมพื้นฐานการอ่านและเขียนข้อมูล Firebase
ข้อมูล Firebase ถูกเขียนไปยังการอ้างอิง Database
และดึงข้อมูลโดยแนบตัวฟังแบบอะซิงโครนัสกับการอ้างอิง ผู้ฟังจะถูกกระตุ้นหนึ่งครั้งสำหรับสถานะเริ่มต้นของข้อมูล และอีกครั้งทุกครั้งที่ข้อมูลเปลี่ยนแปลง
การดำเนินการเขียนขั้นพื้นฐาน
สำหรับการดำเนินการเขียนขั้นพื้นฐาน คุณสามารถใช้ setValue
เพื่อบันทึกข้อมูลไปยังการอ้างอิงที่ระบุ โดยแทนที่ข้อมูลที่มีอยู่ที่เส้นทางนั้น คุณสามารถใช้วิธีนี้เพื่อ:
- ประเภทการส่งผ่านที่สอดคล้องกับประเภท JSON ที่มีดังต่อไปนี้:
-
NSString
-
NSNumber
-
NSDictionary
-
NSArray
-
ตัวอย่างเช่น คุณสามารถเพิ่มผู้ใช้ด้วย setValue
ดังนี้:
สวิฟต์
self.ref.child("users").child(user.uid).setValue(["username": username])
วัตถุประสงค์-C
[[[self.ref child:@"users"] child:authResult.user.uid] setValue:@{@"username": username}];
การใช้ setValue
ด้วยวิธีนี้จะเขียนทับข้อมูลที่ตำแหน่งที่ระบุ รวมถึงโหนดย่อยใดๆ อย่างไรก็ตาม คุณยังสามารถอัปเดตรายการย่อยได้โดยไม่ต้องเขียนวัตถุใหม่ทั้งหมด หากคุณต้องการอนุญาตให้ผู้ใช้อัปเดตโปรไฟล์ คุณสามารถอัปเดตชื่อผู้ใช้ได้ดังนี้:
สวิฟต์
self.ref.child("users/\(user.uid)/username").setValue(username)
วัตถุประสงค์-C
[[[[_ref child:@"users"] child:user.uid] child:@"username"] setValue:username];
อ่านข้อมูล
อ่านข้อมูลโดยการฟังเหตุการณ์ที่มีคุณค่า
หากต้องการอ่านข้อมูลที่เส้นทางและฟังการเปลี่ยนแปลง ให้ใช้ observeEventType:withBlock
ของ FIRDatabaseReference
เพื่อสังเกตเหตุการณ์ FIRDataEventTypeValue
ประเภทเหตุการณ์ | การใช้งานทั่วไป |
---|---|
FIRDataEventTypeValue | อ่านและฟังการเปลี่ยนแปลงเนื้อหาทั้งหมดของเส้นทาง |
คุณสามารถใช้เหตุการณ์ FIRDataEventTypeValue
เพื่ออ่านข้อมูลที่เส้นทางที่กำหนดได้ เนื่องจากเหตุการณ์นั้นมีอยู่ ณ เวลาที่เกิดเหตุการณ์ เมธอดนี้ถูกกระตุ้นหนึ่งครั้งเมื่อแนบ Listener และอีกครั้งทุกครั้งที่ข้อมูล รวมถึงลูก เปลี่ยนแปลง การเรียกกลับเหตุการณ์จะถูกส่งผ่าน snapshot
ที่มีข้อมูลทั้งหมดในตำแหน่งนั้น รวมถึงข้อมูลย่อย หากไม่มีข้อมูล สแน็ปช็อตจะคืน false
เมื่อคุณเรียกการ exists()
และ nil
เมื่อคุณอ่านคุณสมบัติ value
ของมัน
ตัวอย่างต่อไปนี้สาธิตแอปพลิเคชันบล็อกโซเชียลที่ดึงรายละเอียดของโพสต์จากฐานข้อมูล:
สวิฟต์
refHandle = postRef.observe(DataEventType.value, with: { snapshot in // ... })
วัตถุประสงค์-C
_refHandle = [_postRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) { NSDictionary *postDict = snapshot.value; // ... }];
ผู้ฟังได้รับ FIRDataSnapshot
ที่มีข้อมูลในตำแหน่งที่ระบุในฐานข้อมูล ณ เวลาที่เกิดเหตุการณ์ในคุณสมบัติ value
คุณสามารถกำหนดค่าให้กับประเภทเนทีฟที่เหมาะสม เช่น NSDictionary
หากไม่มีข้อมูลที่ตำแหน่งนั้น value
จะเป็น nil
อ่านข้อมูลครั้งเดียว
อ่านครั้งเดียวโดยใช้ getData()
SDK ออกแบบมาเพื่อจัดการการโต้ตอบกับเซิร์ฟเวอร์ฐานข้อมูล ไม่ว่าแอปของคุณจะออนไลน์หรือออฟไลน์
โดยทั่วไป คุณควรใช้เทคนิคเหตุการณ์มูลค่าที่อธิบายไว้ด้านบนเพื่ออ่านข้อมูลเพื่อรับการแจ้งเตือนการอัปเดตข้อมูลจากแบ็กเอนด์ เทคนิคเหล่านั้นลดการใช้งานและการเรียกเก็บเงินของคุณ และได้รับการปรับปรุงเพื่อให้ผู้ใช้ของคุณได้รับประสบการณ์ที่ดีที่สุดเมื่อพวกเขาออนไลน์และออฟไลน์
หากคุณต้องการข้อมูลเพียงครั้งเดียว คุณสามารถใช้ getData()
เพื่อรับภาพรวมของข้อมูลจากฐานข้อมูล หาก getData()
ไม่สามารถส่งคืนค่าเซิร์ฟเวอร์ด้วยเหตุผลใดๆ ก็ตาม ไคลเอนต์จะตรวจสอบแคชหน่วยเก็บข้อมูลในเครื่องและส่งกลับข้อผิดพลาดหากยังไม่พบค่า
ตัวอย่างต่อไปนี้สาธิตการดึงชื่อผู้ใช้ที่เปิดเผยต่อสาธารณะของผู้ใช้ในครั้งเดียวจากฐานข้อมูล:
สวิฟต์
ref.child("users/\(uid)/username").getData(completion: { error, snapshot in guard error == nil else { print(error!.localizedDescription) return; } let userName = snapshot.value as? String ?? "Unknown"; });
วัตถุประสงค์-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()
โดยไม่จำเป็นสามารถเพิ่มการใช้แบนด์วิธและนำไปสู่การสูญเสียประสิทธิภาพ ซึ่งสามารถป้องกันได้โดยใช้ตัวฟังแบบเรียลไทม์ดังที่แสดงไว้ด้านบน
อ่านข้อมูลครั้งเดียวกับผู้สังเกตการณ์
ในบางกรณี คุณอาจต้องการคืนค่าจากแคชในเครื่องทันที แทนที่จะตรวจสอบหาค่าที่อัปเดตบนเซิร์ฟเวอร์ ในกรณีเหล่านั้น คุณสามารถใช้ observeSingleEventOfType
เพื่อรับข้อมูลจากดิสก์แคชในเครื่องได้ทันที
วิธีนี้มีประโยชน์สำหรับข้อมูลที่จำเป็นต้องโหลดเพียงครั้งเดียวและไม่ได้คาดว่าจะเปลี่ยนแปลงบ่อยหรือต้องการการฟังอย่างกระตือรือร้น ตัวอย่างเช่น แอปบล็อกในตัวอย่างก่อนหน้านี้ใช้วิธีนี้เพื่อโหลดโปรไฟล์ของผู้ใช้เมื่อพวกเขาเริ่มเขียนโพสต์ใหม่:
สวิฟต์
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) }
วัตถุประสงค์-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
คุณสามารถอัพเดตค่าลูกระดับล่างได้โดยระบุพาธสำหรับคีย์ หากข้อมูลถูกจัดเก็บไว้ในหลายตำแหน่งเพื่อให้ปรับขนาดได้ดีขึ้น คุณสามารถอัปเดต อินสแตนซ์ทั้งหมดของข้อมูลนั้นโดยใช้การกระจายข้อมูล ตัวอย่างเช่น แอปโซเชียลบล็อกอาจต้องการสร้างโพสต์และอัปเดตเป็นฟีดกิจกรรมล่าสุดและฟีดกิจกรรมของผู้ใช้ที่โพสต์พร้อมกัน ในการทำเช่นนี้ แอปพลิเคชันบล็อกจะใช้โค้ดดังนี้:
สวิฟต์
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)
วัตถุประสงค์-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()
จากนั้นสามารถใช้คีย์เพื่อสร้างรายการที่สองในโพสต์ของผู้ใช้ที่ /user-posts/$userid/$postid
เมื่อใช้เส้นทางเหล่านี้ คุณสามารถดำเนินการอัปเดตหลายตำแหน่งพร้อมกันในแผนผัง JSON ด้วยการเรียก updateChildValues
เพียงครั้งเดียว เช่นตัวอย่างนี้สร้างโพสต์ใหม่ในทั้งสองตำแหน่งได้อย่างไร การอัปเดตพร้อมกันด้วยวิธีนี้ถือเป็นแบบปรมาณู: การอัปเดตทั้งหมดสำเร็จหรือการอัปเดตทั้งหมดล้มเหลว
เพิ่มบล็อกที่เสร็จสมบูรณ์
หากคุณต้องการทราบว่าข้อมูลของคุณถูกคอมมิตเมื่อใด คุณสามารถเพิ่มบล็อกการทำให้เสร็จสมบูรณ์ได้ ทั้ง setValue
และ updateChildValues
ใช้บล็อกการทำให้สมบูรณ์ซึ่งเป็นทางเลือกซึ่งถูกเรียกเมื่อการเขียนถูกคอมมิตไปยังฐานข้อมูล ผู้ฟังนี้มีประโยชน์ในการติดตามว่าข้อมูลใดได้รับการบันทึกและข้อมูลใดบ้างที่ยังคงซิงโครไนซ์อยู่ หากการโทรไม่สำเร็จ ผู้ฟังจะถูกส่งผ่านออบเจกต์ข้อผิดพลาดเพื่อระบุว่าเหตุใดจึงเกิดความล้มเหลว
สวิฟต์
ref.child("users").child(user.uid).setValue(["username": username]) { (error:Error?, ref:DatabaseReference) in if let error = error { print("Data could not be saved: \(error).") } else { print("Data saved successfully!") } }
วัตถุประสงค์-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
เมื่อคุณเพิ่มบล็อกการโทรกลับในการอ้างอิง FIRDatabaseHandle
จะถูกส่งกลับ หมายเลขอ้างอิงเหล่านี้สามารถใช้เพื่อลบบล็อกการโทรกลับ
หากมีการเพิ่มผู้ฟังหลายคนในการอ้างอิงฐานข้อมูล ผู้ฟังแต่ละคนจะถูกเรียกเมื่อมีเหตุการณ์เกิดขึ้น หากต้องการหยุดซิงค์ข้อมูลที่ตำแหน่งนั้น คุณต้องลบผู้สังเกตการณ์ทั้งหมดออกจากตำแหน่งนั้นโดยเรียกเมธอด removeAllObservers
การเรียก removeObserverWithHandle
หรือ removeAllObservers
บนผู้ฟังไม่ได้ลบผู้ฟังที่ลงทะเบียนบนโหนดย่อยโดยอัตโนมัติ คุณต้องติดตามการอ้างอิงหรือหมายเลขอ้างอิงเหล่านั้นเพื่อลบออก
บันทึกข้อมูลเป็นธุรกรรม
เมื่อทำงานกับข้อมูลที่อาจเสียหายจากการแก้ไขพร้อมกัน เช่น ตัวนับส่วนเพิ่ม คุณสามารถใช้ การดำเนินการธุรกรรม ได้ คุณให้อาร์กิวเมนต์สองข้อแก่การดำเนินการนี้: ฟังก์ชันอัปเดตและการเรียกกลับที่สมบูรณ์ซึ่งเป็นทางเลือก ฟังก์ชันอัปเดตใช้สถานะปัจจุบันของข้อมูลเป็นอาร์กิวเมนต์และส่งคืนสถานะใหม่ที่คุณต้องการเขียน
ตัวอย่างเช่น ในตัวอย่างแอปโซเชียลบล็อก คุณสามารถอนุญาตให้ผู้ใช้ติดดาวและเลิกติดดาวโพสต์ และติดตามจำนวนดาวที่โพสต์ได้รับดังนี้:
สวิฟต์
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) } }
วัตถุประสงค์-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
หากไม่มีเลย เซิร์ฟเวอร์จะเปรียบเทียบค่าเริ่มต้นกับมูลค่าปัจจุบันและยอมรับธุรกรรมหากค่าตรงกันหรือปฏิเสธ หากธุรกรรมถูกปฏิเสธ เซิร์ฟเวอร์จะส่งกลับค่าปัจจุบันไปยังไคลเอ็นต์ ซึ่งจะเรียกใช้ธุรกรรมอีกครั้งด้วยมูลค่าที่อัปเดต สิ่งนี้จะเกิดขึ้นซ้ำๆ จนกว่าการทำธุรกรรมจะได้รับการยอมรับหรือมีการพยายามหลายครั้งเกินไป
Atomic ฝั่งเซิร์ฟเวอร์ที่เพิ่มขึ้น
ในกรณีการใช้งานข้างต้น เรากำลังเขียนค่าสองค่าลงในฐานข้อมูล: ID ของผู้ใช้ที่ติดดาว/เลิกติดดาวโพสต์ และจำนวนดาวที่เพิ่มขึ้น หากเราทราบอยู่แล้วว่าผู้ใช้กำลังแสดงโพสต์ เราสามารถใช้การดำเนินการเพิ่มอะตอมแทนธุรกรรมได้
สวิฟต์
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);
วัตถุประสงค์-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 จะซิงโครไนซ์ข้อมูลนั้นกับเซิร์ฟเวอร์ฐานข้อมูลระยะไกลและกับไคลเอนต์อื่นๆ บนพื้นฐาน "ความพยายามอย่างดีที่สุด"
ด้วยเหตุนี้ การเขียนทั้งหมดไปยังฐานข้อมูลจะทริกเกอร์เหตุการณ์ในเครื่องทันที ก่อนที่ข้อมูลใดๆ จะถูกเขียนไปยังเซิร์ฟเวอร์ ซึ่งหมายความว่าแอปของคุณยังคงตอบสนองโดยไม่คำนึงถึงเวลาแฝงของเครือข่ายหรือการเชื่อมต่อ
เมื่อสร้างการเชื่อมต่อใหม่แล้ว แอปของคุณจะได้รับชุดเหตุการณ์ที่เหมาะสมเพื่อให้ไคลเอนต์ซิงค์กับสถานะเซิร์ฟเวอร์ปัจจุบัน โดยไม่ต้องเขียนโค้ดที่กำหนดเองใดๆ
เราจะพูดถึงพฤติกรรมออฟไลน์เพิ่มเติมใน เรียนรู้เพิ่มเติมเกี่ยวกับความสามารถออนไลน์และออฟไลน์