অ্যাপল প্ল্যাটফর্মে অফলাইন ক্ষমতা

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

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

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

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

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
Database.database().isPersistenceEnabled = true

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
[FIRDatabase database].persistenceEnabled = YES;

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

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

The Firebase Realtime Database client automatically keeps a queue of all write operations that are performed while your app is offline. When persistence is enabled, this queue is also persisted to disk so all of your writes are available when the user or operating system restarts the app. When the app regains connectivity, all of the operations are sent to the Firebase Realtime Database server.

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

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

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
scoresRef.keepSynced(false)

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
[scoresRef keepSynced:NO];

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

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

The Firebase Realtime Database stores data returned from a query for use when offline. For queries constructed while offline, the Firebase Realtime Database continues to work for previously loaded data. If the requested data hasn't loaded, the Firebase Realtime Database loads data from the local cache. When network connectivity is available again, the data loads and will reflect the query.

উদাহরণস্বরূপ, এই কোডটি স্কোরের একটি Firebase Realtime Database থেকে শেষ চারটি আইটেম কোয়েরি করে।

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.queryOrderedByValue().queryLimited(toLast: 4).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[[[scoresRef queryOrderedByValue] queryLimitedToLast:4]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

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

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

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

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

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

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

Firebase Database clients provide simple primitives that you can use to write to the database when a client disconnects from the Firebase Database servers. These updates occur whether the client disconnects cleanly or not, so you can rely on them to clean up data even if a connection is dropped or a client crashes. All write operations, including setting, updating, and removing, can be performed upon a disconnection.

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

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

When you establish an onDisconnect() operation, the operation lives on the Firebase Realtime Database server. The server checks security to make sure the user can perform the write event requested, and informs your app if it is invalid. The server then monitors the connection. If at any point the connection times out, or is actively closed by the Realtime Database client, the server checks security a second time (to make sure the operation is still valid) and then invokes the event.

আপনার অ্যাপ রাইট অপারেশনে কলব্যাকটি ব্যবহার করে নিশ্চিত করতে পারে যে onDisconnect সঠিকভাবে সংযুক্ত হয়েছে:

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

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

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
let connectedRef = Database.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
  if snapshot.value as? Bool ?? false {
    print("Connected")
  } else {
    print("Not connected")
  }
})

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    NSLog(@"connected");
  } else {
    NSLog(@"not connected");
  }
}];

/.info/connected একটি বুলিয়ান মান, যা Realtime Database ক্লায়েন্টগুলোর মধ্যে সিঙ্ক্রোনাইজড হয় না, কারণ এর মান ক্লায়েন্টের অবস্থার উপর নির্ভরশীল। অন্য কথায়, যদি একটি ক্লায়েন্ট /.info/connected false হিসেবে পড়ে, তবে এর কোনো নিশ্চয়তা নেই যে অন্য একটি ক্লায়েন্টও false-ই পড়বে।

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

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

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

ঘড়ির তির্যক

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

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
let offsetRef = Database.database().reference(withPath: ".info/serverTimeOffset")
offsetRef.observe(.value, with: { snapshot in
  if let offset = snapshot.value as? TimeInterval {
    print("Estimated server time in milliseconds: \(Date().timeIntervalSince1970 * 1000 + offset)")
  }
})

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
FIRDatabaseReference *offsetRef = [[FIRDatabase database] referenceWithPath:@".info/serverTimeOffset"];
[offsetRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  NSTimeInterval offset = [(NSNumber *)snapshot.value doubleValue];
  NSTimeInterval estimatedServerTimeMs = [[NSDate date] timeIntervalSince1970] * 1000.0 + offset;
  NSLog(@"Estimated server time: %0.3f", estimatedServerTimeMs);
}];

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

By combining disconnect operations with connection state monitoring and server timestamps, you can build a user presence system. In this system, each user stores data at a database location to indicate whether or not a Realtime Database client is online. Clients set this location to true when they come online and a timestamp when they disconnect. This timestamp indicates the last time the given user was online.

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

এখানে একটি সহজ ব্যবহারকারী উপস্থিতি সিস্টেম দেওয়া হলো:

সুইফট

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
// 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
let myConnectionsRef = Database.database().reference(withPath: "users/morgan/connections")

// stores the timestamp of my last disconnect (the last time I was seen online)
let lastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")

let connectedRef = Database.database().reference(withPath: ".info/connected")

connectedRef.observe(.value, with: { snapshot in
  // only handle connection established (or I've reconnected after a loss of connection)
  guard snapshot.value as? Bool ?? false else { return }

  // add this device to my connections list
  let con = myConnectionsRef.childByAutoId()

  // when this device disconnects, remove it.
  con.onDisconnectRemoveValue()

  // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
  // where you set the user's presence to true and the client disconnects before the
  // onDisconnect() operation takes effect, leaving a ghost user.

  // this value could contain info about the device or a timestamp instead of just true
  con.setValue(true)

  // when I disconnect, update the last time I was seen online
  lastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())
})

উদ্দেশ্য-সি

দ্রষ্টব্য: এই Firebase পণ্যটি App Clip টার্গেটে উপলব্ধ নয়।
// 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
FIRDatabaseReference *myConnectionsRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/connections"];

// stores the timestamp of my last disconnect (the last time I was seen online)
FIRDatabaseReference *lastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];

FIRDatabaseReference *connectedRef = [[FIRDatabase database] referenceWithPath:@".info/connected"];
[connectedRef observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) {
  if([snapshot.value boolValue]) {
    // connection established (or I've reconnected after a loss of connection)

    // add this device to my connections list
    FIRDatabaseReference *con = [myConnectionsRef childByAutoId];

    // when this device disconnects, remove it
    [con onDisconnectRemoveValue];

    // The onDisconnect() call is before the call to set() itself. This is to avoid a race condition
    // where you set the user's presence to true and the client disconnects before the
    // onDisconnect() operation takes effect, leaving a ghost user.

    // this value could contain info about the device or a timestamp instead of just true
    [con setValue:@YES];


    // when I disconnect, update the last time I was seen online
    [lastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];
  }
}];