تفعيل إمكانيات الاتصال بلا إنترنت

تعمل تطبيقات Firebase حتى إذا انقطع اتصال تطبيقك بالشبكة مؤقتًا. بالإضافة إلى ذلك، توفّر Firebase أدوات لتخزين البيانات بشكل دائم على الجهاز، وإدارة حالة الاتصال، والتعامل مع وقت الاستجابة.

الاحتفاظ بالبيانات على القرص

تتعامل تطبيقات Firebase تلقائيًا مع انقطاعات الشبكة المؤقتة. تتوفّر البيانات المخزّنة مؤقتًا أثناء عدم الاتصال بالإنترنت، ويعيد Firebase إرسال أي عمليات كتابة عند استعادة الاتصال بالشبكة.

عند تفعيل ميزة استمرار البيانات على القرص، يكتب تطبيقك البيانات محليًا على الجهاز، ما يتيح له الاحتفاظ بالحالة بلا إنترنت، حتى إذا أعاد المستخدم أو نظام التشغيل تشغيل التطبيق.

يمكنك تفعيل إمكانية الاحتفاظ بالبيانات على القرص باستخدام سطر واحد فقط من الرمز البرمجي.

FirebaseDatabase.instance.setPersistenceEnabled(true);

سلوك الاستمرار

من خلال تفعيل إمكانية الاحتفاظ بالبيانات، يتم الاحتفاظ على القرص بأي بيانات يزامنها برنامج قاعدة بيانات Firebase في الوقت الفعلي أثناء الاتصال بالإنترنت، وتصبح هذه البيانات متاحة بلا إنترنت، حتى إذا أعاد المستخدم أو نظام التشغيل تشغيل التطبيق. وهذا يعني أنّ تطبيقك يعمل كما لو كان متصلاً بالإنترنت من خلال استخدام البيانات المحلية المخزَّنة في ذاكرة التخزين المؤقت. سيستمر تشغيل عمليات رد الاتصال الخاصة بالمستمعين عند إجراء تعديلات محلية.

يحتفظ برنامج قاعدة بيانات Firebase في الوقت الفعلي تلقائيًا بقائمة انتظار تضم جميع عمليات الكتابة التي يتم تنفيذها أثناء عدم اتصال تطبيقك بالإنترنت. عند تفعيل وضع الثبات، يتم أيضًا حفظ قائمة الانتظار هذه على القرص، وبالتالي تكون جميع عمليات الكتابة متاحة عندما يعيد المستخدم أو نظام التشغيل تشغيل التطبيق. وعندما يستعيد التطبيق إمكانية الاتصال، يتم إرسال جميع العمليات إلى خادم قاعدة بيانات Firebase في الوقت الفعلي.

إذا كان تطبيقك يستخدم مصادقة 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، لا يضمن ذلك أنّ عميلاً آخر سيقرأها على أنّها 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);
  }
});