การเปิดใช้งานความสามารถออฟไลน์

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

ความคงทนของดิสก์

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

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

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

FirebaseDatabase.instance.setPersistenceEnabled(true);

พฤติกรรมความต่อเนื่อง

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

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

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

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

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

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.keepSynced(true);

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

scoresRef.keepSynced(false);

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

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

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

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

final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.orderByValue().limitToLast(4).onChildAdded.listen((event) {
  debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});

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

scoresRef.orderByValue().limitToLast(2).onChildAdded.listen((event) {
  debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});

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

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

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

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

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

การจัดการการตรวจหาบุคคลในบ้าน

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

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

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

final presenceRef = FirebaseDatabase.instance.ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");

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

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

try {
    await presenceRef.onDisconnect().remove();
} catch (error) {
    debugPrint("Could not establish onDisconnect event: $error");
}

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

final onDisconnectRef = presenceRef.onDisconnect();
onDisconnectRef.set("I disconnected");
// ...
// some time later when we change our minds
// ...
onDisconnectRef.cancel();

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

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

final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
  final connected = event.snapshot.value as bool? ?? false;
  if (connected) {
    debugPrint("Connected.");
  } else {
    debugPrint("Not connected.");
  }
});

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

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

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

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

final userLastOnlineRef =
    FirebaseDatabase.instance.ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(ServerValue.timestamp);

Clock Skew

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

final offsetRef = FirebaseDatabase.instance.ref(".info/serverTimeOffset");
offsetRef.onValue.listen((event) {
  final offset = event.snapshot.value as num? ?? 0.0;
  final estimatedServerTimeMs =
      DateTime.now().millisecondsSinceEpoch + offset;
});

แอป Presence ตัวอย่าง

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

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

// 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.
final myConnectionsRef =
    FirebaseDatabase.instance.ref("users/joe/connections");

// Stores the timestamp of my last disconnect (the last time I was seen online)
final lastOnlineRef =
    FirebaseDatabase.instance.ref("/users/joe/lastOnline");

final connectedRef = FirebaseDatabase.instance.ref(".info/connected");
connectedRef.onValue.listen((event) {
  final connected = event.snapshot.value as bool? ?? false;
  if (connected) {
    final con = myConnectionsRef.push();

    // When this device disconnects, remove it.
    con.onDisconnect().remove();

    // When I disconnect, update the last time I was seen online.
    lastOnlineRef.onDisconnect().set(ServerValue.timestamp);

    // Add this device to my connections list.
    // This value could contain info about the device or a timestamp too.
    con.set(true);
  }
});