আপনার অ্যাপের নেটওয়ার্ক সংযোগ সাময়িকভাবে বিচ্ছিন্ন হয়ে গেলেও ফায়ারবেস অ্যাপ্লিকেশনগুলো কাজ করে। এছাড়াও, ফায়ারবেস স্থানীয়ভাবে ডেটা সংরক্ষণ, উপস্থিতি ব্যবস্থাপনা এবং লেটেন্সি সামলানোর জন্য বিভিন্ন টুল সরবরাহ করে।
ডিস্ক স্থায়িত্ব
ফায়ারবেস অ্যাপগুলো স্বয়ংক্রিয়ভাবে সাময়িক নেটওয়ার্ক বিঘ্ন সামাল দেয়। অফলাইনে থাকাকালীন ক্যাশ করা ডেটা পাওয়া যায় এবং নেটওয়ার্ক সংযোগ পুনরুদ্ধার হলে ফায়ারবেস যেকোনো রাইট পুনরায় পাঠায়।
যখন আপনি ডিস্ক পার্সিস্টেন্স চালু করেন, তখন আপনার অ্যাপ ডেটা স্থানীয়ভাবে ডিভাইসে লিখে রাখে, ফলে ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলেও, অফলাইনে থাকা অবস্থায়ও এটি তার অবস্থা বজায় রাখতে পারে।
আপনি মাত্র এক লাইনের কোড দিয়েই ডিস্ক পারসিস্টেন্স সক্রিয় করতে পারেন।
Kotlin
Firebase.database.setPersistenceEnabled(true)
Java
FirebaseDatabase.getInstance().setPersistenceEnabled(true);
অধ্যবসায় আচরণ
পার্সিস্টেন্স (persistence) চালু করার মাধ্যমে, Firebase Realtime Database ক্লায়েন্ট অনলাইনে থাকাকালীন যে ডেটা সিঙ্ক করে, তা ডিস্কে সংরক্ষিত থাকে এবং অফলাইনেও উপলব্ধ থাকে, এমনকি ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলেও। এর মানে হলো, আপনার অ্যাপটি ক্যাশে সংরক্ষিত স্থানীয় ডেটা ব্যবহার করে অনলাইনের মতোই কাজ করে। স্থানীয় আপডেটের জন্য লিসেনার কলব্যাকগুলো চালু থাকবে।
আপনার অ্যাপ অফলাইনে থাকাকালীন সম্পাদিত সমস্ত রাইট অপারেশনের একটি কিউ Firebase Realtime Database ক্লায়েন্ট স্বয়ংক্রিয়ভাবে রাখে। যখন পার্সিস্টেন্স সক্রিয় থাকে, তখন এই কিউটি ডিস্কেও সংরক্ষিত হয়, ফলে ব্যবহারকারী বা অপারেটিং সিস্টেম অ্যাপটি পুনরায় চালু করলে আপনার সমস্ত রাইট উপলব্ধ থাকে। অ্যাপটি পুনরায় সংযোগ ফিরে পেলে, সমস্ত অপারেশন Firebase Realtime Database সার্ভারে পাঠানো হয়।
আপনার অ্যাপ যদি ফায়ারবেস অথেনটিকেশন ব্যবহার করে, তাহলে Firebase Realtime Database ক্লায়েন্ট অ্যাপ পুনরায় চালু হওয়ার পরেও ব্যবহারকারীর অথেনটিকেশন টোকেনটি সংরক্ষণ করে রাখে। আপনার অ্যাপ অফলাইনে থাকাকালীন যদি অথেনটিকেশন টোকেনের মেয়াদ শেষ হয়ে যায়, তাহলে ক্লায়েন্ট রাইট অপারেশনগুলো থামিয়ে দেয় যতক্ষণ না আপনার অ্যাপ ব্যবহারকারীকে পুনরায় অথেনটিকেট করে। অন্যথায়, নিরাপত্তা নিয়মের কারণে রাইট অপারেশনগুলো ব্যর্থ হতে পারে।
ডেটা সতেজ রাখা
Firebase Realtime Database সক্রিয় লিসেনারদের জন্য ডেটার একটি স্থানীয় কপি সিঙ্ক্রোনাইজ ও সংরক্ষণ করে। এছাড়াও, আপনি নির্দিষ্ট অবস্থানগুলোকে সিঙ্কে রাখতে পারেন।
Kotlin
val scoresRef = Firebase.database.getReference("scores") scoresRef.keepSynced(true)
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.keepSynced(true);
Firebase Realtime Database ক্লায়েন্ট স্বয়ংক্রিয়ভাবে এই অবস্থানগুলি থেকে ডেটা ডাউনলোড করে এবং সেটিকে সিঙ্ক করে রাখে, এমনকি যদি রেফারেন্সটিতে কোনো সক্রিয় লিসেনার না-ও থাকে। আপনি নিম্নলিখিত কোড লাইনটি ব্যবহার করে সিঙ্ক্রোনাইজেশন আবার বন্ধ করতে পারেন।
Kotlin
scoresRef.keepSynced(false)
Java
scoresRef.keepSynced(false);
ডিফল্টরূপে, পূর্বে সিঙ্ক করা ১০ মেগাবাইট ডেটা ক্যাশে জমা থাকে। বেশিরভাগ অ্যাপ্লিকেশনের জন্য এটি যথেষ্ট হওয়া উচিত। যদি ক্যাশে তার নির্ধারিত আকারের চেয়ে বড় হয়ে যায়, তাহলে Firebase Realtime Database সবচেয়ে কম ব্যবহৃত ডেটা মুছে ফেলে। যে ডেটা সিঙ্কে রাখা হয়, তা ক্যাশে থেকে মুছে ফেলা হয় না।
অফলাইনে ডেটা অনুসন্ধান করা
Firebase Realtime Database অফলাইনে ব্যবহারের জন্য কোয়েরি থেকে প্রাপ্ত ডেটা সংরক্ষণ করে। অফলাইনে থাকাকালীন তৈরি করা কোয়েরিগুলোর জন্য, Firebase Realtime Database পূর্বে লোড করা ডেটার ওপর কাজ চালিয়ে যায়। যদি অনুরোধ করা ডেটা লোড না হয়ে থাকে, তবে Firebase Realtime Database স্থানীয় ক্যাশে থেকে ডেটা লোড করে। যখন নেটওয়ার্ক সংযোগ পুনরায় উপলব্ধ হয়, তখন ডেটা লোড হয়ে যায় এবং কোয়েরিতে তা প্রতিফলিত হয়।
উদাহরণস্বরূপ, এই কোডটি স্কোরের একটি Firebase Realtime Database থেকে শেষ চারটি আইটেম কোয়েরি করে।
Kotlin
val scoresRef = Firebase.database.getReference("scores") scoresRef.orderByValue().limitToLast(4).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
Java
DatabaseReference scoresRef = FirebaseDatabase.getInstance().getReference("scores"); scoresRef.orderByValue().limitToLast(4).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
ধরে নিন যে ব্যবহারকারীর সংযোগ বিচ্ছিন্ন হয়ে যায়, তিনি অফলাইনে চলে যান এবং অ্যাপটি পুনরায় চালু করেন। অফলাইনে থাকা অবস্থাতেই, অ্যাপটি একই অবস্থান থেকে শেষ দুটি আইটেমের জন্য কোয়েরি করে। এই কোয়েরিটি সফলভাবে শেষ দুটি আইটেম ফেরত দেবে, কারণ অ্যাপটি উপরের কোয়েরিতে চারটি আইটেমই লোড করে রেখেছিল।
Kotlin
scoresRef.orderByValue().limitToLast(2).addChildEventListener(object : ChildEventListener { override fun onChildAdded(snapshot: DataSnapshot, previousChild: String?) { Log.d(TAG, "The ${snapshot.key} dinosaur's score is ${snapshot.value}") } // ... })
Java
scoresRef.orderByValue().limitToLast(2).addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(@NonNull DataSnapshot snapshot, String previousChild) { Log.d(TAG, "The " + snapshot.getKey() + " dinosaur's score is " + snapshot.getValue()); } // ... });
পূর্ববর্তী উদাহরণে, Firebase Realtime Database ক্লায়েন্ট পার্সিস্টেড ক্যাশে ব্যবহার করে সর্বোচ্চ স্কোর করা দুটি ডাইনোসরের জন্য 'child added' ইভেন্ট তৈরি করে। কিন্তু এটি 'value' ইভেন্ট তৈরি করবে না, কারণ অ্যাপটি অনলাইনে থাকাকালীন সেই কোয়েরিটি কখনও এক্সিকিউট করেনি।
অ্যাপটি অফলাইনে থাকাকালীন যদি শেষ ছয়টি আইটেমের জন্য অনুরোধ করে, তবে এটি সাথে সাথেই ক্যাশ করা চারটি আইটেমের জন্য 'চাইল্ড অ্যাডেড' ইভেন্ট পেয়ে যাবে। ডিভাইসটি আবার অনলাইনে এলে, Firebase Realtime Database ক্লায়েন্ট সার্ভারের সাথে সিঙ্ক্রোনাইজ করে এবং অ্যাপটির জন্য শেষ দুটি 'চাইল্ড অ্যাডেড' ও 'ভ্যালু' ইভেন্টগুলো পেয়ে যায়।
অফলাইনে লেনদেন পরিচালনা
অ্যাপটি অফলাইনে থাকাকালীন সম্পাদিত যেকোনো লেনদেন সারিবদ্ধ করা হয়। অ্যাপটি নেটওয়ার্ক সংযোগ ফিরে পেলে, লেনদেনগুলো Realtime Database সার্ভারে পাঠানো হয়।
উপস্থিতি পরিচালনা
রিয়েলটাইম অ্যাপ্লিকেশনগুলিতে ক্লায়েন্টরা কখন সংযোগ স্থাপন করে এবং সংযোগ বিচ্ছিন্ন করে তা শনাক্ত করা প্রায়শই দরকারি হয়। উদাহরণস্বরূপ, কোনো ব্যবহারকারীর ক্লায়েন্ট সংযোগ বিচ্ছিন্ন হলে আপনি তাকে 'অফলাইন' হিসেবে চিহ্নিত করতে চাইতে পারেন।
ফায়ারবেস ডেটাবেস ক্লায়েন্টগুলো কিছু সহজ প্রিমিটিভ প্রদান করে, যা ব্যবহার করে কোনো ক্লায়েন্ট ফায়ারবেস ডেটাবেস সার্ভার থেকে সংযোগ বিচ্ছিন্ন হলে ডেটাবেসে লেখা যায়। ক্লায়েন্টটি সঠিকভাবে সংযোগ বিচ্ছিন্ন করুক বা না করুক, এই আপডেটগুলো সম্পন্ন হয়। তাই, সংযোগ বিচ্ছিন্ন হয়ে গেলেও বা ক্লায়েন্ট ক্র্যাশ করলেও ডেটা পরিষ্কার করার জন্য আপনি এগুলোর ওপর নির্ভর করতে পারেন। সংযোগ বিচ্ছিন্ন হওয়ার পরেও ডেটা সেট করা, আপডেট করা এবং মুছে ফেলাসহ সমস্ত লেখার কাজ সম্পাদন করা যায়।
onDisconnect প্রিমিটিভ ব্যবহার করে সংযোগ বিচ্ছিন্ন হওয়ার পর ডেটা লেখার একটি সহজ উদাহরণ নিচে দেওয়া হলো:
Kotlin
val presenceRef = Firebase.database.getReference("disconnectmessage") // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!")
Java
DatabaseReference presenceRef = FirebaseDatabase.getInstance().getReference("disconnectmessage"); // Write a string when this client loses connection presenceRef.onDisconnect().setValue("I disconnected!");
অনডিসকানেক্ট কীভাবে কাজ করে
যখন আপনি একটি onDisconnect() অপারেশন স্থাপন করেন, তখন অপারেশনটি Firebase Realtime Database সার্ভারে থাকে। সার্ভার নিরাপত্তা পরীক্ষা করে নিশ্চিত করে যে ব্যবহারকারী অনুরোধ করা রাইট ইভেন্টটি সম্পাদন করতে পারবে কিনা, এবং যদি তা অবৈধ হয় তবে আপনার অ্যাপকে জানিয়ে দেয়। এরপর সার্ভার সংযোগটি পর্যবেক্ষণ করে। যদি কোনো সময়ে সংযোগের সময়সীমা শেষ হয়ে যায়, অথবা Realtime Database ক্লায়েন্ট দ্বারা সক্রিয়ভাবে বন্ধ করা হয়, তবে সার্ভার দ্বিতীয়বার নিরাপত্তা পরীক্ষা করে (অপারেশনটি এখনও বৈধ কিনা তা নিশ্চিত করার জন্য) এবং তারপর ইভেন্টটি আহ্বান করে।
আপনার অ্যাপ রাইট অপারেশনে কলব্যাকটি ব্যবহার করে নিশ্চিত করতে পারে যে onDisconnect সঠিকভাবে সংযুক্ত হয়েছে:
Kotlin
presenceRef.onDisconnect().removeValue { error, reference -> error?.let { Log.d(TAG, "could not establish onDisconnect event: ${error.message}") } }
Java
presenceRef.onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, @NonNull DatabaseReference reference) { if (error != null) { Log.d(TAG, "could not establish onDisconnect event:" + error.getMessage()); } } });
.cancel() কল করেও একটি onDisconnect ইভেন্ট বাতিল করা যায়:
Kotlin
val onDisconnectRef = presenceRef.onDisconnect() onDisconnectRef.setValue("I disconnected") // ... // some time later when we change our minds // ... onDisconnectRef.cancel()
Java
OnDisconnect onDisconnectRef = presenceRef.onDisconnect(); onDisconnectRef.setValue("I disconnected"); // ... // some time later when we change our minds // ... onDisconnectRef.cancel();
সংযোগের অবস্থা সনাক্তকরণ
উপস্থিতি-সম্পর্কিত অনেক বৈশিষ্ট্যের জন্য, আপনার অ্যাপের অনলাইন বা অফলাইন অবস্থা জানাটা দরকারি। Firebase Realtime Database /.info/connected এ একটি বিশেষ স্থান প্রদান করে, যা প্রতিবার Firebase Realtime Database ক্লায়েন্টের সংযোগের অবস্থা পরিবর্তিত হলে আপডেট করা হয়। এখানে একটি উদাহরণ দেওয়া হলো:
Kotlin
val connectedRef = Firebase.database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue(Boolean::class.java) ?: false if (connected) { Log.d(TAG, "connected") } else { Log.d(TAG, "not connected") } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
Java
DatabaseReference connectedRef = FirebaseDatabase.getInstance().getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { Log.d(TAG, "connected"); } else { Log.d(TAG, "not connected"); } } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
/.info/connected একটি বুলিয়ান মান, যা Realtime Database ক্লায়েন্টগুলোর মধ্যে সিঙ্ক্রোনাইজড হয় না, কারণ এর মান ক্লায়েন্টের অবস্থার উপর নির্ভরশীল। অন্য কথায়, যদি একটি ক্লায়েন্ট /.info/connected false হিসেবে পড়ে, তবে এর কোনো নিশ্চয়তা নেই যে অন্য একটি ক্লায়েন্টও false-ই পড়বে।
অ্যান্ড্রয়েডে, ব্যান্ডউইথ এবং ব্যাটারির ব্যবহার কমাতে ফায়ারবেস স্বয়ংক্রিয়ভাবে সংযোগের অবস্থা পরিচালনা করে। যখন কোনো ক্লায়েন্টের কোনো সক্রিয় লিসেনার থাকে না, কোনো পেন্ডিং রাইট বা onDisconnect অপারেশন থাকে না, এবং goOffline মেথডের মাধ্যমে স্পষ্টভাবে সংযোগ বিচ্ছিন্ন করা হয় না, তখন ফায়ারবেস ৬০ সেকেন্ড নিষ্ক্রিয় থাকার পর সংযোগটি বন্ধ করে দেয়।
লেটেন্সি পরিচালনা
সার্ভার টাইমস্ট্যাম্প
Firebase Realtime Database সার্ভারগুলো সার্ভারে তৈরি হওয়া টাইমস্ট্যাম্পকে ডেটা হিসেবে সন্নিবেশ করার একটি ব্যবস্থা প্রদান করে। এই বৈশিষ্ট্যটি, onDisconnect সাথে মিলিত হয়ে, একটি Realtime Database ক্লায়েন্ট কখন সংযোগ বিচ্ছিন্ন করেছে সেই সময়টি নির্ভরযোগ্যভাবে লিখে রাখার একটি সহজ উপায় প্রদান করে।
Kotlin
val userLastOnlineRef = Firebase.database.getReference("users/joe/lastOnline") userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP)
Java
DatabaseReference userLastOnlineRef = FirebaseDatabase.getInstance().getReference("users/joe/lastOnline"); userLastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP);
ঘড়ির তির্যক
যদিও firebase.database.ServerValue.TIMESTAMP অনেক বেশি নির্ভুল এবং বেশিরভাগ রিড/রাইট অপারেশনের জন্য পছন্দনীয়, Firebase Realtime Database -এর সার্ভারগুলোর সাপেক্ষে ক্লায়েন্টের ঘড়ির সময়ের অসামঞ্জস্যতা অনুমান করার জন্য এটি মাঝে মাঝে কাজে আসতে পারে। সার্ভারের সময় অনুমান করার জন্য Firebase Realtime Database ক্লায়েন্টরা স্থানীয় রিপোর্ট করা সময়ের (মিলিসেকেন্ডে ইপক টাইম) সাথে যে মানটি মিলিসেকেন্ডে যোগ করে, তা পাওয়ার জন্য আপনি /.info/serverTimeOffset লোকেশনে একটি কলব্যাক সংযুক্ত করতে পারেন। মনে রাখবেন যে এই অফসেটের নির্ভুলতা নেটওয়ার্কিং ল্যাটেন্সি দ্বারা প্রভাবিত হতে পারে, এবং তাই এটি মূলত ঘড়ির সময়ের বড় (> ১ সেকেন্ড) অমিল খুঁজে বের করার জন্য উপযোগী।
Kotlin
val offsetRef = Firebase.database.getReference(".info/serverTimeOffset") offsetRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val offset = snapshot.getValue(Double::class.java) ?: 0.0 val estimatedServerTimeMs = System.currentTimeMillis() + offset } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled") } })
Java
DatabaseReference offsetRef = FirebaseDatabase.getInstance().getReference(".info/serverTimeOffset"); offsetRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { double offset = snapshot.getValue(Double.class); double estimatedServerTimeMs = System.currentTimeMillis() + offset; } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled"); } });
নমুনা উপস্থিতি অ্যাপ
সংযোগ বিচ্ছিন্ন করার কার্যক্রম, সংযোগের অবস্থা পর্যবেক্ষণ এবং সার্ভার টাইমস্ট্যাম্পের সমন্বয়ে আপনি একটি ব্যবহারকারীর উপস্থিতি সিস্টেম তৈরি করতে পারেন। এই সিস্টেমে, প্রতিটি ব্যবহারকারী একটি Realtime Database ক্লায়েন্ট অনলাইন আছে কি না তা নির্দেশ করার জন্য একটি ডেটাবেস অবস্থানে ডেটা সংরক্ষণ করে। ক্লায়েন্টরা অনলাইন হলে এই অবস্থানটিকে 'ট্রু' (true) এবং সংযোগ বিচ্ছিন্ন হলে একটি টাইমস্ট্যাম্প সেট করে। এই টাইমস্ট্যাম্পটি নির্দেশ করে যে প্রদত্ত ব্যবহারকারী শেষবার কখন অনলাইন ছিলেন।
মনে রাখবেন, কোনো ব্যবহারকারীকে অনলাইন হিসেবে চিহ্নিত করার আগেই আপনার অ্যাপকে সংযোগ বিচ্ছিন্ন করার অপারেশনগুলো কিউতে রাখতে হবে, যাতে সার্ভারে উভয় কমান্ড পাঠানোর আগেই ক্লায়েন্টের নেটওয়ার্ক সংযোগ বিচ্ছিন্ন হয়ে গেলে কোনো রেস কন্ডিশন তৈরি না হয়।
এখানে একটি সহজ ব্যবহারকারী উপস্থিতি সিস্টেম দেওয়া হলো:
Kotlin
// 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 val database = Firebase.database val myConnectionsRef = database.getReference("users/joe/connections") // Stores the timestamp of my last disconnect (the last time I was seen online) val lastOnlineRef = database.getReference("/users/joe/lastOnline") val connectedRef = database.getReference(".info/connected") connectedRef.addValueEventListener(object : ValueEventListener { override fun onDataChange(snapshot: DataSnapshot) { val connected = snapshot.getValue<Boolean>() ?: false if (connected) { val con = myConnectionsRef.push() // When this device disconnects, remove it con.onDisconnect().removeValue() // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP) // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(java.lang.Boolean.TRUE) } } override fun onCancelled(error: DatabaseError) { Log.w(TAG, "Listener was cancelled at .info/connected") } })
Java
// 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 FirebaseDatabase database = FirebaseDatabase.getInstance(); final DatabaseReference myConnectionsRef = database.getReference("users/joe/connections"); // Stores the timestamp of my last disconnect (the last time I was seen online) final DatabaseReference lastOnlineRef = database.getReference("/users/joe/lastOnline"); final DatabaseReference connectedRef = database.getReference(".info/connected"); connectedRef.addValueEventListener(new ValueEventListener() { @Override public void onDataChange(@NonNull DataSnapshot snapshot) { boolean connected = snapshot.getValue(Boolean.class); if (connected) { DatabaseReference con = myConnectionsRef.push(); // When this device disconnects, remove it con.onDisconnect().removeValue(); // When I disconnect, update the last time I was seen online lastOnlineRef.onDisconnect().setValue(ServerValue.TIMESTAMP); // Add this device to my connections list // this value could contain info about the device or a timestamp too con.setValue(Boolean.TRUE); } } @Override public void onCancelled(@NonNull DatabaseError error) { Log.w(TAG, "Listener was cancelled at .info/connected"); } });