Apple প্ল্যাটফর্মে অফলাইন কেপেবিলিটি

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

ডিস্ক পারসিসটেন্স

Firebase অ্যাপ সাময়িক নেটওয়ার্ক বিঘ্নিত হওয়ার সমস্যা অটোমেটিক ম্যানেজ করে। অফলাইন থাকাকালীন ক্যাশে করা ডেটা উপলভ্য থাকে এবং নেটওয়ার্ক কানেক্টিভিটি রিস্টোর করা হলে Firebase যেকোনও রাইট আবার পাঠায়।

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

আপনি মাত্র এক লাইন কোড লিখে ডিস্ক পারসিস্টেন্স চালু করতে পারবেন।

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
Database.database().isPersistenceEnabled = true

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
[FIRDatabase database].persistenceEnabled = YES;

পারসিস্টেন্স বিহেভিওর

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

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

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

ডেটা আপডেট রাখা

Firebase Realtime Database অ্যাক্টিভ শ্রোতাদের জন্য ডেটার একটি লোকাল কপি সিঙ্ক ও স্টোর করে। এছাড়াও, আপনি নির্দিষ্ট লোকেশন সিঙ্ক করে রাখতে পারবেন।

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
let scoresRef = Database.database().reference(withPath: "scores")
scoresRef.keepSynced(true)

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
FIRDatabaseReference *scoresRef = [[FIRDatabase database] referenceWithPath:@"scores"];
[scoresRef keepSynced:YES];

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
scoresRef.keepSynced(false)

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
[scoresRef keepSynced:NO];

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

অফলাইনে ডেটা কোয়েরি করা

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

যেমন, এই কোডটি স্কোরের Firebase Realtime Database-এ শেষ চারটি আইটেমের জন্য কোয়েরি করে

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
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")")
}

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
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);
    }];

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
scoresRef.queryOrderedByValue().queryLimited(toLast: 2).observe(.childAdded) { snapshot in
  print("The \(snapshot.key) dinosaur's score is \(snapshot.value ?? "null")")
}

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
[[[scoresRef queryOrderedByValue] queryLimitedToLast:2]
    observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
      NSLog(@"The %@ dinosaur's score is %@", snapshot.key, snapshot.value);
    }];

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

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

অফলাইনে ট্রানজ্যাকশন ম্যানেজ করা

অ্যাপ অফলাইন থাকাকালীন কোনও ট্রানজ্যাকশন করা হলে, তা সারিতে রাখা হয়। অ্যাপটি নেটওয়ার্ক কানেক্টিভিটি ফিরে পেলে, ট্রানজ্যাকশনগুলি Realtime Database সার্ভারে পাঠানো হয়।

উপস্থিতি ম্যানেজ করা

রিয়েল-টাইম অ্যাপ্লিকেশনে ক্লায়েন্ট কখন কানেক্ট ও ডিসকানেক্ট করছে তা শনাক্ত করা প্রায়ই কাজে লাগে। যেমন, আপনি হয়ত কোনও ব্যবহারকারীর ক্লায়েন্ট ডিসকানেক্ট হয়ে গেলে তাকে 'অফলাইন' হিসেবে চিহ্নিত করতে চান।

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

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
let presenceRef = Database.database().reference(withPath: "disconnectmessage");
// Write a string when this client loses connection
presenceRef.onDisconnectSetValue("I disconnected!")

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
FIRDatabaseReference *presenceRef = [[FIRDatabase database] referenceWithPath:@"disconnectmessage"];
// Write a string when this client loses connection
[presenceRef onDisconnectSetValue:@"I disconnected!"];

onDisconnect কীভাবে কাজ করে

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

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
presenceRef.onDisconnectRemoveValue { error, reference in
  if let error = error {
    print("Could not establish onDisconnect event: \(error)")
  }
}

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
[presenceRef onDisconnectRemoveValueWithCompletionBlock:^(NSError *error, FIRDatabaseReference *reference) {
  if (error != nil) {
    NSLog(@"Could not establish onDisconnect event: %@", error);
  }
}];

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
presenceRef.onDisconnectSetValue("I disconnected")
// some time later when we change our minds
presenceRef.cancelDisconnectOperations()

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
[presenceRef onDisconnectSetValue:@"I disconnected"];
// some time later when we change our minds
[presenceRef cancelDisconnectOperations];

কানেকশন স্ট্যাটাস শনাক্ত করা হচ্ছে

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
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")
  }
})

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
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-কে মিথ্যা হিসেবে পড়লে, অন্য ক্লায়েন্টও যে মিথ্যা হিসেবে পড়বে তার কোনও গ্যারেন্টি নেই।

লেটেন্সি ম্যানেজ করা

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

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
let userLastOnlineRef = Database.database().reference(withPath: "users/morgan/lastOnline")
userLastOnlineRef.onDisconnectSetValue(ServerValue.timestamp())

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
FIRDatabaseReference *userLastOnlineRef = [[FIRDatabase database] referenceWithPath:@"users/morgan/lastOnline"];
[userLastOnlineRef onDisconnectSetValue:[FIRServerValue timestamp]];

ক্লক স্কিউ

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
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)")
  }
})

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
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);
}];

Sample Presence অ্যাপ

কানেকশন স্টেট মনিটরিং ও সার্ভার টাইমস্ট্যাম্পের সাথে ডিসকানেক্ট অপারেশন একত্রিত করে, আপনি ব্যবহারকারীর উপস্থিতি সিস্টেম তৈরি করতে পারবেন। এই সিস্টেমে, প্রতিটি ব্যবহারকারী ডেটাবেসের কোনও লোকেশনে ডেটা স্টোর করেন যাতে বোঝা যায় যে Realtime Database ক্লায়েন্ট অনলাইন আছে কিনা। ক্লায়েন্টরা অনলাইন হলে এই লোকেশনকে সত্য হিসেবে সেট করে এবং ডিসকানেক্ট হলে টাইমস্ট্যাম্প সেট করে। এই টাইমস্ট্যাম্প নির্দেশ করে যে শেষবার কখন প্রদত্ত ব্যবহারকারী অনলাইন ছিলেন।

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

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

Swift

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
// 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())
})

Objective-C

মনে রাখবেন: এই Firebase প্রোডাক্ট অ্যাপ ক্লিপ টার্গেটে উপলভ্য নয়।
// 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]];
  }
}];