ความสามารถออฟไลน์บนแพลตฟอร์ม Apple

แอปพลิเคชัน Firebase จะทำงานได้แม้ว่าแอปของคุณจะขาดการเชื่อมต่อเครือข่าย ชั่วคราว นอกจากนี้ Firebase ยังมีเครื่องมือสำหรับเก็บข้อมูลไว้ในเครื่อง จัดการสถานะการออนไลน์ และจัดการเวลาในการตอบสนอง

การเก็บข้อมูลไว้ในดิสก์

แอป Firebase จะจัดการการหยุดชะงักของเครือข่ายชั่วคราวโดยอัตโนมัติ ข้อมูลที่แคชไว้จะพร้อมใช้งานขณะออฟไลน์ และ Firebase จะส่งการเขียนซ้ำ เมื่อมีการเชื่อมต่อเครือข่ายอีกครั้ง

เมื่อคุณเปิดใช้การเก็บข้อมูลไว้ในดิสก์ แอปจะเขียนข้อมูลลงใน อุปกรณ์ในเครื่องเพื่อให้แอปคงสถานะไว้ได้ขณะออฟไลน์ แม้ว่าผู้ใช้ หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม

คุณเปิดใช้การเก็บข้อมูลไว้ในดิสก์ได้ด้วยโค้ดเพียงบรรทัดเดียว

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
Database.database().isPersistenceEnabled = true

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
[FIRDatabase database].persistenceEnabled = YES;

ลักษณะการทำงานของการเก็บข้อมูล

เมื่อเปิดใช้การเก็บข้อมูลไว้ในดิสก์ ไคลเอ็นต์ Firebase Realtime Database จะซิงค์ข้อมูลใดก็ตามขณะออนไลน์และเก็บข้อมูลนั้นไว้ในดิสก์เพื่อให้พร้อมใช้งานแบบออฟไลน์ แม้ว่าผู้ใช้หรือระบบปฏิบัติการจะรีสตาร์ทแอปก็ตาม ซึ่งหมายความว่าแอปจะทำงานเหมือนกับตอนออนไลน์โดยใช้ข้อมูลในเครื่องที่จัดเก็บไว้ในแคช การเรียกกลับของ Listener จะยังคงทำงานสำหรับการอัปเดตในเครื่อง

ไคลเอ็นต์ Firebase Realtime Database จะเก็บคิวการดำเนินการเขียนทั้งหมด ที่ดำเนินการขณะแอปออฟไลน์ไว้โดยอัตโนมัติ เมื่อเปิดใช้การเก็บข้อมูลไว้ในดิสก์ ระบบจะเก็บคิวนี้ไว้ในดิสก์ด้วยเพื่อให้การเขียนทั้งหมดพร้อมใช้งานเมื่อผู้ใช้หรือระบบปฏิบัติการรีสตาร์ทแอป เมื่อแอปกลับมาเชื่อมต่อได้อีกครั้ง ระบบจะส่งการดำเนินการทั้งหมดไปยังเซิร์ฟเวอร์ Firebase Realtime Database

หากแอปใช้ การตรวจสอบสิทธิ์ Firebase, ไคลเอ็นต์ Firebase Realtime Database จะเก็บโทเค็นการตรวจสอบสิทธิ์ของผู้ใช้ไว้เมื่อรีสตาร์ทแอป หากโทเค็นการตรวจสอบสิทธิ์หมดอายุขณะแอปออฟไลน์ ไคลเอ็นต์จะหยุด การดำเนินการเขียนไว้ชั่วคราวจนกว่าแอปจะตรวจสอบสิทธิ์ผู้ใช้อีกครั้ง ไม่เช่นนั้น การดำเนินการเขียนอาจล้มเหลวเนื่องจากกฎความปลอดภัย

อัปเดตข้อมูลอยู่เสมอ

Firebase Realtime Database จะซิงโครไนซ์และจัดเก็บสำเนาข้อมูลในเครื่องสำหรับ Listener ที่ใช้งานอยู่ นอกจากนี้ คุณยังซิงค์ตำแหน่งที่เฉพาะเจาะจง ได้ด้วย

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

ไคลเอ็นต์ Firebase Realtime Database จะดาวน์โหลดข้อมูลในตำแหน่งเหล่านี้โดยอัตโนมัติและซิงค์ข้อมูลไว้เสมอ แม้ว่าการอ้างอิงจะไม่มี Listener ที่ใช้งานอยู่ก็ตาม คุณปิดการซิงโครไนซ์อีกครั้งได้ด้วย โค้ดบรรทัดต่อไปนี้

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
scoresRef.keepSynced(false)

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
[scoresRef keepSynced:NO];

ระบบจะแคชข้อมูลที่ซิงค์ไว้ก่อนหน้านี้ 10 MB โดยค่าเริ่มต้น ซึ่งควรเพียงพอสำหรับแอปพลิเคชันส่วนใหญ่ หากแคชมีขนาดใหญ่กว่าขนาดที่กำหนดค่าไว้ Firebase Realtime Database จะล้างข้อมูลที่ใช้ล่าสุดน้อยที่สุด ระบบจะไม่ล้างข้อมูลที่ซิงค์ไว้จากแคช

การค้นหาข้อมูลแบบออฟไลน์

Firebase Realtime Database จะจัดเก็บข้อมูลที่ส่งกลับจากการค้นหาเพื่อใช้ เมื่อออฟไลน์ สำหรับการค้นหาที่สร้างขึ้นขณะออฟไลน์ Firebase Realtime Database จะยังคงทำงานกับข้อมูลที่โหลดไว้ก่อนหน้านี้ หากข้อมูลที่ขอไม่ได้โหลดไว้ Firebase Realtime Database จะโหลด ข้อมูลจากแคชในเครื่อง เมื่อมีการเชื่อมต่อเครือข่ายอีกครั้ง ข้อมูลจะโหลดและแสดงผลการค้นหา

ตัวอย่างเช่น โค้ดนี้จะค้นหารายการ 4 รายการล่าสุดใน Firebase Realtime Database ที่มีคะแนน

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[[[scoresRef queryOrderedByValue] queryLimitedToLast:4]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

สมมติว่าผู้ใช้ขาดการเชื่อมต่อ ไปที่โหมดออฟไลน์ และรีสตาร์ทแอป ขณะที่ยังออฟไลน์อยู่ แอปจะค้นหารายการ 2 รายการล่าสุดจาก ตำแหน่งเดียวกัน การค้นหานี้จะแสดงรายการ 2 รายการล่าสุดได้สำเร็จ เนื่องจากแอปได้โหลดรายการทั้ง 4 รายการในการค้นหาข้างต้นแล้ว

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

ในตัวอย่างก่อนหน้า ไคลเอ็นต์ Firebase Realtime Database จะแสดงเหตุการณ์ "child added" สำหรับไดโนเสาร์ 2 ตัวที่มีคะแนนสูงสุดโดยใช้แคชที่เก็บไว้ แต่จะไม่แสดงเหตุการณ์ 'value' เนื่องจากแอปไม่เคยดำเนินการค้นหานั้นขณะออนไลน์

หากแอปขอรายการ 6 รายการล่าสุดขณะออฟไลน์ แอปจะแสดงเหตุการณ์ 'child added' สำหรับรายการ 4 รายการที่แคชไว้ทันที เมื่ออุปกรณ์กลับมาออนไลน์ ไคลเอ็นต์ Firebase Realtime Database จะซิงโครไนซ์กับเซิร์ฟเวอร์และแสดงเหตุการณ์ "child added" 2 รายการสุดท้ายและเหตุการณ์ "value" สำหรับแอป

การจัดการธุรกรรมแบบออฟไลน์

ระบบจะจัดคิวธุรกรรมทั้งหมดที่ดำเนินการขณะแอปออฟไลน์ เมื่อแอปกลับมาเชื่อมต่อเครือข่ายได้อีกครั้ง ระบบจะส่งธุรกรรมไปยัง เซิร์ฟเวอร์Realtime Database

การจัดการสถานะการออนไลน์

ในแอปพลิเคชันแบบเรียลไทม์ การตรวจหาเวลาที่ไคลเอ็นต์ เชื่อมต่อและยกเลิกการเชื่อมต่อมักจะมีประโยชน์ เช่น คุณอาจ ต้องการทำเครื่องหมายผู้ใช้เป็น "ออฟไลน์" เมื่อไคลเอ็นต์ของผู้ใช้ยกเลิกการเชื่อมต่อ

ไคลเอ็นต์ฐานข้อมูล Firebase มีฟังก์ชันพื้นฐานที่เรียบง่ายซึ่งคุณใช้เขียนลงในฐานข้อมูลได้เมื่อไคลเอ็นต์ยกเลิกการเชื่อมต่อจากเซิร์ฟเวอร์ฐานข้อมูล Firebase การอัปเดตเหล่านี้จะเกิดขึ้นไม่ว่าไคลเอ็นต์จะยกเลิกการเชื่อมต่ออย่างถูกต้องหรือไม่ก็ตาม คุณจึงมั่นใจได้ว่าการอัปเดตจะล้างข้อมูลแม้ว่าการเชื่อมต่อจะขาดหายไป หรือไคลเอ็นต์ขัดข้อง คุณสามารถดำเนินการเขียนทั้งหมด รวมถึงการตั้งค่า การอัปเดต และการนำออกได้เมื่อยกเลิกการเชื่อมต่อ

ต่อไปนี้คือตัวอย่างง่ายๆ ของการเขียนข้อมูลเมื่อยกเลิกการเชื่อมต่อโดยใช้ onDisconnect ฟังก์ชันพื้นฐาน:

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

วิธีการทำงานของ onDisconnect

เมื่อคุณสร้างการดำเนินการ onDisconnect() การดำเนินการ จะอยู่ในเซิร์ฟเวอร์ Firebase Realtime Database เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยเพื่อให้แน่ใจว่าผู้ใช้สามารถดำเนินการเขียนที่ขอได้ และจะแจ้งให้แอปของคุณทราบหากการดำเนินการไม่ถูกต้อง จากนั้นเซิร์ฟเวอร์จะตรวจสอบการเชื่อมต่อ หากการเชื่อมต่อหมดเวลาหรือไคลเอ็นต์ Realtime Database ปิดการเชื่อมต่ออย่างชัดเจน เซิร์ฟเวอร์จะตรวจสอบความปลอดภัยอีกครั้ง (เพื่อให้แน่ใจว่าการดำเนินการยังคงถูกต้อง) แล้วเรียกใช้เหตุการณ์

แอปของคุณสามารถใช้การเรียกกลับในการดำเนินการเขียน เพื่อให้แน่ใจว่าได้แนบ onDisconnect อย่างถูกต้องแล้ว

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

คุณยังยกเลิกเหตุการณ์ onDisconnect ได้ด้วยการเรียกใช้ .cancel()

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

การตรวจหาสถานะการเชื่อมต่อ

สำหรับฟีเจอร์ที่เกี่ยวข้องกับสถานะการออนไลน์หลายๆ ฟีเจอร์ แอปของคุณควรทราบว่าแอปออนไลน์หรือออฟไลน์อยู่เมื่อใด Firebase Realtime Database มีตำแหน่งพิเศษที่ /.info/connected ซึ่ง จะอัปเดตทุกครั้งที่สถานะการเชื่อมต่อของไคลเอ็นต์ Firebase Realtime Database เปลี่ยนไป ตัวอย่างมีดังนี้

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
  if snapshot.value as? Bool ?? false {
    print("Connected")
  } else {
    print("Not connected")
  }
})

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    NSLog(@"connected");
  } else {
    NSLog(@"not connected");
  }
}];

/.info/connected เป็นค่าบูลีนที่ไม่ได้ ซิงโครไนซ์ระหว่างRealtime Databaseไคลเอ็นต์เนื่องจากค่าขึ้นอยู่กับสถานะของไคลเอ็นต์ กล่าวอีกนัยหนึ่งคือ หากไคลเอ็นต์หนึ่ง อ่าน /.info/connected เป็นเท็จ ก็ไม่ได้หมายความว่าไคลเอ็นต์อื่นจะอ่านเป็นเท็จด้วย

การจัดการเวลาในการตอบสนอง

การประทับเวลาของเซิร์ฟเวอร์

เซิร์ฟเวอร์ Firebase Realtime Database มีกลไกในการแทรก การประทับเวลาที่สร้างขึ้นในเซิร์ฟเวอร์เป็นข้อมูล ฟีเจอร์นี้เมื่อใช้ร่วมกับ onDisconnect จะเป็นวิธีที่ง่ายและเชื่อถือได้ในการจดบันทึกเวลาที่ ไคลเอ็นต์ Realtime Database ยกเลิกการเชื่อมต่อ:

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

เวลาของระบบไม่ตรงกัน

แม้ว่า firebase.database.ServerValue.TIMESTAMP จะมีความแม่นยำมากกว่าและเหมาะสำหรับการดำเนินการอ่าน/เขียนส่วนใหญ่ แต่บางครั้งก็มีประโยชน์ในการประมาณเวลาของระบบที่ไม่ตรงกันของไคลเอ็นต์กับเซิร์ฟเวอร์ของ Firebase Realtime Database คุณ สามารถแนบการเรียกกลับไปยังตำแหน่ง /.info/serverTimeOffset เพื่อรับค่าเป็นมิลลิวินาทีที่Firebase Realtime Databaseไคลเอ็นต์ เพิ่มลงในเวลาที่รายงานในเครื่อง (เวลา Epoch เป็นมิลลิวินาที) เพื่อประมาณ เวลาของเซิร์ฟเวอร์ โปรดทราบว่าความแม่นยำของการชดเชยนี้อาจได้รับผลกระทบจาก เวลาในการตอบสนองของเครือข่าย ดังนั้นจึงมีประโยชน์หลักๆ ในการค้นหา ความคลาดเคลื่อนขนาดใหญ่ (> 1 วินาที) ในเวลาของระบบ

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
  if let offset = snapshot.value as? TimeInterval {
    print("Estimated server time in milliseconds: \(Date().timeIntervalSince1970 * 1000 + offset)")
  }
})

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
FIRDatabaseReference *offsetRef = [[FIRDatabase database] referenceWithPath:@".info/serverTimeOffset"];
[offsetRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  NSTimeInterval offset = [(NSNumber *)snapshot.value doubleValue];
  NSTimeInterval estimatedServerTimeMs = [[NSDate date] timeIntervalSince1970] * 1000.0 + offset;
  NSLog(@"Estimated server time: %0.3f", estimatedServerTimeMs);
}];

แอปตัวอย่างสถานะการออนไลน์

การรวมการดำเนินการยกเลิกการเชื่อมต่อกับการตรวจสอบสถานะการเชื่อมต่อและ การประทับเวลาของเซิร์ฟเวอร์จะช่วยให้คุณสร้างระบบสถานะการออนไลน์ของผู้ใช้ได้ ในระบบนี้ ผู้ใช้แต่ละรายจะจัดเก็บข้อมูลไว้ในตำแหน่งฐานข้อมูลเพื่อระบุว่า Realtime Databaseไคลเอ็นต์ออนไลน์อยู่หรือไม่ ไคลเอ็นต์จะตั้งค่าตำแหน่งนี้เป็น "จริง" เมื่อ ออนไลน์และตั้งค่าการประทับเวลาเมื่อยกเลิกการเชื่อมต่อ การประทับเวลานี้ จะระบุเวลาล่าสุดที่ผู้ใช้รายนั้นออนไลน์

โปรดทราบว่าแอปควรจัดคิวการดำเนินการยกเลิกการเชื่อมต่อก่อนที่จะทำเครื่องหมายผู้ใช้เป็น ออนไลน์ เพื่อหลีกเลี่ยงสภาวะการแข่งขันในกรณีที่การเชื่อมต่อเครือข่ายของไคลเอ็นต์ ขาดหายไปก่อนที่จะส่งคำสั่งทั้ง 2 รายการไปยังเซิร์ฟเวอร์ได้

ต่อไปนี้คือระบบสถานะการออนไลน์ของผู้ใช้แบบง่าย

Swift

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections")

// stores the timestamp of my last disconnect (the last time I was seen online)
let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")

let connectedRef = Database.database().reference(withPath: ".info/connected")

connectedRef.observe(.value, with: { snapshot in
  // only handle connection established (or I've reconnected after a loss of connection)
  guard snapshot.value as? Bool ?? false else { return }

  // add this device to my connections list
  let con = myConnectionsRef.childByAutoId()

  // when this device disconnects, remove it.
  con.onDisconnectRemoveValue()

  // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
  // where you set the user's presence to true and the client disconnects before the
  // onDisconnect() operation takes effect, leaving a ghost user.

  // this value could contain info about the device or a timestamp instead of just true
  con.setValue(true)

  // when I disconnect, update the last time I was seen online
  lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
})

Objective-C

หมายเหตุ: ผลิตภัณฑ์ Firebase นี้ไม่พร้อมใช้งานในเป้าหมาย App Clip
// since I can connect from multiple devices, we store each connection instance separately
// any time that connectionsRef's value is null (i.e. has no children) I am offline
FIRDatabaseReference *myConnectionsRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/connections"];

// stores the timestamp of my last disconnect (the last time I was seen online)
FIRDatabaseReference *lastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];

FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    // connection established (or I've reconnected after a loss of connection)

    // add this device to my connections list
    FIRDatabaseReference *con = [myConnectionsRef childByAutoId];

    // when this device disconnects, remove it
    [con onDisconnectRemoveValue];

    // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
    // where you set the user's presence to true and the client disconnects before the
    // onDisconnect() operation takes effect, leaving a ghost user.

    // this value could contain info about the device or a timestamp instead of just true
    [con setValue:@YES];


    // when I disconnect, update the last time I was seen online
    [lastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
  }
}];