(ঐচ্ছিক) Firebase Local Emulator Suite দিয়ে প্রোটোটাইপ এবং পরীক্ষা করুন
আপনার অ্যাপ কীভাবে Realtime Database থেকে ডেটা পড়ে এবং লেখে, সে সম্পর্কে কথা বলার আগে, চলুন এমন কিছু টুলের সাথে পরিচয় করিয়ে দিই যা আপনি Realtime Database কার্যকারিতা প্রোটোটাইপ এবং পরীক্ষা করার জন্য ব্যবহার করতে পারেন: Firebase Local Emulator Suite । আপনি যদি বিভিন্ন ডেটা মডেল পরীক্ষা করে দেখেন, আপনার নিরাপত্তা নিয়মগুলো অপ্টিমাইজ করেন, অথবা ব্যাক-এন্ডের সাথে ইন্টারঅ্যাক্ট করার সবচেয়ে সাশ্রয়ী উপায় খুঁজে বের করার চেষ্টা করেন, তবে লাইভ সার্ভিস ডেপ্লয় না করে স্থানীয়ভাবে কাজ করতে পারাটা একটি দারুণ ব্যাপার হতে পারে।
একটি Realtime Database এমুলেটর হলো Local Emulator Suite একটি অংশ, যা আপনার অ্যাপকে আপনার এমুলেটেড ডেটাবেসের বিষয়বস্তু ও কনফিগারেশনের সাথে, এবং ঐচ্ছিকভাবে আপনার এমুলেটেড প্রোজেক্ট রিসোর্সগুলোর (ফাংশন, অন্যান্য ডেটাবেস এবং নিরাপত্তা বিধি) সাথে ইন্টারঅ্যাক্ট করতে সক্ষম করে।
Realtime Database এমুলেটর ব্যবহার করতে মাত্র কয়েকটি ধাপ অনুসরণ করতে হয়:
- এমুলেটরের সাথে সংযোগ করার জন্য আপনার অ্যাপের টেস্ট কনফিগ-এ একটি কোড লাইন যোগ করুন।
- আপনার স্থানীয় প্রজেক্ট ডিরেক্টরির রুট থেকে
firebase emulators:startচালান। - আপনার অ্যাপের প্রোটোটাইপ কোড থেকে যথারীতি Realtime Database প্ল্যাটফর্ম SDK ব্যবহার করে, অথবা Realtime Database REST API ব্যবহার করে কল করা।
Realtime Database এবং Cloud Functions সম্পর্কিত একটি বিস্তারিত নির্দেশিকা উপলব্ধ আছে। আপনার Local Emulator Suite পরিচিতিটিও দেখে নেওয়া উচিত।
একটি ডাটাবেস রেফারেন্স নিন
ডাটাবেস থেকে ডেটা পড়তে বা লিখতে, আপনার firebase.database.Reference এর একটি ইনস্ট্যান্স প্রয়োজন।
Web
import { getDatabase } from "firebase/database"; const database = getDatabase();
Web
var database = firebase.database();
ডেটা লিখুন
এই ডকুমেন্টে ডেটা পুনরুদ্ধারের প্রাথমিক বিষয় এবং ফায়ারবেস ডেটা কীভাবে সাজাতে ও ফিল্টার করতে হয়, তা আলোচনা করা হয়েছে।
firebase.database.Reference এ একটি অ্যাসিঙ্ক্রোনাস লিসেনার সংযুক্ত করার মাধ্যমে ফায়ারবেস ডেটা পুনরুদ্ধার করা হয়। ডেটার প্রাথমিক অবস্থার জন্য লিসেনারটি একবার ট্রিগার হয় এবং ডেটা পরিবর্তিত হলেই আবার ট্রিগার হয়।
মৌলিক লেখার ক্রিয়াকলাপ
সাধারণ লেখার কাজের জন্য, আপনি set() ব্যবহার করে একটি নির্দিষ্ট রেফারেন্সে ডেটা সংরক্ষণ করতে পারেন, যা সেই পাথে থাকা যেকোনো বিদ্যমান ডেটাকে প্রতিস্থাপন করে। উদাহরণস্বরূপ, একটি সোশ্যাল ব্লগিং অ্যাপ্লিকেশন set() ব্যবহার করে নিম্নরূপে একজন ব্যবহারকারীকে যুক্ত করতে পারে:
Web
import { getDatabase, ref, set } from "firebase/database"; function writeUserData(userId, name, email, imageUrl) { const db = getDatabase(); set(ref(db, 'users/' + userId), { username: name, email: email, profile_picture : imageUrl }); }
Web
function writeUserData(userId, name, email, imageUrl) { firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }); }
set() ব্যবহার করলে নির্দিষ্ট স্থানের ডেটা, তার চাইল্ড নোডগুলো সহ, ওভাররাইট হয়ে যায়।
ডেটা পড়ুন
মূল্যবান ঘটনাগুলোর জন্য কান পেতে শুনুন
কোনো পাথের ডেটা পড়তে এবং পরিবর্তনের জন্য নজর রাখতে, ইভেন্ট পর্যবেক্ষণের জন্য onValue() ব্যবহার করুন। এই ইভেন্টটি ব্যবহার করে আপনি একটি নির্দিষ্ট পাথের বিষয়বস্তুর স্ট্যাটিক স্ন্যাপশট পড়তে পারেন, যা ইভেন্টের সময় যেমন ছিল। লিসেনার সংযুক্ত হলে এই মেথডটি একবার ট্রিগার হয় এবং চাইল্ডসহ ডেটা প্রতিবার পরিবর্তিত হলে আবার ট্রিগার হয়। ইভেন্ট কলব্যাকে একটি স্ন্যাপশট পাস করা হয়, যাতে চাইল্ড ডেটাসহ সেই লোকেশনের সমস্ত ডেটা থাকে। যদি কোনো ডেটা না থাকে, তাহলে আপনি exists() কল করলে স্ন্যাপশটটি false রিটার্ন করবে এবং val() কল করলে null রিটার্ন করবে।
নিম্নলিখিত উদাহরণটি একটি সোশ্যাল ব্লগিং অ্যাপ্লিকেশন প্রদর্শন করে যা ডাটাবেস থেকে একটি পোস্টের স্টার সংখ্যা পুনরুদ্ধার করে:
Web
import { getDatabase, ref, onValue } from "firebase/database"; const db = getDatabase(); const starCountRef = ref(db, 'posts/' + postId + '/starCount'); onValue(starCountRef, (snapshot) => { const data = snapshot.val(); updateStarCount(postElement, data); });
Web
var starCountRef = firebase.database().ref('posts/' + postId + '/starCount'); starCountRef.on('value', (snapshot) => { const data = snapshot.val(); updateStarCount(postElement, data); });
লিসেনার একটি snapshot গ্রহণ করে, যাতে ইভেন্টের সময় ডাটাবেসের নির্দিষ্ট অবস্থানের ডেটা থাকে। আপনি val() মেথড ব্যবহার করে snapshot ডেটা পুনরুদ্ধার করতে পারেন।
ডেটা একবার পড়ুন
get() ব্যবহার করে ডেটা একবার পড়ুন
আপনার অ্যাপ অনলাইন বা অফলাইন যাই থাকুক না কেন, ডাটাবেস সার্ভারের সাথে যোগাযোগ পরিচালনার জন্য এই SDK-টি ডিজাইন করা হয়েছে।
সাধারণত, ব্যাকএন্ড থেকে ডেটার আপডেটের নোটিফিকেশন পেতে আপনার উপরে বর্ণিত ভ্যালু ইভেন্ট টেকনিকগুলো ব্যবহার করা উচিত। লিসেনার টেকনিকগুলো আপনার ব্যবহার ও বিলিং কমায় এবং ব্যবহারকারীরা অনলাইন ও অফলাইন উভয় অবস্থাতেই যাতে সেরা অভিজ্ঞতা পান, সেজন্য এগুলোকে অপ্টিমাইজ করা হয়।
আপনার যদি ডেটা শুধু একবারই প্রয়োজন হয়, তাহলে ডাটাবেস থেকে ডেটার একটি স্ন্যাপশট নিতে আপনি get() ব্যবহার করতে পারেন। যদি কোনো কারণে get() সার্ভারের মান ফেরত দিতে না পারে, তাহলে ক্লায়েন্ট স্থানীয় স্টোরেজ ক্যাশে অনুসন্ধান করবে এবং তারপরেও মানটি খুঁজে না পেলে একটি ত্রুটি ফেরত দেবে।
get() এর অপ্রয়োজনীয় ব্যবহার ব্যান্ডউইথের ব্যবহার বাড়িয়ে দিতে পারে এবং পারফরম্যান্সের অবনতি ঘটাতে পারে, যা উপরে দেখানো পদ্ধতি অনুযায়ী একটি রিয়েলটাইম লিসেনার ব্যবহার করে প্রতিরোধ করা যায়।
Web
import { getDatabase, ref, child, get } from "firebase/database"; const dbRef = ref(getDatabase()); get(child(dbRef, `users/${userId}`)).then((snapshot) => { if (snapshot.exists()) { console.log(snapshot.val()); } else { console.log("No data available"); } }).catch((error) => { console.error(error); });
Web
const dbRef = firebase.database().ref(); dbRef.child("users").child(userId).get().then((snapshot) => { if (snapshot.exists()) { console.log(snapshot.val()); } else { console.log("No data available"); } }).catch((error) => { console.error(error); });
একজন পর্যবেক্ষকের সাথে একবার ডেটা পড়ুন।
কিছু ক্ষেত্রে আপনি সার্ভারে আপডেট হওয়া মান পরীক্ষা না করে, স্থানীয় ক্যাশ থেকে মানটি অবিলম্বে ফেরত পেতে চাইতে পারেন। সেইসব ক্ষেত্রে আপনি স্থানীয় ডিস্ক ক্যাশ থেকে অবিলম্বে ডেটা পেতে once() ব্যবহার করতে পারেন।
এটি এমন ডেটার জন্য উপযোগী যা কেবল একবার লোড করা প্রয়োজন এবং যা ঘন ঘন পরিবর্তন হবে বলে আশা করা যায় না বা যার জন্য সক্রিয়ভাবে পর্যবেক্ষণের প্রয়োজন হয় না। উদাহরণস্বরূপ, পূর্ববর্তী উদাহরণগুলিতে থাকা ব্লগিং অ্যাপটি এই পদ্ধতিটি ব্যবহার করে একজন ব্যবহারকারীর প্রোফাইল লোড করে যখন তিনি একটি নতুন পোস্ট লেখা শুরু করেন:
Web
import { getDatabase, ref, onValue } from "firebase/database"; import { getAuth } from "firebase/auth"; const db = getDatabase(); const auth = getAuth(); const userId = auth.currentUser.uid; return onValue(ref(db, '/users/' + userId), (snapshot) => { const username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... }, { onlyOnce: true });
Web
var userId = firebase.auth().currentUser.uid; return firebase.database().ref('/users/' + userId).once('value').then((snapshot) => { var username = (snapshot.val() && snapshot.val().username) || 'Anonymous'; // ... });
ডেটা আপডেট বা মুছে ফেলা
নির্দিষ্ট ক্ষেত্রগুলি আপডেট করুন
কোনো নোডের অন্যান্য চাইল্ড নোড ওভাররাইট না করে একই সাথে নির্দিষ্ট চাইল্ড নোডগুলোতে লেখার জন্য, update() মেথডটি ব্যবহার করুন।
update() কল করার সময়, আপনি কী-এর জন্য একটি পাথ নির্দিষ্ট করে নিম্ন-স্তরের চাইল্ড ভ্যালুগুলো আপডেট করতে পারেন। যদি আরও ভালোভাবে স্কেল করার জন্য ডেটা একাধিক স্থানে সংরক্ষিত থাকে, তবে আপনি ডেটা ফ্যান-আউট ব্যবহার করে সেই ডেটার সমস্ত ইনস্ট্যান্স আপডেট করতে পারেন।
উদাহরণস্বরূপ, একটি সোশ্যাল ব্লগিং অ্যাপ এই ধরনের কোড ব্যবহার করে একটি পোস্ট তৈরি করতে পারে এবং একই সাথে সেটিকে সাম্প্রতিক কার্যকলাপ ফিড এবং পোস্টকারী ব্যবহারকারীর কার্যকলাপ ফিডে আপডেট করতে পারে:
Web
import { getDatabase, ref, child, push, update } from "firebase/database"; function writeNewPost(uid, username, picture, title, body) { const db = getDatabase(); // A post entry. const postData = { author: username, uid: uid, body: body, title: title, starCount: 0, authorPic: picture }; // Get a key for a new Post. const newPostKey = push(child(ref(db), 'posts')).key; // Write the new post's data simultaneously in the posts list and the user's post list. const updates = {}; updates['/posts/' + newPostKey] = postData; updates['/user-posts/' + uid + '/' + newPostKey] = postData; return update(ref(db), updates); }
Web
function writeNewPost(uid, username, picture, title, body) { // A post entry. var postData = { author: username, uid: uid, body: body, title: title, starCount: 0, authorPic: picture }; // Get a key for a new Post. var newPostKey = firebase.database().ref().child('posts').push().key; // Write the new post's data simultaneously in the posts list and the user's post list. var updates = {}; updates['/posts/' + newPostKey] = postData; updates['/user-posts/' + uid + '/' + newPostKey] = postData; return firebase.database().ref().update(updates); }
এই উদাহরণে push() ব্যবহার করে /posts/$postid এ সকল ব্যবহারকারীর পোস্ট ধারণকারী নোডে একটি পোস্ট তৈরি করা হয় এবং একই সাথে `key`-টি পুনরুদ্ধার করা হয়। এরপর এই `key`-টি ব্যবহার করে /user-posts/$userid/$postid এ ব্যবহারকারীর পোস্টসমূহে একটি দ্বিতীয় এন্ট্রি তৈরি করা যেতে পারে।
এই পাথগুলো ব্যবহার করে, আপনি update() ফাংশনের একটিমাত্র কলের মাধ্যমে JSON ট্রি-এর একাধিক স্থানে একযোগে আপডেট করতে পারেন, যেমনটি এই উদাহরণে উভয় স্থানেই নতুন পোস্টটি তৈরি করা হয়েছে। এইভাবে করা একযোগে আপডেটগুলো অ্যাটমিক হয়: হয় সব আপডেট সফল হয় অথবা সব আপডেট ব্যর্থ হয়।
একটি সমাপ্তি কলব্যাক যোগ করুন
আপনার ডেটা কখন কমিট করা হয়েছে তা জানতে চাইলে, আপনি একটি কমপ্লিশন কলব্যাক যোগ করতে পারেন। set() এবং update() উভয় ফাংশনেই একটি ঐচ্ছিক কমপ্লিশন কলব্যাক থাকে, যা ডাটাবেসে রাইট কমিট হয়ে গেলে কল করা হয়। যদি কলটি অসফল হয়, তবে ব্যর্থতার কারণ নির্দেশ করে কলব্যাকটিকে একটি এরর অবজেক্ট পাঠানো হয়।
Web
import { getDatabase, ref, set } from "firebase/database"; const db = getDatabase(); set(ref(db, 'users/' + userId), { username: name, email: email, profile_picture : imageUrl }) .then(() => { // Data saved successfully! }) .catch((error) => { // The write failed... });
Web
firebase.database().ref('users/' + userId).set({ username: name, email: email, profile_picture : imageUrl }, (error) => { if (error) { // The write failed... } else { // Data saved successfully! } });
ডেটা মুছে ফেলুন
ডেটা মুছে ফেলার সবচেয়ে সহজ উপায় হলো, সেই ডেটার অবস্থানের রেফারেন্সের উপর remove() ফাংশনটি কল করা।
আপনি set() বা update() এর মতো অন্য কোনো রাইট অপারেশনের ভ্যালু হিসেবে null উল্লেখ করেও ডিলিট করতে পারেন। একটিমাত্র API কলে একাধিক চাইল্ড ডিলিট করার জন্য আপনি update() এর সাথে এই কৌশলটি ব্যবহার করতে পারেন।
একটি Promise গ্রহণ করুন
আপনার ডেটা কখন Firebase Realtime Database সার্ভারে কমিট করা হয় তা জানতে, আপনি একটি Promise (Promise) ব্যবহার করতে পারেন। set() এবং update() উভয় ফাংশনই একটি Promise রিটার্ন করতে পারে, যা ব্যবহার করে আপনি জানতে পারবেন কখন ডেটাবেসে রাইটটি কমিট করা হয়েছে।
শ্রোতাদের বিচ্ছিন্ন করুন
আপনার Firebase ডাটাবেস রেফারেন্সে off() মেথডটি কল করার মাধ্যমে কলব্যাকগুলি সরিয়ে ফেলা হয়।
আপনি off() ফাংশনে প্যারামিটার হিসেবে একটি লিসেনার পাস করে সেটি সরাতে পারেন। কোনো আর্গুমেন্ট ছাড়া off() ফাংশনটি কল করলে সেই লোকেশনের সমস্ত লিসেনার মুছে যায়।
প্যারেন্ট লিসেনারে off() কল করলে তার চাইল্ড নোডগুলিতে রেজিস্টার করা লিসেনারগুলি স্বয়ংক্রিয়ভাবে মুছে যায় না; কলব্যাকটি সরাতে হলে যেকোনো চাইল্ড লিসেনারেও off() কল করতে হবে।
লেনদেন হিসেবে ডেটা সংরক্ষণ করুন
যখন এমন ডেটা নিয়ে কাজ করা হয় যা যুগপৎ পরিবর্তনের ফলে নষ্ট হয়ে যেতে পারে, যেমন ইনক্রিমেন্টাল কাউন্টার, তখন আপনি একটি ট্রানজ্যাকশন অপারেশন ব্যবহার করতে পারেন। আপনি এই অপারেশনটিকে একটি আপডেট ফাংশন এবং একটি ঐচ্ছিক কমপ্লিশন কলব্যাক দিতে পারেন। আপডেট ফাংশনটি আর্গুমেন্ট হিসেবে ডেটার বর্তমান অবস্থা গ্রহণ করে এবং আপনি যে নতুন কাঙ্ক্ষিত অবস্থাটি লিখতে চান তা রিটার্ন করে। যদি আপনার নতুন মান সফলভাবে লেখার আগে অন্য কোনো ক্লায়েন্ট সেই লোকেশনে লেখে, তাহলে আপনার আপডেট ফাংশনটি নতুন বর্তমান মান দিয়ে আবার কল করা হয় এবং লেখার চেষ্টাটি পুনরায় করা হয়।
উদাহরণস্বরূপ, উদাহরণে দেওয়া সোশ্যাল ব্লগিং অ্যাপটিতে, আপনি ব্যবহারকারীদের পোস্ট স্টার ও আনস্টার করার সুযোগ দিতে পারেন এবং একটি পোস্ট কতগুলো স্টার পেয়েছে তার হিসাব নিম্নোক্তভাবে রাখতে পারেন:
Web
import { getDatabase, ref, runTransaction } from "firebase/database"; function toggleStar(uid) { const db = getDatabase(); const postRef = ref(db, '/posts/foo-bar-123'); runTransaction(postRef, (post) => { if (post) { if (post.stars && post.stars[uid]) { post.starCount--; post.stars[uid] = null; } else { post.starCount++; if (!post.stars) { post.stars = {}; } post.stars[uid] = true; } } return post; }); }
Web
function toggleStar(postRef, uid) { postRef.transaction((post) => { if (post) { if (post.stars && post.stars[uid]) { post.starCount--; post.stars[uid] = null; } else { post.starCount++; if (!post.stars) { post.stars = {}; } post.stars[uid] = true; } } return post; }); }
ট্রানজ্যাকশন ব্যবহার করলে, একাধিক ব্যবহারকারী একই সময়ে একই পোস্টে স্টার দিলে বা ক্লায়েন্টের কাছে পুরোনো ডেটা থাকলেও স্টারের সংখ্যা ভুল হওয়া থেকে রক্ষা করা যায়। যদি ট্রানজ্যাকশনটি প্রত্যাখ্যাত হয়, সার্ভার ক্লায়েন্টকে বর্তমান মানটি ফেরত পাঠায়, যা আপডেট করা মান দিয়ে ট্রানজ্যাকশনটি আবার চালায়। এই প্রক্রিয়াটি ততক্ষণ চলতে থাকে যতক্ষণ না ট্রানজ্যাকশনটি গৃহীত হয় বা আপনি এটি বাতিল করেন।
পারমাণবিক সার্ভার-সাইড বৃদ্ধি
উপরের উদাহরণে আমরা ডাটাবেসে দুটি ভ্যালু লিখছি: যে ইউজার পোস্টটি স্টার বা আনস্টার করছেন তার আইডি, এবং স্টারের সংখ্যা বৃদ্ধির পরিমাণ। যদি আমরা আগে থেকেই জানি যে ইউজার পোস্টটি স্টার করছেন, তাহলে আমরা ট্রানজ্যাকশনের পরিবর্তে একটি অ্যাটমিক ইনক্রিমেন্ট অপারেশন ব্যবহার করতে পারি।
Web
function addStar(uid, key) { import { getDatabase, increment, ref, update } from "firebase/database"; const dbRef = ref(getDatabase()); const updates = {}; updates[`posts/${key}/stars/${uid}`] = true; updates[`posts/${key}/starCount`] = increment(1); updates[`user-posts/${key}/stars/${uid}`] = true; updates[`user-posts/${key}/starCount`] = increment(1); update(dbRef, updates); }
Web
function addStar(uid, key) { const updates = {}; updates[`posts/${key}/stars/${uid}`] = true; updates[`posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); updates[`user-posts/${key}/stars/${uid}`] = true; updates[`user-posts/${key}/starCount`] = firebase.database.ServerValue.increment(1); firebase.database().ref().update(updates); }
এই কোডটি কোনো ট্রানজ্যাকশন অপারেশন ব্যবহার করে না, তাই কোনো সাংঘর্ষিক আপডেট ঘটলে এটি স্বয়ংক্রিয়ভাবে পুনরায় রান হয় না। তবে, যেহেতু ইনক্রিমেন্ট অপারেশনটি সরাসরি ডাটাবেস সার্ভারে সম্পন্ন হয়, তাই কোনো সংঘাতের সম্ভাবনা থাকে না।
যদি আপনি অ্যাপ্লিকেশন-নির্দিষ্ট দ্বন্দ্ব শনাক্ত ও বাতিল করতে চান, যেমন কোনো ব্যবহারকারী পূর্বে স্টার দেওয়া কোনো পোস্টে আবার স্টার দেওয়া, তাহলে সেই নির্দিষ্ট পরিস্থিতির জন্য আপনার নিজস্ব নিরাপত্তা নিয়ম লেখা উচিত।
অফলাইনে ডেটা নিয়ে কাজ করুন
যদি কোনো ক্লায়েন্ট তার নেটওয়ার্ক সংযোগ হারায়, আপনার অ্যাপটি সঠিকভাবে কাজ করতে থাকবে।
ফায়ারবেস ডেটাবেসের সাথে সংযুক্ত প্রতিটি ক্লায়েন্ট তার সক্রিয় ডেটার নিজস্ব একটি অভ্যন্তরীণ সংস্করণ বজায় রাখে। যখন ডেটা লেখা হয়, তখন তা প্রথমে এই স্থানীয় সংস্করণে লেখা হয়। এরপর ফায়ারবেস ক্লায়েন্ট সেই ডেটা দূরবর্তী ডেটাবেস সার্ভার এবং অন্যান্য ক্লায়েন্টদের সাথে সর্বাত্মক প্রচেষ্টার ভিত্তিতে সিঙ্ক্রোনাইজ করে।
এর ফলে, সার্ভারে কোনো ডেটা লেখার আগেই, ডেটাবেসে করা সমস্ত রাইট অপারেশন তাৎক্ষণিকভাবে লোকাল ইভেন্ট ট্রিগার করে। এর মানে হলো, নেটওয়ার্ক ল্যাটেন্সি বা কানেক্টিভিটি নির্বিশেষে আপনার অ্যাপটি রেসপন্সিভ থাকে।
সংযোগ পুনঃপ্রতিষ্ঠিত হলে, আপনার অ্যাপটি প্রয়োজনীয় ইভেন্টগুলো পেয়ে যায়, যার ফলে কোনো কাস্টম কোড না লিখেই ক্লায়েন্ট বর্তমান সার্ভার অবস্থার সাথে সিঙ্ক হয়ে যায়।
অনলাইন এবং অফলাইন সক্ষমতা সম্পর্কে আরও জানুন অংশে আমরা অফলাইন আচরণ নিয়ে আরও আলোচনা করব।