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 যাচাইকরণ ব্যবহার করলে, 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 Database ক্লায়েন্টরা এমন সাধারণ প্রিমিটিভ প্রদান করে যা আপনি Firebase Database সার্ভার থেকে ক্লায়েন্ট ডিসকানেক্ট হয়ে গেলে ডেটাবেসে লিখতে ব্যবহার করতে পারেন। ক্লায়েন্ট ডিসকানেক্ট হোক বা না হোক, এইসব আপডেট হয়, তাই কানেকশন ড্রপ হয়ে গেলেও বা ক্লায়েন্ট ক্র্যাশ করলেও, ডেটা মুছে ফেলার জন্য আপনি এগুলির উপর ভরসা করতে পারেন। ডিসকানেক্ট হয়ে যাওয়ার পরে, সেটিং, আপডেট করা ও সরিয়ে দেওয়া সহ সবকটি রাইট অপারেশন করা যেতে পারে।

ডিসকানেক্ট করার পরে ডেটা লেখার একটি সহজ উদাহরণ এখানে দেওয়া হল, এর জন্য 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-এর সার্ভারের সাথে তুলনা করে ক্লায়েন্টের ক্লক স্কিউ অনুমান করার জন্য এটি মাঝে মাঝে কাজে লাগতে পারে। আপনি লোকেশন থেকে ভ্যালু পেতে, মিলিসেকেন্ডে, একটি কলব্যাক অ্যাটাচ করতে পারেন। /.info/serverTimeOffset Firebase Realtime Database ক্লায়েন্ট সার্ভারের সময় অনুমান করতে, লোকাল রিপোর্টেড সময়ে (মিলিসেকেন্ডে ইপক টাইম) এই ভ্যালু যোগ করে। মনে রাখবেন, এই অফসেটের নির্ভুলতা নেটওয়ার্ক লেটেন্সি দ্বারা প্রভাবিত হতে পারে এবং তাই এটি মূলত ক্লক টাইমে বড় (> ১ সেকেন্ড) পার্থক্য খুঁজে বের করার জন্য উপযোগী।

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 App

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

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

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

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