ওয়েবে ডেটা পড়ুন এবং লিখুন

সেভ করা পৃষ্ঠা গুছিয়ে রাখতে 'সংগ্রহ' ব্যবহার করুন আপনার পছন্দ অনুযায়ী কন্টেন্ট সেভ করুন ও সঠিক বিভাগে রাখুন।

(ঐচ্ছিক) ফায়ারবেস স্থানীয় এমুলেটর স্যুটের সাথে প্রোটোটাইপ এবং পরীক্ষা করুন

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

একটি রিয়েলটাইম ডাটাবেস এমুলেটর হল স্থানীয় এমুলেটর স্যুটের অংশ, যা আপনার অ্যাপকে আপনার অনুকরণ করা ডাটাবেস সামগ্রী এবং কনফিগারেশনের সাথে ইন্টারঅ্যাক্ট করতে সক্ষম করে, সেইসাথে ঐচ্ছিকভাবে আপনার অনুকরণ করা প্রকল্প সংস্থানগুলি (ফাংশন, অন্যান্য ডেটাবেস এবং নিরাপত্তা নিয়ম)।

রিয়েলটাইম ডেটাবেস এমুলেটর ব্যবহার করার জন্য মাত্র কয়েকটি ধাপ জড়িত:

  1. এমুলেটরের সাথে সংযোগ করতে আপনার অ্যাপের পরীক্ষা কনফিগারে কোডের একটি লাইন যোগ করা হচ্ছে।
  2. আপনার স্থানীয় প্রজেক্ট ডিরেক্টরির রুট থেকে, firebase emulators:start
  3. যথারীতি রিয়েলটাইম ডেটাবেস প্ল্যাটফর্ম SDK ব্যবহার করে বা রিয়েলটাইম ডেটাবেস REST API ব্যবহার করে আপনার অ্যাপের প্রোটোটাইপ কোড থেকে কল করা।

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

একটি ডাটাবেস রেফারেন্স পান

ডাটাবেস থেকে ডেটা পড়তে বা লিখতে, আপনাকে firebase.database.Reference এর একটি উদাহরণ প্রয়োজন। রেফারেন্স:

Web version 9

import { getDatabase } from "firebase/database";

const database = getDatabase();

Web version 8

var database = firebase.database();

ডেটা লিখুন

এই দস্তাবেজটি ডেটা পুনরুদ্ধারের মূল বিষয়গুলি কভার করে এবং কীভাবে Firebase ডেটা অর্ডার এবং ফিল্টার করতে হয়।

একটি firebase.database.Reference এ একটি অ্যাসিঙ্ক্রোনাস লিসেনার সংযুক্ত করে Firebase ডেটা পুনরুদ্ধার করা হয়। শ্রোতা ডেটার প্রাথমিক অবস্থার জন্য একবার ট্রিগার হয় এবং আবার যে কোনও সময় ডেটা পরিবর্তন হয়।

মৌলিক লেখার ক্রিয়াকলাপ

মৌলিক লেখার ক্রিয়াকলাপের জন্য, আপনি একটি নির্দিষ্ট রেফারেন্সে ডেটা সংরক্ষণ করতে set() ব্যবহার করতে পারেন, সেই পথে বিদ্যমান ডেটা প্রতিস্থাপন করতে পারেন। উদাহরণস্বরূপ, একটি সামাজিক ব্লগিং অ্যাপ্লিকেশন set() সহ একজন ব্যবহারকারীকে নিম্নরূপ যোগ করতে পারে:

Web version 9

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 version 8

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 version 9

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 version 8

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 version 9

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 version 8

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 version 9

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 version 8

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 version 9

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 version 8

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 এ সমস্ত ব্যবহারকারীর জন্য পোস্ট রয়েছে এবং একই সাথে কীটি পুনরুদ্ধার করে। কীটি তারপর /user-posts/$userid/$postid এ ব্যবহারকারীর পোস্টে একটি দ্বিতীয় এন্ট্রি তৈরি করতে ব্যবহার করা যেতে পারে।

এই পাথগুলি ব্যবহার করে, আপনি JSON ট্রিতে একাধিক অবস্থানে একযোগে আপডেট করতে পারবেন update() করার জন্য একটি কল দিয়ে, যেমন এই উদাহরণটি কীভাবে উভয় স্থানে নতুন পোস্ট তৈরি করে। এইভাবে করা যুগপত আপডেটগুলি পরমাণু: হয় সমস্ত আপডেট সফল হয় বা সমস্ত আপডেট ব্যর্থ হয়।

একটি সম্পূর্ণ কলব্যাক যোগ করুন

আপনি যদি জানতে চান আপনার ডেটা কখন প্রতিশ্রুতিবদ্ধ হয়েছে, আপনি একটি সম্পূর্ণ কলব্যাক যোগ করতে পারেন। set() এবং update() উভয়ই একটি ঐচ্ছিক সমাপ্তি কলব্যাক নেয় যা ডাটাবেসের সাথে লেখার প্রতিশ্রুতিবদ্ধ হলে বলা হয়। যদি কলটি ব্যর্থ হয়, তাহলে কলব্যাকটি কেন ব্যর্থ হয়েছে তা নির্দেশ করে একটি ত্রুটি অবজেক্ট পাস করা হয়।

Web version 9

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 version 8

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 রিয়েলটাইম ডেটাবেস সার্ভারে প্রতিশ্রুতিবদ্ধ তা জানতে, আপনি একটি Promise ব্যবহার করতে পারেন। set() এবং update() উভয়ই একটি Promise ফেরত দিতে পারে যা আপনি জানতে ব্যবহার করতে পারেন যখন লেখাটি ডাটাবেসের সাথে প্রতিশ্রুতিবদ্ধ হয়।

শ্রোতাদের বিচ্ছিন্ন করুন

আপনার ফায়ারবেস ডাটাবেস রেফারেন্সে off() পদ্ধতিতে কল করে কলব্যাকগুলি সরানো হয়।

আপনি একটি একক শ্রোতাকে একটি প্যারামিটার হিসাবে off() এ পাস করে অপসারণ করতে পারেন। কোনো আর্গুমেন্ট ছাড়াই লোকেশনে কল off() সেই অবস্থানের সমস্ত শ্রোতাদের সরিয়ে দেয়।

একটি অভিভাবক শ্রোতাকে কল off() এর চাইল্ড নোডগুলিতে নিবন্ধিত শ্রোতাদের স্বয়ংক্রিয়ভাবে সরিয়ে দেয় না; off() যেকোন শিশু শ্রোতাদের কলব্যাক অপসারণের জন্যও ডাকতে হবে।

লেনদেন হিসাবে ডেটা সংরক্ষণ করুন

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

উদাহরণস্বরূপ, সোশ্যাল ব্লগিং অ্যাপের উদাহরণে, আপনি ব্যবহারকারীদের পোস্ট স্টার এবং আনস্টার করার অনুমতি দিতে পারেন এবং একটি পোস্ট কতগুলি স্টার পেয়েছে তার ট্র্যাক রাখতে পারেন:

Web version 9

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 version 8

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

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

পারমাণবিক সার্ভার-সাইড বৃদ্ধি

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

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

এই কোড একটি লেনদেন অপারেশন ব্যবহার করে না, তাই এটি স্বয়ংক্রিয়ভাবে পুনরায় চালানো হয় না যদি একটি বিরোধপূর্ণ আপডেট থাকে। যাইহোক, যেহেতু ইনক্রিমেন্ট অপারেশন সরাসরি ডাটাবেস সার্ভারে ঘটে, তাই বিরোধের কোন সুযোগ নেই।

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

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

যদি কোনো ক্লায়েন্ট তার নেটওয়ার্ক সংযোগ হারিয়ে ফেলে, তাহলে আপনার অ্যাপ সঠিকভাবে কাজ করতে থাকবে।

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

ফলস্বরূপ, সার্ভারে কোনো ডেটা লেখার আগে সমস্ত ডাটাবেসে লিখলে তাৎক্ষণিকভাবে স্থানীয় ইভেন্ট ট্রিগার করে। এর মানে নেটওয়ার্ক লেটেন্সি বা সংযোগ নির্বিশেষে আপনার অ্যাপ প্রতিক্রিয়াশীল থাকে।

একবার সংযোগ পুনঃস্থাপিত হলে, আপনার অ্যাপ ইভেন্টের উপযুক্ত সেট পায় যাতে ক্লায়েন্ট বর্তমান সার্ভারের অবস্থার সাথে সিঙ্ক করে, কোনো কাস্টম কোড না লিখেই।

আমরা অনলাইন এবং অফলাইন ক্ষমতা সম্পর্কে আরও জানুন .. এ অফলাইন আচরণ সম্পর্কে আরও কথা বলব।

পরবর্তী পদক্ষেপ