অফলাইন ক্ষমতা সক্রিয় করা হচ্ছে

আপনার অ্যাপের নেটওয়ার্ক সংযোগ সাময়িকভাবে বিচ্ছিন্ন হয়ে গেলেও ফায়ারবেস অ্যাপ্লিকেশনগুলো কাজ করে। এছাড়াও, ফায়ারবেস স্থানীয়ভাবে ডেটা সংরক্ষণ, উপস্থিতি ব্যবস্থাপনা এবং লেটেন্সি সামলানোর জন্য বিভিন্ন টুল সরবরাহ করে।

ডিস্ক স্থায়িত্ব

ফায়ারবেস অ্যাপগুলো স্বয়ংক্রিয়ভাবে সাময়িক নেটওয়ার্ক বিঘ্ন সামাল দেয়। অফলাইনে থাকাকালীন ক্যাশ করা ডেটা পাওয়া যায় এবং নেটওয়ার্ক সংযোগ পুনরুদ্ধার হলে ফায়ারবেস যেকোনো রাইট পুনরায় পাঠায়।

যখন আপনি ডিস্ক পার্সিস্টেন্স চালু করেন, তখন আপনার অ্যাপ ডেটা স্থানীয়ভাবে ডিভাইসে লিখে রাখে, ফলে ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলেও, অফলাইনে থাকা অবস্থায়ও এটি তার অবস্থা বজায় রাখতে পারে।

আপনি মাত্র এক লাইনের কোড দিয়েই ডিস্ক পারসিস্টেন্স সক্রিয় করতে পারেন।

FirebaseDatabase.instance.setPersistenceEnabled(true);

অধ্যবসায় আচরণ

পার্সিস্টেন্স (persistence) চালু করার মাধ্যমে, ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট অনলাইনে থাকাকালীন যে ডেটা সিঙ্ক করে, তা ডিস্কে সংরক্ষিত থাকে এবং অফলাইনেও উপলব্ধ থাকে, এমনকি ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলেও। এর মানে হলো, আপনার অ্যাপটি ক্যাশে সংরক্ষিত স্থানীয় ডেটা ব্যবহার করে অনলাইনের মতোই কাজ করে। স্থানীয় আপডেটের জন্য লিসেনার কলব্যাকগুলো চালু থাকবে।

আপনার অ্যাপ অফলাইনে থাকাকালীন সম্পাদিত সমস্ত রাইট অপারেশনের একটি কিউ ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট স্বয়ংক্রিয়ভাবে রাখে। যখন পার্সিস্টেন্স সক্রিয় থাকে, তখন এই কিউটি ডিস্কেও সংরক্ষিত হয়, ফলে ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলে আপনার সমস্ত রাইট উপলব্ধ থাকে। অ্যাপটি পুনরায় সংযোগ ফিরে পেলে, সমস্ত অপারেশন ফায়ারবেস রিয়েলটাইম ডেটাবেস সার্ভারে পাঠানো হয়।

আপনার অ্যাপ যদি ফায়ারবেস অথেনটিকেশন ব্যবহার করে, তাহলে ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট অ্যাপ পুনরায় চালু হওয়ার পরেও ব্যবহারকারীর অথেনটিকেশন টোকেনটি সংরক্ষণ করে রাখে। আপনার অ্যাপ অফলাইনে থাকাকালীন যদি অথেনটিকেশন টোকেনের মেয়াদ শেষ হয়ে যায়, তাহলে ক্লায়েন্ট রাইট অপারেশনগুলো থামিয়ে দেয় যতক্ষণ না আপনার অ্যাপ ব্যবহারকারীকে পুনরায় অথেনটিকেট করে। অন্যথায়, নিরাপত্তা নিয়মের কারণে রাইট অপারেশনগুলো ব্যর্থ হতে পারে।

ডেটা সতেজ রাখা

ফায়ারবেস রিয়েলটাইম ডেটাবেস সক্রিয় লিসেনারদের জন্য ডেটার একটি স্থানীয় কপি সিঙ্ক্রোনাইজ ও সংরক্ষণ করে। এছাড়াও, আপনি নির্দিষ্ট অবস্থানগুলোকে সিঙ্কে রাখতে পারেন।

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

ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট স্বয়ংক্রিয়ভাবে এই অবস্থানগুলি থেকে ডেটা ডাউনলোড করে এবং সেটিকে সিঙ্ক করে রাখে, এমনকি যদি রেফারেন্সটিতে কোনো সক্রিয় লিসেনার না-ও থাকে। আপনি নিম্নলিখিত কোড লাইনটি ব্যবহার করে সিঙ্ক্রোনাইজেশন আবার বন্ধ করতে পারেন।

scoresRef.keepSynced(false);

ডিফল্টরূপে, পূর্বে সিঙ্ক করা ১০ মেগাবাইট ডেটা ক্যাশে জমা থাকে। বেশিরভাগ অ্যাপ্লিকেশনের জন্য এটি যথেষ্ট হওয়া উচিত। যদি ক্যাশে তার নির্ধারিত আকারের চেয়ে বড় হয়ে যায়, তাহলে ফায়ারবেস রিয়েলটাইম ডেটাবেস সবচেয়ে কম ব্যবহৃত ডেটা মুছে ফেলে। যে ডেটা সিঙ্কে রাখা হয়, তা ক্যাশে থেকে মুছে ফেলা হয় না।

অফলাইনে ডেটা অনুসন্ধান করা

ফায়ারবেস রিয়েলটাইম ডেটাবেস, অফলাইনে ব্যবহারের জন্য কোয়েরি থেকে প্রাপ্ত ডেটা সংরক্ষণ করে। অফলাইনে থাকাকালীন তৈরি করা কোয়েরিগুলোর জন্য, ফায়ারবেস রিয়েলটাইম ডেটাবেস পূর্বে লোড করা ডেটার ওপর কাজ চালিয়ে যায়। যদি অনুরোধ করা ডেটা লোড না হয়ে থাকে, তবে ফায়ারবেস রিয়েলটাইম ডেটাবেস স্থানীয় ক্যাশে থেকে ডেটা লোড করে। যখন নেটওয়ার্ক সংযোগ পুনরায় উপলব্ধ হয়, তখন ডেটা লোড হয়ে যায় এবং কোয়েরিতে তা প্রতিফলিত হয়।

উদাহরণস্বরূপ, এই কোডটি স্কোরের একটি ডেটাবেস থেকে শেষ চারটি আইটেম খুঁজে বের করে:

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}.");
});

পূর্ববর্তী উদাহরণে, ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট পার্সিস্টেড ক্যাশে ব্যবহার করে সর্বোচ্চ স্কোর করা দুটি ডাইনোসরের জন্য 'child added' ইভেন্ট তৈরি করে। কিন্তু এটি 'value' ইভেন্ট তৈরি করবে না, কারণ অ্যাপটি অনলাইনে থাকাকালীন সেই কোয়েরিটি কখনও এক্সিকিউট করেনি।

অ্যাপটি অফলাইনে থাকাকালীন যদি শেষ ছয়টি আইটেমের জন্য অনুরোধ করে, তবে এটি সাথে সাথেই ক্যাশ করা চারটি আইটেমের জন্য 'চাইল্ড অ্যাডেড' ইভেন্ট পেয়ে যাবে। ডিভাইসটি আবার অনলাইনে এলে, ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্ট সার্ভারের সাথে সিঙ্ক্রোনাইজ করে এবং অ্যাপটির জন্য শেষ দুটি 'চাইল্ড অ্যাডেড' ও 'ভ্যালু' ইভেন্টগুলো পেয়ে যায়।

অফলাইনে লেনদেন পরিচালনা

অ্যাপটি অফলাইনে থাকাকালীন সম্পাদিত যেকোনো লেনদেন সারিবদ্ধ করা হয়। অ্যাপটি নেটওয়ার্ক সংযোগ ফিরে পেলে, লেনদেনগুলো রিয়েলটাইম ডেটাবেস সার্ভারে পাঠানো হয়।

ফায়ারবেস রিয়েলটাইম ডেটাবেসে অফলাইন পরিস্থিতি এবং নেটওয়ার্ক সংযোগ সামলানোর জন্য অনেক বৈশিষ্ট্য রয়েছে। আপনার অ্যাপে পার্সিস্টেন্স সক্রিয় থাকুক বা না থাকুক, এই নির্দেশিকার বাকি অংশ প্রযোজ্য হবে।

উপস্থিতি পরিচালনা

রিয়েলটাইম অ্যাপ্লিকেশনগুলিতে ক্লায়েন্টরা কখন সংযোগ স্থাপন করে এবং সংযোগ বিচ্ছিন্ন করে তা শনাক্ত করা প্রায়শই দরকারি হয়। উদাহরণস্বরূপ, কোনো ব্যবহারকারীর ক্লায়েন্ট সংযোগ বিচ্ছিন্ন হলে আপনি তাকে 'অফলাইন' হিসেবে চিহ্নিত করতে চাইতে পারেন।

ফায়ারবেস ডেটাবেস ক্লায়েন্টগুলো কিছু সহজ প্রিমিটিভ প্রদান করে, যা ব্যবহার করে কোনো ক্লায়েন্ট ফায়ারবেস ডেটাবেস সার্ভার থেকে সংযোগ বিচ্ছিন্ন হলে ডেটাবেসে লেখা যায়। ক্লায়েন্টটি সঠিকভাবে সংযোগ বিচ্ছিন্ন করুক বা না করুক, এই আপডেটগুলো সম্পন্ন হয়। তাই, সংযোগ বিচ্ছিন্ন হয়ে গেলেও বা ক্লায়েন্ট ক্র্যাশ করলেও ডেটা পরিষ্কার করার জন্য আপনি এগুলোর ওপর নির্ভর করতে পারেন। সংযোগ বিচ্ছিন্ন হওয়ার পরেও ডেটা সেট করা, আপডেট করা এবং মুছে ফেলাসহ সমস্ত লেখার কাজ সম্পাদন করা যায়।

onDisconnect প্রিমিটিভ ব্যবহার করে সংযোগ বিচ্ছিন্ন হওয়ার পর ডেটা লেখার একটি সহজ উদাহরণ নিচে দেওয়া হলো:

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

অনডিসকানেক্ট কীভাবে কাজ করে

যখন আপনি একটি onDisconnect() অপারেশন স্থাপন করেন, তখন অপারেশনটি ফায়ারবেস রিয়েলটাইম ডেটাবেস সার্ভারে থাকে। সার্ভার নিরাপত্তা পরীক্ষা করে নিশ্চিত করে যে ব্যবহারকারী অনুরোধ করা রাইট ইভেন্টটি সম্পাদন করতে পারবে কিনা, এবং যদি এটি অবৈধ হয় তবে আপনার অ্যাপকে জানিয়ে দেয়। এরপর সার্ভার সংযোগটি পর্যবেক্ষণ করে। যদি কোনো সময়ে সংযোগের সময়সীমা শেষ হয়ে যায়, অথবা রিয়েলটাইম ডেটাবেস ক্লায়েন্ট দ্বারা সক্রিয়ভাবে বন্ধ করা হয়, তবে সার্ভার দ্বিতীয়বার নিরাপত্তা পরীক্ষা করে (অপারেশনটি এখনও বৈধ কিনা তা নিশ্চিত করার জন্য) এবং তারপর ইভেন্টটি আহ্বান করে।

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

.cancel() কল করেও একটি onDisconnect ইভেন্ট বাতিল করা যায়:

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

সংযোগের অবস্থা সনাক্তকরণ

উপস্থিতি-সম্পর্কিত অনেক বৈশিষ্ট্যের জন্য, আপনার অ্যাপের অনলাইন বা অফলাইন অবস্থা জানাটা দরকারি। Firebase Realtime Database /.info/connected এ একটি বিশেষ স্থান প্রদান করে, যা প্রতিবার Firebase Realtime Database ক্লায়েন্টের সংযোগের অবস্থা পরিবর্তিত হলে আপডেট করা হয়। এখানে একটি উদাহরণ দেওয়া হলো:

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-ই পড়বে।

লেটেন্সি পরিচালনা

সার্ভার টাইমস্ট্যাম্প

ফায়ারবেস রিয়েলটাইম ডেটাবেস সার্ভারগুলো সার্ভারে তৈরি হওয়া টাইমস্ট্যাম্পকে ডেটা হিসেবে সন্নিবেশ করার একটি ব্যবস্থা প্রদান করে। এই বৈশিষ্ট্যটি, onDisconnect সাথে মিলিত হয়ে, একটি রিয়েলটাইম ডেটাবেস ক্লায়েন্ট কখন সংযোগ বিচ্ছিন্ন করেছে সেই সময়টি নির্ভরযোগ্যভাবে লিখে রাখার একটি সহজ উপায় প্রদান করে।

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

ঘড়ির তির্যক

যদিও ServerValue.timestamp অনেক বেশি নির্ভুল এবং বেশিরভাগ রিড/রাইট অপারেশনের জন্য পছন্দনীয়, তবুও ফায়ারবেস রিয়েলটাইম ডেটাবেসের সার্ভারগুলোর সাপেক্ষে ক্লায়েন্টের ঘড়ির সময়ের অসামঞ্জস্যতা অনুমান করার জন্য এটি মাঝে মাঝে কাজে আসতে পারে। সার্ভারের সময় অনুমান করার জন্য ফায়ারবেস রিয়েলটাইম ডেটাবেস ক্লায়েন্টরা স্থানীয় রিপোর্ট করা সময়ের (মিলিসেকেন্ডে ইপক টাইম) সাথে যে মানটি মিলিসেকেন্ডে যোগ করে, তা পাওয়ার জন্য আপনি /.info/serverTimeOffset লোকেশনে একটি কলব্যাক সংযুক্ত করতে পারেন। মনে রাখবেন যে এই অফসেটের নির্ভুলতা নেটওয়ার্কিং ল্যাটেন্সির দ্বারা প্রভাবিত হতে পারে, এবং তাই এটি মূলত ঘড়ির সময়ের বড় (> ১ সেকেন্ড) অমিল খুঁজে বের করার জন্য উপযোগী।

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

নমুনা উপস্থিতি অ্যাপ

সংযোগ বিচ্ছিন্ন করার কার্যক্রম, সংযোগের অবস্থা পর্যবেক্ষণ এবং সার্ভার টাইমস্ট্যাম্পের সমন্বয়ে আপনি একটি ব্যবহারকারীর উপস্থিতি সিস্টেম তৈরি করতে পারেন। এই সিস্টেমে, প্রতিটি ব্যবহারকারী একটি রিয়েলটাইম ডেটাবেস ক্লায়েন্ট অনলাইন আছে কি না তা নির্দেশ করার জন্য একটি ডেটাবেস অবস্থানে ডেটা সংরক্ষণ করে। ক্লায়েন্টরা অনলাইন হলে এই অবস্থানটিকে 'ট্রু' (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);
  }
});