(ঐচ্ছিক) ফায়ারবেস এমুলেটর স্যুট দিয়ে প্রোটোটাইপ এবং পরীক্ষা করুন
আপনার অ্যাপ কীভাবে রিয়েলটাইম ডেটাবেস থেকে ডেটা পড়ে এবং লেখে, সে সম্পর্কে কথা বলার আগে, চলুন এমন কিছু টুলের সাথে পরিচিত হই যা আপনি রিয়েলটাইম ডেটাবেসের কার্যকারিতা প্রোটোটাইপ এবং পরীক্ষা করার জন্য ব্যবহার করতে পারেন: ফায়ারবেস এমুলেটর স্যুট। আপনি যদি বিভিন্ন ডেটা মডেল পরীক্ষা করে দেখেন, আপনার নিরাপত্তা নিয়মগুলো অপ্টিমাইজ করেন, অথবা ব্যাক-এন্ডের সাথে ইন্টারঅ্যাক্ট করার সবচেয়ে সাশ্রয়ী উপায় খুঁজে বের করার চেষ্টা করেন, তবে লাইভ সার্ভিস ডেপ্লয় না করে স্থানীয়ভাবে কাজ করতে পারাটা একটি দারুণ ব্যাপার হতে পারে।
একটি রিয়েলটাইম ডেটাবেস এমুলেটর হলো এমুলেটর স্যুটের একটি অংশ, যা আপনার অ্যাপকে আপনার এমুলেটেড ডেটাবেসের বিষয়বস্তু ও কনফিগারেশনের সাথে, এবং ঐচ্ছিকভাবে আপনার এমুলেটেড প্রোজেক্ট রিসোর্সগুলোর (ফাংশন, অন্যান্য ডেটাবেস এবং নিরাপত্তা বিধি) সাথে ইন্টারঅ্যাক্ট করতে সক্ষম করে।
রিয়েলটাইম ডেটাবেস এমুলেটর ব্যবহার করতে মাত্র কয়েকটি ধাপ অনুসরণ করতে হয়:
- এমুলেটরের সাথে সংযোগ করার জন্য আপনার অ্যাপের টেস্ট কনফিগ-এ একটি কোড লাইন যোগ করুন।
- আপনার স্থানীয় প্রজেক্ট ডিরেক্টরির রুট থেকে
firebase emulators:startচালান। - আপনার অ্যাপের প্রোটোটাইপ কোড থেকে যথারীতি রিয়েলটাইম ডেটাবেস প্ল্যাটফর্ম SDK ব্যবহার করে, অথবা রিয়েলটাইম ডেটাবেস REST API ব্যবহার করে কল করা।
রিয়েলটাইম ডেটাবেস এবং ক্লাউড ফাংশন সম্পর্কিত একটি বিস্তারিত নির্দেশিকা উপলব্ধ আছে। আপনার এমুলেটর স্যুট পরিচিতিটিও দেখে নেওয়া উচিত।
একটি ডেটাবেস রেফারেন্স পান
ডাটাবেস থেকে ডেটা পড়তে বা লিখতে, আপনার DatabaseReference এর একটি ইনস্ট্যান্স প্রয়োজন।
DatabaseReference ref = FirebaseDatabase.instance.ref();
ডেটা লিখুন
এই ডকুমেন্টে ফায়ারবেস ডেটা পড়া এবং লেখার প্রাথমিক বিষয়গুলো আলোচনা করা হয়েছে।
ফায়ারবেস ডেটা একটি DatabaseReference এ লেখা হয় এবং সেই রেফারেন্স দ্বারা নির্গত ইভেন্টগুলোর জন্য অপেক্ষা করে বা শুনে তা পুনরুদ্ধার করা হয়। ডেটার প্রাথমিক অবস্থার জন্য একবার এবং ডেটা পরিবর্তিত হলেই আবার ইভেন্ট নির্গত হয়।
মৌলিক লেখার ক্রিয়াকলাপ
সাধারণ রাইট অপারেশনের জন্য, আপনি set() ব্যবহার করে একটি নির্দিষ্ট রেফারেন্সে ডেটা সেভ করতে পারেন, যা ওই পাথে থাকা যেকোনো ডেটাকে প্রতিস্থাপন করে। আপনি নিম্নলিখিত টাইপগুলোর রেফারেন্স সেট করতে পারেন: String , boolean , int , double , Map , List ।
উদাহরণস্বরূপ, আপনি set() ব্যবহার করে নিম্নলিখিতভাবে একজন ব্যবহারকারী যোগ করতে পারেন:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
await ref.set({
"name": "John",
"age": 18,
"address": {
"line1": "100 Mountain View"
}
});
এইভাবে set() ব্যবহার করলে নির্দিষ্ট স্থানের ডেটা ওভাররাইট হয়ে যায়, যার মধ্যে যেকোনো চাইল্ড নোডও অন্তর্ভুক্ত থাকে। তবে, আপনি পুরো অবজেক্টটি পুনরায় না লিখেও একটি চাইল্ডকে আপডেট করতে পারেন। আপনি যদি ব্যবহারকারীদের তাদের প্রোফাইল আপডেট করার অনুমতি দিতে চান, তাহলে আপনি নিম্নলিখিতভাবে ইউজারনেম আপডেট করতে পারেন:
DatabaseReference ref = FirebaseDatabase.instance.ref("users/123");
// Only update the age, leave the name and address!
await ref.update({
"age": 19,
});
` update() ` মেথডটি নোডগুলির একটি সাব-পাথ গ্রহণ করে, যার ফলে আপনি ডাটাবেসের একাধিক নোড একসাথে আপডেট করতে পারেন:
DatabaseReference ref = FirebaseDatabase.instance.ref("users");
await ref.update({
"123/age": 19,
"123/address/line1": "1 Mountain View",
});
ডেটা পড়ুন
ভ্যালু ইভেন্ট শুনে ডেটা পড়ুন
কোনো পাথের ডেটা পড়তে এবং পরিবর্তনের জন্য নজর রাখতে, DatabaseReference এর onValue প্রপার্টি ব্যবহার করে DatabaseEvent গুলোর জন্য লিসেন করুন।
আপনি DatabaseEvent ব্যবহার করে একটি নির্দিষ্ট পাথের ডেটা পড়তে পারেন, যা ইভেন্টটি ঘটার সময় যেমন থাকে ঠিক তেমনভাবেই বিদ্যমান থাকে। লিসেনারটি সংযুক্ত হলে এই ইভেন্টটি একবার ট্রিগার হয় এবং চাইল্ড ডেটা সহ প্রতিবার ডেটা পরিবর্তিত হলে এটি আবার ট্রিগার হয়। ইভেন্টটির একটি snapshot প্রপার্টি আছে, যাতে চাইল্ড ডেটা সহ সেই লোকেশনের সমস্ত ডেটা থাকে। যদি কোনো ডেটা না থাকে, তাহলে snappt-এর exists প্রপার্টি false এবং value প্রপার্টি null হবে।
নিম্নলিখিত উদাহরণটি একটি সোশ্যাল ব্লগিং অ্যাপ্লিকেশন প্রদর্শন করে যা ডাটাবেস থেকে একটি পোস্টের বিবরণ পুনরুদ্ধার করে:
DatabaseReference starCountRef =
FirebaseDatabase.instance.ref('posts/$postId/starCount');
starCountRef.onValue.listen((DatabaseEvent event) {
final data = event.snapshot.value;
updateStarCount(data);
});
লিসেনারটি তার value প্রপার্টিতে একটি DataSnapshot গ্রহণ করে, যেটিতে ইভেন্টের সময় ডাটাবেসের নির্দিষ্ট অবস্থানে থাকা ডেটা থাকে।
ডেটা একবার পড়ুন
get() ব্যবহার করে একবার পড়ুন
আপনার অ্যাপ অনলাইন বা অফলাইন যাই থাকুক না কেন, ডাটাবেস সার্ভারের সাথে যোগাযোগ পরিচালনার জন্য এই SDK-টি ডিজাইন করা হয়েছে।
সাধারণত, ব্যাকএন্ড থেকে ডেটার আপডেট সম্পর্কে অবহিত হতে আপনার উপরে বর্ণিত ভ্যালু ইভেন্ট কৌশলগুলো ব্যবহার করা উচিত। এই কৌশলগুলো আপনার ব্যবহার ও বিলিং কমায় এবং ব্যবহারকারীরা অনলাইন ও অফলাইন উভয় অবস্থাতেই যাতে সেরা অভিজ্ঞতা পান, সেজন্য এগুলোকে অপ্টিমাইজ করা হয়েছে।
আপনার যদি ডেটা শুধু একবারই প্রয়োজন হয়, তাহলে ডাটাবেস থেকে ডেটার একটি স্ন্যাপশট নিতে আপনি get() ব্যবহার করতে পারেন। যদি কোনো কারণে get() সার্ভারের মান ফেরত দিতে না পারে, তাহলে ক্লায়েন্ট স্থানীয় স্টোরেজ ক্যাশে অনুসন্ধান করবে এবং তারপরেও মানটি খুঁজে না পেলে একটি ত্রুটি ফেরত দেবে।
নিম্নলিখিত উদাহরণটি ডাটাবেস থেকে একজন ব্যবহারকারীর সর্বজনীন ইউজারনেম একবার পুনরুদ্ধার করার পদ্ধতি প্রদর্শন করে:
final ref = FirebaseDatabase.instance.ref();
final snapshot = await ref.child('users/$userId').get();
if (snapshot.exists) {
print(snapshot.value);
} else {
print('No data available.');
}
get() এর অপ্রয়োজনীয় ব্যবহার ব্যান্ডউইথের ব্যবহার বাড়িয়ে দিতে পারে এবং পারফরম্যান্সের অবনতি ঘটাতে পারে, যা উপরে দেখানো পদ্ধতি অনুযায়ী একটি রিয়েলটাইম লিসেনার ব্যবহার করে প্রতিরোধ করা যায়।
once() ব্যবহার করে ডেটা একবার পড়ুন।
কিছু ক্ষেত্রে আপনি সার্ভারে আপডেট হওয়া মান পরীক্ষা না করে, স্থানীয় ক্যাশ থেকে মানটি অবিলম্বে ফেরত পেতে চাইতে পারেন। সেইসব ক্ষেত্রে আপনি স্থানীয় ডিস্ক ক্যাশ থেকে অবিলম্বে ডেটা পেতে once() ব্যবহার করতে পারেন।
এটি এমন ডেটার জন্য উপযোগী যা কেবল একবার লোড করা প্রয়োজন এবং যা ঘন ঘন পরিবর্তন হবে বলে আশা করা যায় না বা যার জন্য সক্রিয়ভাবে পর্যবেক্ষণের প্রয়োজন হয় না। উদাহরণস্বরূপ, পূর্ববর্তী উদাহরণগুলিতে থাকা ব্লগিং অ্যাপটি এই পদ্ধতিটি ব্যবহার করে একজন ব্যবহারকারীর প্রোফাইল লোড করে যখন তিনি একটি নতুন পোস্ট লেখা শুরু করেন:
final event = await ref.once(DatabaseEventType.value);
final username = event.snapshot.value?.username ?? 'Anonymous';
ডেটা আপডেট বা মুছে ফেলা
নির্দিষ্ট ক্ষেত্রগুলি আপডেট করুন
কোনো নোডের অন্যান্য চাইল্ড নোড ওভাররাইট না করে একই সাথে নির্দিষ্ট চাইল্ড নোডগুলোতে লেখার জন্য, update() মেথডটি ব্যবহার করুন।
update() কল করার সময়, আপনি key-এর জন্য একটি পাথ নির্দিষ্ট করে নিম্ন-স্তরের চাইল্ড ভ্যালুগুলো আপডেট করতে পারেন। যদি আরও ভালোভাবে স্কেল করার জন্য ডেটা একাধিক স্থানে সংরক্ষিত থাকে, তাহলে আপনি ডেটা ফ্যান-আউট ব্যবহার করে সেই ডেটার সমস্ত ইনস্ট্যান্স আপডেট করতে পারেন। উদাহরণস্বরূপ, একটি সোশ্যাল ব্লগিং অ্যাপ একটি পোস্ট তৈরি করে একই সাথে সেটিকে রিসেন্ট অ্যাক্টিভিটি ফিড এবং পোস্টকারী ব্যবহারকারীর অ্যাক্টিভিটি ফিডে আপডেট করতে চাইতে পারে। এটি করার জন্য, ব্লগিং অ্যাপ্লিকেশনটি এই ধরনের কোড ব্যবহার করে:
void writeNewPost(String uid, String username, String picture, String title,
String body) async {
// A post entry.
final postData = {
'author': username,
'uid': uid,
'body': body,
'title': title,
'starCount': 0,
'authorPic': picture,
};
// Get a key for a new Post.
final newPostKey =
FirebaseDatabase.instance.ref().child('posts').push().key;
// Write the new post's data simultaneously in the posts list and the
// user's post list.
final Map<String, Map> updates = {};
updates['/posts/$newPostKey'] = postData;
updates['/user-posts/$uid/$newPostKey'] = postData;
return FirebaseDatabase.instance.ref().update(updates);
}
এই উদাহরণে push() ব্যবহার করে /posts/$postid এ সকল ব্যবহারকারীর পোস্ট ধারণকারী নোডে একটি পোস্ট তৈরি করা হয় এবং একই সাথে key ব্যবহার করে `key`-টি পুনরুদ্ধার করা হয়। এরপর এই `key`-টি ব্যবহার করে /user-posts/$userid/$postid এ ব্যবহারকারীর পোস্টসমূহে একটি দ্বিতীয় এন্ট্রি তৈরি করা যেতে পারে।
এই পাথগুলো ব্যবহার করে, আপনি update() ফাংশনের একটিমাত্র কলের মাধ্যমে JSON ট্রি-এর একাধিক স্থানে একযোগে আপডেট করতে পারেন, যেমনটি এই উদাহরণে উভয় স্থানেই নতুন পোস্টটি তৈরি করা হয়েছে। এইভাবে করা একযোগে আপডেটগুলো অ্যাটমিক হয়: হয় সব আপডেট সফল হয় অথবা সব আপডেট ব্যর্থ হয়।
একটি সমাপ্তি কলব্যাক যোগ করুন
আপনার ডেটা কখন কমিট করা হয়েছে তা জানতে চাইলে, আপনি কমপ্লিশন কলব্যাক রেজিস্টার করতে পারেন। set() এবং update() উভয়ই Future রিটার্ন করে, যেগুলোর সাথে আপনি সাকসেস এবং এরর কলব্যাক সংযুক্ত করতে পারেন। এই কলব্যাকগুলো তখন কল করা হয় যখন ডেটাবেসে রাইটটি কমিট করা হয় এবং যখন কলটি অসফল হয়।
FirebaseDatabase.instance
.ref('users/$userId/email')
.set(emailAddress)
.then((_) {
// Data saved successfully!
})
.catchError((error) {
// The write failed...
});
ডেটা মুছে ফেলুন
ডেটা মুছে ফেলার সবচেয়ে সহজ উপায় হলো, সেই ডেটার অবস্থানের রেফারেন্সের উপর remove() ফাংশনটি কল করা।
আপনি set() বা update() এর মতো অন্য কোনো রাইট অপারেশনের ভ্যালু হিসেবে null উল্লেখ করেও ডিলিট করতে পারেন। একটিমাত্র API কলে একাধিক চাইল্ড ডিলিট করার জন্য আপনি update() এর সাথে এই কৌশলটি ব্যবহার করতে পারেন।
লেনদেন হিসেবে ডেটা সংরক্ষণ করুন
যখন এমন ডেটা নিয়ে কাজ করা হয় যা একই সাথে একাধিক পরিবর্তনের ফলে নষ্ট হয়ে যেতে পারে, যেমন ইনক্রিমেন্টাল কাউন্টার, তখন আপনি runTransaction() ফাংশনে একটি ট্রানজ্যাকশন হ্যান্ডলার পাস করে একটি ট্রানজ্যাকশন ব্যবহার করতে পারেন। একটি ট্রানজ্যাকশন হ্যান্ডলার আর্গুমেন্ট হিসেবে ডেটার বর্তমান অবস্থা গ্রহণ করে এবং আপনি যে নতুন কাঙ্ক্ষিত অবস্থাটি লিখতে চান তা রিটার্ন করে। যদি আপনার নতুন মান সফলভাবে লেখার আগে অন্য কোনো ক্লায়েন্ট সেই লোকেশনে লেখে, তাহলে আপনার আপডেট ফাংশনটি নতুন বর্তমান মান দিয়ে আবার কল করা হয় এবং লেখার চেষ্টাটি পুনরায় করা হয়।
উদাহরণস্বরূপ, উদাহরণে দেওয়া সোশ্যাল ব্লগিং অ্যাপটিতে, আপনি ব্যবহারকারীদের পোস্ট স্টার ও আনস্টার করার সুযোগ দিতে পারেন এবং একটি পোস্ট কতগুলো স্টার পেয়েছে তার হিসাব নিম্নোক্তভাবে রাখতে পারেন:
void toggleStar(String uid) async {
DatabaseReference postRef =
FirebaseDatabase.instance.ref("posts/foo-bar-123");
TransactionResult result = await postRef.runTransaction((Object? post) {
// Ensure a post at the ref exists.
if (post == null) {
return Transaction.abort();
}
Map<String, dynamic> _post = Map<String, dynamic>.from(post as Map);
if (_post["stars"] is Map && _post["stars"][uid] != null) {
_post["starCount"] = (_post["starCount"] ?? 1) - 1;
_post["stars"][uid] = null;
} else {
_post["starCount"] = (_post["starCount"] ?? 0) + 1;
if (!_post.containsKey("stars")) {
_post["stars"] = {};
}
_post["stars"][uid] = true;
}
// Return the new data.
return Transaction.success(_post);
});
}
ডিফল্টরূপে, প্রতিবার ট্রানজ্যাকশন আপডেট ফাংশনটি চলার সময় ইভেন্ট তৈরি হয়, তাই আপনি যদি ফাংশনটি একাধিকবার চালান, তাহলে আপনি মধ্যবর্তী অবস্থা দেখতে পারেন। এই মধ্যবর্তী অবস্থাগুলো দমন করতে এবং এর পরিবর্তে ইভেন্ট তৈরি হওয়ার আগে ট্রানজ্যাকশনটি সম্পূর্ণ হওয়া পর্যন্ত অপেক্ষা করতে, আপনি applyLocally কে false সেট করতে পারেন।
await ref.runTransaction((Object? post) {
// ...
}, applyLocally: false);
একটি লেনদেনের ফলাফল হলো TransactionResult , যাতে লেনদেনটি সম্পন্ন হয়েছে কিনা এবং নতুন স্ন্যাপশটের মতো তথ্য থাকে:
DatabaseReference ref = FirebaseDatabase.instance.ref("posts/123");
TransactionResult result = await ref.runTransaction((Object? post) {
// ...
});
print('Committed? ${result.committed}'); // true / false
print('Snapshot? ${result.snapshot}'); // DataSnapshot
একটি লেনদেন বাতিল করা
যদি আপনি নিরাপদে কোনো ট্রানজ্যাকশন বাতিল করতে চান, তাহলে একটি AbortTransactionException থ্রো করার জন্য Transaction.abort() কল করুন:
TransactionResult result = await ref.runTransaction((Object? user) {
if (user !== null) {
return Transaction.abort();
}
// ...
});
print(result.committed); // false
পারমাণবিক সার্ভার-সাইড বৃদ্ধি
উপরের উদাহরণে আমরা ডাটাবেসে দুটি ভ্যালু লিখছি: যে ইউজার পোস্টটি স্টার বা আনস্টার করছেন তার আইডি, এবং স্টারের সংখ্যা বৃদ্ধির পরিমাণ। যদি আমরা আগে থেকেই জানি যে ইউজার পোস্টটি স্টার করছেন, তাহলে আমরা ট্রানজ্যাকশনের পরিবর্তে একটি অ্যাটমিক ইনক্রিমেন্ট অপারেশন ব্যবহার করতে পারি।
void addStar(uid, key) async {
Map<String, Object?> updates = {};
updates["posts/$key/stars/$uid"] = true;
updates["posts/$key/starCount"] = ServerValue.increment(1);
updates["user-posts/$key/stars/$uid"] = true;
updates["user-posts/$key/starCount"] = ServerValue.increment(1);
return FirebaseDatabase.instance.ref().update(updates);
}
এই কোডটি কোনো ট্রানজ্যাকশন অপারেশন ব্যবহার করে না, তাই কোনো সাংঘর্ষিক আপডেট ঘটলে এটি স্বয়ংক্রিয়ভাবে পুনরায় রান হয় না। তবে, যেহেতু ইনক্রিমেন্ট অপারেশনটি সরাসরি ডাটাবেস সার্ভারে সম্পন্ন হয়, তাই কোনো সংঘাতের সম্ভাবনা থাকে না।
যদি আপনি অ্যাপ্লিকেশন-নির্দিষ্ট দ্বন্দ্ব শনাক্ত ও বাতিল করতে চান, যেমন কোনো ব্যবহারকারী পূর্বে স্টার দেওয়া কোনো পোস্টে আবার স্টার দেওয়া, তাহলে সেই নির্দিষ্ট পরিস্থিতির জন্য আপনার নিজস্ব নিরাপত্তা নিয়ম লেখা উচিত।
অফলাইনে ডেটা নিয়ে কাজ করুন
যদি কোনো ক্লায়েন্ট তার নেটওয়ার্ক সংযোগ হারায়, আপনার অ্যাপটি সঠিকভাবে কাজ করতে থাকবে।
ফায়ারবেস ডেটাবেসের সাথে সংযুক্ত প্রতিটি ক্লায়েন্ট তার সক্রিয় ডেটার নিজস্ব একটি অভ্যন্তরীণ সংস্করণ বজায় রাখে। যখন ডেটা লেখা হয়, তখন তা প্রথমে এই স্থানীয় সংস্করণে লেখা হয়। এরপর ফায়ারবেস ক্লায়েন্ট সেই ডেটা দূরবর্তী ডেটাবেস সার্ভার এবং অন্যান্য ক্লায়েন্টদের সাথে সর্বাত্মক প্রচেষ্টার ভিত্তিতে সিঙ্ক্রোনাইজ করে।
এর ফলে, সার্ভারে কোনো ডেটা লেখার আগেই, ডেটাবেসে করা সমস্ত রাইট অপারেশন তাৎক্ষণিকভাবে লোকাল ইভেন্ট ট্রিগার করে। এর মানে হলো, নেটওয়ার্ক ল্যাটেন্সি বা কানেক্টিভিটি নির্বিশেষে আপনার অ্যাপটি রেসপন্সিভ থাকে।
সংযোগ পুনঃপ্রতিষ্ঠিত হলে, আপনার অ্যাপটি প্রয়োজনীয় ইভেন্টগুলো পেয়ে যায়, যার ফলে কোনো কাস্টম কোড না লিখেই ক্লায়েন্ট বর্তমান সার্ভার অবস্থার সাথে সিঙ্ক হয়ে যায়।
অনলাইন এবং অফলাইন সক্ষমতা সম্পর্কে আরও জানুন অংশে আমরা অফলাইন আচরণ নিয়ে আরও আলোচনা করব।