تعمل تطبيقات Firebase حتى إذا انقطع اتصال تطبيقك بالشبكة مؤقتًا. بالإضافة إلى ذلك، توفّر Firebase أدوات لتخزين البيانات بشكل دائم على الجهاز، وإدارة حالة الاتصال، والتعامل مع وقت الاستجابة.
الاحتفاظ بالبيانات على القرص
تتعامل تطبيقات Firebase تلقائيًا مع الانقطاعات المؤقتة في الشبكة. تتوفّر البيانات المخزّنة مؤقتًا أثناء عدم الاتصال بالإنترنت، ويعيد Firebase إرسال أي عمليات كتابة عند استعادة الاتصال بالشبكة.
عند تفعيل ميزة استمرار البيانات على القرص، يكتب تطبيقك البيانات محليًا على الجهاز، ما يتيح له الاحتفاظ بالحالة بلا إنترنت، حتى إذا أعاد المستخدم أو نظام التشغيل تشغيل التطبيق.
يمكنك تفعيل ميزة استمرار البيانات على القرص باستخدام سطر واحد فقط من الرمز البرمجي.
FirebaseDatabase.instance.setPersistenceEnabled(true);
سلوك الاستمرار
من خلال تفعيل ميزة استمرار البيانات، يتم الاحتفاظ على القرص بأي بيانات يزامنها برنامج قاعدة بيانات Firebase في الوقت الفعلي أثناء الاتصال بالإنترنت، وتصبح هذه البيانات متاحة بلا إنترنت، حتى إذا أعاد المستخدم أو نظام التشغيل تشغيل التطبيق. وهذا يعني أنّ تطبيقك يعمل كما لو كان متصلاً بالإنترنت من خلال استخدام البيانات المحلية المخزَّنة في ذاكرة التخزين المؤقت. سيستمر تشغيل عمليات رد الاتصال الخاصة بمعالجة الأحداث للمستمعين عند إجراء تعديلات محلية.
يحتفظ برنامج Firebase Realtime Database تلقائيًا بقائمة انتظار لجميع عمليات الكتابة التي يتم تنفيذها أثناء عدم اتصال تطبيقك بالإنترنت. عند تفعيل وضع الثبات، يتم أيضًا حفظ هذه قائمة الانتظار على القرص، وبالتالي تصبح جميع عمليات الكتابة متاحة عندما يعيد المستخدم أو نظام التشغيل تشغيل التطبيق. وعندما يستعيد التطبيق الاتصال، يتم إرسال جميع العمليات إلى خادم Firebase Realtime Database.
إذا كان تطبيقك يستخدم مصادقة Firebase، سيحتفظ عميل قاعدة بيانات Firebase في الوقت الفعلي برمز المصادقة المميز للمستخدم عند إعادة تشغيل التطبيق. إذا انتهت صلاحية رمز المصادقة المميز أثناء عدم اتصال تطبيقك بالإنترنت، سيوقف العميل عمليات الكتابة مؤقتًا إلى أن يعيد تطبيقك مصادقة المستخدم، وإلا قد تفشل عمليات الكتابة بسبب قواعد الأمان.
الحفاظ على حداثة البيانات
تتم مزامنة قاعدة بيانات Firebase في الوقت الفعلي وتخزين نسخة محلية من البيانات للمستمعين النشطين. بالإضافة إلى ذلك، يمكنك إبقاء مواقع جغرافية معيّنة متزامنة.
final scoresRef = FirebaseDatabase.instance.ref("scores");
scoresRef.keepSynced(true);
ينزّل برنامج قاعدة بيانات Firebase في الوقت الفعلي تلقائيًا البيانات في هذه المواقع الجغرافية ويحافظ على مزامنتها حتى إذا لم يكن للمرجع أي متتبِّعين نشطين. يمكنك إيقاف المزامنة مجددًا باستخدام سطر الرمز التالي.
scoresRef.keepSynced(false);
يتم تلقائيًا تخزين 10 ميغابايت من البيانات التي تمت مزامنتها سابقًا مؤقتًا. من المفترض أن يكون ذلك كافيًا لمعظم التطبيقات. إذا تجاوز حجم ذاكرة التخزين المؤقت الحجم الذي تم ضبطه، ستزيل "قاعدة بيانات Firebase في الوقت الفعلي" البيانات التي تم استخدامها الأقل مؤخرًا. لا تتم إزالة البيانات التي تتم مزامنتها من ذاكرة التخزين المؤقت.
الاستعلام عن البيانات بلا إنترنت
تخزِّن "قاعدة بيانات Firebase في الوقت الفعلي" البيانات التي يتم عرضها من طلب بحث لاستخدامها عندما تكون غير متصل بالإنترنت. بالنسبة إلى طلبات البحث التي تم إنشاؤها بلا إنترنت، تستمر قاعدة بيانات Firebase في الوقت الفعلي في العمل مع البيانات التي تم تحميلها سابقًا. إذا لم يتم تحميل البيانات المطلوبة، ستحمّل "قاعدة بيانات Firebase في الوقت الفعلي" البيانات من ذاكرة التخزين المؤقت المحلية. عندما يتوفّر اتصال بالشبكة مجددًا، سيتم تحميل البيانات وستعرض نتائج البحث.
على سبيل المثال، تستعلم هذه التعليمة البرمجية عن آخر أربعة عناصر في قاعدة بيانات للنتائج:
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}.");
});
لنفترض أنّ المستخدم فقد الاتصال بالإنترنت، وأصبح غير متصل، وأعاد تشغيل التطبيق. أثناء عدم الاتصال بالإنترنت، يستعلم التطبيق عن آخر عنصرَين من الموقع الجغرافي نفسه. سيعرض طلب البحث هذا آخر عنصرَين بنجاح لأنّ التطبيق حمّل جميع العناصر الأربعة في طلب البحث أعلاه.
scoresRef.orderByValue().limitToLast(2).onChildAdded.listen((event) {
debugPrint("The ${event.snapshot.key} dinosaur's score is ${event.snapshot.value}.");
});
في المثال السابق، يرسل برنامج قاعدة بيانات Firebase في الوقت الفعلي حدثَي "تمت إضافة عنصر فرعي" لأعلى ديناصورَين من حيث النتيجة، وذلك باستخدام ذاكرة التخزين المؤقت الثابتة. ولكن لن يتم تسجيل حدث "القيمة"، لأنّ التطبيق لم ينفّذ هذا الطلب مطلقًا عندما كان متصلاً بالإنترنت.
إذا طلب التطبيق آخر ستة عناصر أثناء عدم الاتصال بالإنترنت، سيتلقّى أحداث "تمت إضافة طفل" للعناصر الأربعة المخزّنة مؤقتًا على الفور. عندما يعود الجهاز إلى الاتصال بالإنترنت، تتم مزامنة برنامج قاعدة بيانات Firebase في الوقت الفعلي مع الخادم، ويحصل على آخر حدثَين من نوع "تمت إضافة عنصر فرعي" وحدث "القيمة" للتطبيق.
معالجة المعاملات بلا إنترنت
تتم إضافة أي معاملات يتم إجراؤها عندما يكون التطبيق غير متصل بالإنترنت إلى قائمة الانتظار. وبعد استعادة الاتصال بالشبكة، يتم إرسال المعاملات إلى خادم قاعدة بيانات الوقت الفعلي.
تتضمّن "قاعدة بيانات Firebase في الوقت الفعلي" العديد من الميزات للتعامل مع سيناريوهات عدم الاتصال بالإنترنت والاتصال بالشبكة. تنطبق بقية هذا الدليل على تطبيقك سواء فعّلت ميزة استمرار البيانات أم لا.
إدارة حالة التوفّر
في التطبيقات التي تعمل في الوقت الفعلي، من المفيد غالبًا رصد حالات اتصال العملاء وانقطاع الاتصال. على سبيل المثال، قد تحتاج إلى وضع علامة "غير متصل" على مستخدم عندما ينقطع اتصال العميل.
توفّر برامج Firebase Database الأساسية عناصر بسيطة يمكنك استخدامها للكتابة إلى قاعدة البيانات عند انقطاع اتصال أحد البرامج بخوادم Firebase Database. وتحدث هذه التحديثات سواء تم فصل العميل بشكل سليم أم لا، لذا يمكنك الاعتماد عليها لتنظيف البيانات حتى إذا تم قطع الاتصال أو تعطّل العميل. يمكن تنفيذ جميع عمليات الكتابة، بما في ذلك الضبط والتعديل والإزالة، عند قطع الاتصال.
في ما يلي مثال بسيط على كتابة البيانات عند قطع الاتصال باستخدام العنصر الأساسي onDisconnect:
final presenceRef = FirebaseDatabase.instance.ref("disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnect().set("I disconnected!");
طريقة عمل onDisconnect
عند إنشاء عملية onDisconnect()، يتم تخزينها على خادم "قاعدة بيانات Firebase في الوقت الفعلي". يتحقّق الخادم من الأمان للتأكّد من أنّ المستخدم يمكنه تنفيذ حدث الكتابة المطلوب، ويُعلم تطبيقك إذا كان غير صالح. يراقب الخادم بعد ذلك عملية الاتصال. إذا انتهت مهلة الاتصال في أي وقت أو إذا أغلق برنامج قاعدة بيانات الوقت الفعلي الاتصال بشكل نشط، يتحقّق الخادم من الأمان مرة ثانية (للتأكّد من أنّ العملية لا تزال صالحة) ثم يستدعي الحدث.
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 هي قيمة منطقية لا تتم مزامنتها بين عملاء قاعدة بيانات الوقت الفعلي لأنّ القيمة تعتمد على حالة العميل. بعبارة أخرى، إذا قرأ أحد العملاء القيمة /.info/connected على أنّها false، لا يضمن ذلك أنّ عميلاً آخر سيقرأ القيمة نفسها.
وقت الاستجابة
الطوابع الزمنية للخادم
توفّر خوادم "قاعدة بيانات Firebase في الوقت الفعلي" آلية لإدراج الطوابع الزمنية التي يتم إنشاؤها على الخادم كبيانات. توفّر هذه الميزة، إلى جانب onDisconnect، طريقة سهلة لتسجيل الوقت الذي تم فيه قطع اتصال أحد عملاء قاعدة بيانات الوقت الفعلي بشكل موثوق:
final userLastOnlineRef =
FirebaseDatabase.instance.ref("users/joe/lastOnline");
userLastOnlineRef.onDisconnect().set(ServerValue.timestamp);
Clock Skew
مع أنّ ServerValue.timestamp أكثر دقة بكثير، وهو الخيار المفضّل لمعظم عمليات القراءة والكتابة، قد يكون من المفيد أحيانًا تقدير انحراف ساعة العميل بالنسبة إلى خوادم Firebase Realtime Database. يمكنك إرفاق دالة ردّ نداء بالموقع /.info/serverTimeOffset للحصول على القيمة بالمللي ثانية التي يضيفها عملاء قاعدة بيانات Firebase في الوقت الفعلي إلى الوقت المحلي المسجّل (وقت الحقبة بالمللي ثانية) لتقدير وقت الخادم. يُرجى العِلم أنّ دقة هذا الإزاحة يمكن أن تتأثر بوقت استجابة الشبكة، وبالتالي فهي مفيدة بشكل أساسي في رصد التناقضات الكبيرة (أكثر من ثانية واحدة) في الوقت.
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;
});
تطبيق Sample Presence
من خلال الجمع بين عمليات قطع الاتصال ومراقبة حالة الاتصال والطوابع الزمنية للخادم، يمكنك إنشاء نظام لتحديد حالة المستخدم. في هذا النظام، يخزّن كل مستخدم البيانات في موقع قاعدة بيانات للإشارة إلى ما إذا كان عميل قاعدة بيانات الوقت الفعلي متصلاً بالإنترنت أم لا. تضبط الأجهزة العميلة هذا الموقع الجغرافي على "صحيح" عند الاتصال بالإنترنت، كما تضبط طابعًا زمنيًا عند قطع الاتصال. يشير هذا الطابع الزمني إلى آخر مرة كان فيها المستخدم المحدّد متصلاً بالإنترنت.
يُرجى العِلم أنّه يجب أن يضع تطبيقك عمليات قطع الاتصال في قائمة الانتظار قبل وضع علامة على أنّ المستخدم متصل بالإنترنت، وذلك لتجنُّب أي حالات تعارض في حال فقدان اتصال شبكة العميل قبل إرسال كلا الأمرين إلى الخادم.
// 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);
}
});