فعال کردن قابلیت های آفلاین

برنامه‌های Firebase حتی اگر برنامه شما به طور موقت اتصال شبکه خود را از دست بدهد، کار می‌کنند. علاوه بر این، Firebase ابزارهایی برای ذخیره داده‌ها به صورت محلی، مدیریت حضور و مدیریت تأخیر ارائه می‌دهد.

پایداری دیسک

برنامه‌های Firebase به طور خودکار وقفه‌های موقت شبکه را مدیریت می‌کنند. داده‌های ذخیره شده در حالت آفلاین در دسترس هستند و Firebase پس از برقراری مجدد اتصال شبکه، هرگونه نوشتن را دوباره ارسال می‌کند.

وقتی قابلیت ماندگاری دیسک را فعال می‌کنید، برنامه شما داده‌ها را به صورت محلی روی دستگاه می‌نویسد تا برنامه بتواند وضعیت را در حالت آفلاین حفظ کند، حتی اگر کاربر یا سیستم عامل برنامه را مجدداً راه‌اندازی کند.

شما می‌توانید تنها با یک خط کد، قابلیت ماندگاری دیسک را فعال کنید.

FirebaseDatabase.instance.setPersistenceEnabled(true);

رفتار مداوم

با فعال کردن پایداری، هر داده‌ای که کلاینت Firebase Realtime Database در حالت آنلاین همگام‌سازی می‌کند، روی دیسک باقی می‌ماند و حتی زمانی که کاربر یا سیستم عامل برنامه را مجدداً راه‌اندازی کند، به صورت آفلاین در دسترس است. این بدان معناست که برنامه شما با استفاده از داده‌های محلی ذخیره شده در حافظه پنهان، مانند حالت آنلاین کار می‌کند. فراخوانی‌های شنونده برای به‌روزرسانی‌های محلی همچنان فعال خواهند بود.

کلاینت پایگاه داده Realtime Firebase به طور خودکار صفی از تمام عملیات نوشتن که در حالت آفلاین بودن برنامه شما انجام می‌شوند را نگه می‌دارد. وقتی قابلیت پایداری فعال باشد، این صف نیز در دیسک ذخیره می‌شود، بنابراین تمام نوشته‌های شما هنگام راه‌اندازی مجدد برنامه توسط کاربر یا سیستم عامل در دسترس هستند. وقتی برنامه دوباره متصل می‌شود، تمام عملیات به سرور پایگاه داده Realtime Firebase ارسال می‌شوند.

اگر برنامه شما از احراز هویت فایربیس استفاده می‌کند، کلاینت پایگاه داده بلادرنگ فایربیس، توکن احراز هویت کاربر را در طول راه‌اندازی مجدد برنامه حفظ می‌کند. اگر توکن احراز هویت در حالی که برنامه شما آفلاین است منقضی شود، کلاینت عملیات نوشتن را تا زمانی که برنامه شما کاربر را دوباره احراز هویت کند، متوقف می‌کند، در غیر این صورت عملیات نوشتن ممکن است به دلیل قوانین امنیتی با شکست مواجه شود.

تازه نگه داشتن داده‌ها

پایگاه داده بلادرنگ Firebase یک کپی محلی از داده‌ها را برای شنوندگان فعال همگام‌سازی و ذخیره می‌کند. علاوه بر این، می‌توانید مکان‌های خاصی را در همگام‌سازی نگه دارید.

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

کلاینت پایگاه داده‌ی بلادرنگ فایربیس به طور خودکار داده‌ها را در این مکان‌ها دانلود می‌کند و حتی اگر مرجع هیچ شنونده‌ی فعالی نداشته باشد، آن‌ها را همگام‌سازی نگه می‌دارد. می‌توانید همگام‌سازی را با خط کد زیر دوباره غیرفعال کنید.

scoresRef.keepSynced(false);

به طور پیش‌فرض، 10 مگابایت از داده‌های همگام‌سازی‌شده قبلی در حافظه پنهان (cache) ذخیره می‌شود. این مقدار برای اکثر برنامه‌ها کافی است. اگر حافظه پنهان از اندازه پیکربندی‌شده خود بزرگتر شود، پایگاه داده Firebase Realtime Database داده‌هایی را که اخیراً کمتر استفاده شده‌اند، پاک می‌کند. داده‌هایی که همگام‌سازی شده‌اند، از حافظه پنهان پاک نمی‌شوند.

جستجوی آفلاین داده‌ها

پایگاه داده‌ی بلادرنگ 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 Realtime Database با استفاده از حافظه پنهان دائمی، رویدادهای «child added» را برای دو دایناسور با بالاترین امتیاز ایجاد می‌کند. اما رویداد «value» را ایجاد نمی‌کند، زیرا برنامه هرگز آن پرس‌وجو را در حالت آنلاین اجرا نکرده است.

اگر برنامه در حالت آفلاین شش مورد آخر را درخواست کند، رویدادهای «child added» را برای چهار مورد ذخیره شده فوراً دریافت می‌کند. وقتی دستگاه دوباره آنلاین می‌شود، کلاینت Firebase Realtime Database با سرور همگام‌سازی می‌شود و دو رویداد آخر «child added» و «value» را برای برنامه دریافت می‌کند.

مدیریت تراکنش‌ها به صورت آفلاین

هر تراکنشی که در حالت آفلاین بودن برنامه انجام شود، در صف انتظار قرار می‌گیرد. به محض اینکه برنامه دوباره به شبکه متصل شود، تراکنش‌ها به سرور پایگاه داده Realtime ارسال می‌شوند.

پایگاه داده بلادرنگ Firebase ویژگی‌های زیادی برای مدیریت سناریوهای آفلاین و اتصال به شبکه دارد. بقیه این راهنما صرف نظر از اینکه پایداری را فعال کرده باشید یا نه، برای برنامه شما اعمال می‌شود.

مدیریت حضور

در برنامه‌های بلادرنگ، اغلب تشخیص زمان اتصال و قطع اتصال کلاینت‌ها مفید است. برای مثال، ممکن است بخواهید وقتی کلاینت یک کاربر قطع می‌شود، او را به عنوان «آفلاین» علامت‌گذاری کنید.

کلاینت‌های پایگاه داده فایربیس، مقادیر اولیه ساده‌ای را ارائه می‌دهند که می‌توانید هنگام قطع ارتباط یک کلاینت از سرورهای پایگاه داده فایربیس، برای نوشتن در پایگاه داده از آنها استفاده کنید. این به‌روزرسانی‌ها چه کلاینت به طور کامل قطع شود و چه نشود، رخ می‌دهند، بنابراین می‌توانید برای پاکسازی داده‌ها حتی در صورت قطع اتصال یا خرابی کلاینت، به آنها اعتماد کنید. تمام عملیات نوشتن، از جمله تنظیم، به‌روزرسانی و حذف، می‌توانند پس از قطع ارتباط انجام شوند.

در اینجا یک مثال ساده از نوشتن داده‌ها پس از قطع اتصال با استفاده از تابع اولیه 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 اجرا می‌شود. سرور امنیت را بررسی می‌کند تا مطمئن شود که کاربر می‌تواند رویداد نوشتن درخواستی را انجام دهد و در صورت نامعتبر بودن، به برنامه شما اطلاع می‌دهد. سپس سرور اتصال را نظارت می‌کند. اگر در هر نقطه‌ای اتصال به پایان برسد یا توسط کلاینت 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 Realtime یک مکان ویژه در /.info/connected فراهم می‌کند که هر بار وضعیت اتصال کلاینت پایگاه داده Firebase Realtime به‌روزرسانی می‌شود. در اینجا مثالی آورده شده است:

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 همگام‌سازی نمی‌شود زیرا مقدار آن به وضعیت کلاینت وابسته است. به عبارت دیگر، اگر یک کلاینت /.info/connected را false بخواند، هیچ تضمینی وجود ندارد که کلاینت جداگانه نیز false را بخواند.

مدیریت تأخیر

مهرهای زمانی سرور

سرورهای پایگاه داده Realtime Firebase مکانیزمی را برای درج مهرهای زمانی تولید شده در سرور به عنوان داده ارائه می‌دهند. این ویژگی، همراه با onDisconnect ، راهی آسان برای ثبت مطمئن زمان قطع اتصال یک کلاینت پایگاه داده Realtime فراهم می‌کند:

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

انحراف ساعت

اگرچه ServerValue.timestamp بسیار دقیق‌تر است و برای اکثر عملیات خواندن/نوشتن ترجیح داده می‌شود، اما گاهی اوقات می‌تواند برای تخمین انحراف ساعت کلاینت نسبت به سرورهای Firebase Realtime Database مفید باشد. می‌توانید یک callback به مکان /.info/serverTimeOffset پیوست کنید تا مقداری را که کلاینت‌های Firebase Realtime Database به زمان گزارش شده محلی (زمان 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;
});

نمونه اپلیکیشن حضور و غیاب

با ترکیب عملیات قطع ارتباط با نظارت بر وضعیت اتصال و مهرهای زمانی سرور، می‌توانید یک سیستم حضور کاربر ایجاد کنید. در این سیستم، هر کاربر داده‌ها را در یک مکان پایگاه داده ذخیره می‌کند تا نشان دهد که آیا یک کلاینت پایگاه داده Realtime آنلاین است یا خیر. کلاینت‌ها هنگام آنلاین شدن، این مکان را روی true تنظیم می‌کنند و هنگام قطع ارتباط، یک مهر زمانی. این مهر زمانی آخرین باری را که کاربر مشخص آنلاین بوده است، نشان می‌دهد.

توجه داشته باشید که برنامه شما باید عملیات قطع ارتباط را قبل از اینکه کاربر به صورت آنلاین علامت‌گذاری شود، در صف قرار دهد تا از هرگونه شرایط رقابتی در صورت قطع اتصال شبکه کلاینت قبل از ارسال هر دو دستور به سرور، جلوگیری شود.

// 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);
  }
});