Android-এ ডেটা পড়া ও লেখা

এই ডকুমেন্টে Firebase ডেটা পড়া ও লেখার প্রাথমিক বিষয়গুলি কভার করা হয়েছে।

Firebase ডেটা FirebaseDatabase রেফারেন্সে লেখা হয় এবং রেফারেন্সে অ্যাসিঙ্ক্রোনাস লিসেনার অ্যাটাচ করে তা ফিরিয়ে আনা হয়। ডেটার প্রাথমিক অবস্থার জন্য একবার এবং ডেটা পরিবর্তন হলে আবার যেকোনও সময় লিসেনার ট্রিগার করা হয়।

(ঐচ্ছিক) Firebase Local Emulator Suite-এর সাথে প্রোটোটাইপ ও টেস্ট করুন

আপনার অ্যাপ কীভাবে Realtime Database থেকে ডেটা পড়ে এবং তাতে ডেটা লেখে সেই বিষয়ে কথা বলার আগে, আসুন এমন কিছু টুলের সাথে পরিচিত হই যেগুলি আপনি Realtime Database-এর কার্যকারিতা প্রোটোটাইপ এবং পরীক্ষা করার জন্য ব্যবহার করতে পারেন: Firebase Local Emulator Suite। আপনি যদি বিভিন্ন ডেটা মডেল ব্যবহার করে দেখেন, আপনার নিরাপত্তা সংক্রান্ত নিয়ম অপ্টিমাইজ করেন অথবা ব্যাক-এন্ডের সাথে ইন্টার‍্যাক্ট করার সবচেয়ে সাশ্রয়ী উপায় খুঁজে বের করার চেষ্টা করেন, তাহলে লাইভ পরিষেবা ডিপ্লয় না করে লোকালভাবে কাজ করতে পারাটা একটা দারুণ আইডিয়া হতে পারে।

Realtime Database এমুলেটর হল Local Emulator Suite-এর অংশ, যা আপনার অ্যাপকে আপনার এমুলেট করা ডেটাবেস কন্টেন্ট এবং কনফিগারেশনের সাথে ইন্টার‍্যাক্ট করতে দেয়, পাশাপাশি আপনার এমুলেট করা প্রোজেক্ট রিসোর্স (ফাংশন, অন্যান্য ডেটাবেস এবং নিরাপত্তা সংক্রান্ত নিয়ম) ঐচ্ছিকভাবে ব্যবহার করতে দেয়।

Realtime Database এমুলেটর ব্যবহার করার জন্য মাত্র কয়েকটি ধাপ অনুসরণ করতে হয়:

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

Realtime Database ও Cloud Functions-এর বিস্তারিত ওয়াকথ্রু উপলভ্য। এছাড়াও, আপনাকে Local Emulator Suite ভূমিকা দেখতে হবে।

DatabaseReference পান

ডেটাবেস থেকে ডেটা পড়তে বা লিখতে, আপনার DatabaseReference-এর ইনস্ট্যান্স প্রয়োজন:

Kotlin

private lateinit var database: DatabaseReference
// ...
database = Firebase.database.reference

Java

private DatabaseReference mDatabase;
// ...
mDatabase = FirebaseDatabase.getInstance().getReference();

ডেটা এডিট করা

বেসিক রাইট অপারেশন

বেসিক রাইট অপারেশনের জন্য, আপনি setValue() ব্যবহার করে নির্দিষ্ট রেফারেন্সে ডেটা সেভ করতে পারেন, যা সেই পাথে আগে থেকে থাকা যেকোনও ডেটা রিপ্লেস করে। আপনি এই পদ্ধতি ব্যবহার করে:

  • পাসের ধরন যা উপলভ্য JSON ধরনের সাথে মিলে যায়, যেমন:
    • String
    • Long
    • Double
    • Boolean
    • Map<String, Object>
    • List<Object>
  • একটি কাস্টম জাভা অবজেক্ট পাস করুন, যদি ক্লাসটি এটিকে সংজ্ঞায়িত করে তবে একটি ডিফল্ট আছে কনস্ট্রাক্টর যা কোন আর্গুমেন্ট নেয় না এবং প্রপার্টির জন্য পাবলিক গেটার আছে অ্যাসাইন করা হবে।

আপনি Java অবজেক্ট ব্যবহার করলে, আপনার অবজেক্টের কন্টেন্ট অটোমেটিক নেস্টেড পদ্ধতিতে চাইল্ড লোকেশনে ম্যাপ করা হয়। এছাড়াও, Java অবজেক্ট ব্যবহার করলে সাধারণত আপনার কোড আরও সহজে পড়া ও ম্যানেজ করা যায়। যেমন, আপনার যদি কোনও অ্যাপে ব্যবহারকারীর প্রাথমিক প্রোফাইল থাকে, তাহলে আপনার User অবজেক্টটি নিম্নলিখিতভাবে দেখানো হতে পারে:

Kotlin

@IgnoreExtraProperties
data class User(val username: String? = null, val email: String? = null) {
    // Null default values create a no-argument default constructor, which is needed
    // for deserialization from a DataSnapshot.
}

Java

@IgnoreExtraProperties
public class User {

    public String username;
    public String email;

    public User() {
        // Default constructor required for calls to DataSnapshot.getValue(User.class)
    }

    public User(String username, String email) {
        this.username = username;
        this.email = email;
    }

}

আপনি setValue()-এর মাধ্যমে এইভাবে কোনও ব্যবহারকারীকে যোগ করতে পারবেন:

Kotlin

fun writeNewUser(userId: String, name: String, email: String) {
    val user = User(name, email)

    database.child("users").child(userId).setValue(user)
}

Java

public void writeNewUser(String userId, String name, String email) {
    User user = new User(name, email);

    mDatabase.child("users").child(userId).setValue(user);
}

এইভাবে setValue() ব্যবহার করলে নির্দিষ্ট লোকেশনে ডেটা ওভাররাইট হয়ে যায়, যার মধ্যে যেকোনও চাইল্ড নোডও অন্তর্ভুক্ত। তবে, আপনি এখনও সম্পূর্ণ অবজেক্ট আবার না লিখে চাইল্ড আপডেট করতে পারবেন। আপনি যদি ব্যবহারকারীদের তাদের প্রোফাইল আপডেট করার অনুমতি দিতে চান তাহলে আপনি নিম্নলিখিত উপায়ে ইউজারনেম আপডেট করতে পারেন:

Kotlin

database.child("users").child(userId).child("username").setValue(name)

Java

mDatabase.child("users").child(userId).child("username").setValue(name);

ডেটা দেখা

পারসিস্টেন্ট লিসনারের মাধ্যমে ডেটা পড়া

কোনও পাথে ডেটা পড়তে এবং পরিবর্তনের জন্য শুনতে, DatabaseReference-এ ValueEventListener যোগ করতে addValueEventListener() পদ্ধতি ব্যবহার করুন।

শ্রোতা ইভেন্ট কলব্যাক সাধারণ ব্যবহার
ValueEventListener onDataChange() কোনও পাথের সম্পূর্ণ কন্টেন্টে পরিবর্তন করা হলে তা পড়ে ও শোনে।

ইভেন্ট চলাকালীন নির্দিষ্ট পাথে কন্টেন্টের স্ট্যাটিক স্ন্যাপশট পড়তে আপনি onDataChange() পদ্ধতি ব্যবহার করতে পারেন। এই পদ্ধতি একবার ট্রিগার করা হয় যখন লিসেনার অ্যাটাচ করা হয় এবং আবার প্রতিবার ডেটা, চিলড্রেন সহ, পরিবর্তন হয়। ইভেন্ট কলব্যাক একটি স্ন্যাপশট পাস করে যাতে চাইল্ড ডেটা সহ সেই লোকেশনের সমস্ত ডেটা থাকে। কোনও ডেটা না থাকলে, আপনি exists() কল করলে স্ন্যাপশট false রিটার্ন করবে এবং আপনি getValue() কল করলে null রিটার্ন করবে।

নিম্নলিখিত উদাহরণে দেখানো হয়েছে যে কীভাবে একটি সোশ্যাল ব্লগিং অ্যাপ্লিকেশন ডেটাবেস থেকে একটি পোস্টের বিবরণ খুঁজে বের করে:

Kotlin

val postListener = object : ValueEventListener {
    override fun onDataChange(dataSnapshot: DataSnapshot) {
        // Get Post object and use the values to update the UI
        val post = dataSnapshot.getValue<Post>()
        // ...
    }

    override fun onCancelled(databaseError: DatabaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException())
    }
}
postReference.addValueEventListener(postListener)

Java

ValueEventListener postListener = new ValueEventListener() {
    @Override
    public void onDataChange(DataSnapshot dataSnapshot) {
        // Get Post object and use the values to update the UI
        Post post = dataSnapshot.getValue(Post.class);
        // ..
    }

    @Override
    public void onCancelled(DatabaseError databaseError) {
        // Getting Post failed, log a message
        Log.w(TAG, "loadPost:onCancelled", databaseError.toException());
    }
};
mPostReference.addValueEventListener(postListener);

ইভেন্টের সময় ডেটাবেসের নির্দিষ্ট লোকেশনে ডেটা সহ একটি DataSnapshot লিসেনার পায়। স্ন্যাপশটে getValue() কল করলে ডেটার জাভা অবজেক্ট রিপ্রেজেন্টেশন রিটার্ন করে। লোকেশনটিতে কোনও ডেটা না থাকলে, getValue() কল করলে null রিটার্ন করে।

এই উদাহরণে, ValueEventListener onCancelled() পদ্ধতিও সংজ্ঞায়িত করে যা পড়া বাতিল করা হলে কল করা হয়। যেমন, ক্লায়েন্টের কাছে Firebase ডেটাবেস লোকেশন থেকে ডেটা রিড করার অনুমতি না থাকলে, রিড করার অনুরোধ বাতিল করা হতে পারে। এই পদ্ধতিটি একটি DatabaseError অবজেক্ট পাস করে যা নির্দেশ করে যে কেন ব্যর্থতা ঘটেছে।

একবার ডেটা দেখা

get() ব্যবহার করে একবার পড়ুন

আপনার অ্যাপ অনলাইন বা অফলাইন যাই হোক না কেন, ডেটাবেস সার্ভারের সাথে ইন্টার‍্যাকশন ম্যানেজ করার জন্য SDK ডিজাইন করা হয়েছে।

সাধারণত, ব্যাকএন্ড থেকে ডেটার আপডেট সম্পর্কে বিজ্ঞপ্তি পেতে, ডেটা পড়ার জন্য আপনাকে উপরে বর্ণিত ValueEventListener টেকনিক ব্যবহার করতে হবে। লিসেনার টেকনিক আপনার ব্যবহার ও বিলিং কমায় এবং ব্যবহারকারীরা অনলাইন ও অফলাইন হলে তাদের সেরা অভিজ্ঞতা দেওয়ার জন্য অপ্টিমাইজ করা হয়।

আপনার যদি ডেটা শুধুমাত্র একবার প্রয়োজন হয়, তাহলে আপনি get() ব্যবহার করে ডেটাবেস থেকে ডেটার স্ন্যাপশট পেতে পারেন। কোনও কারণে get() সার্ভার ভ্যালু ফেরত দিতে না পারলে, ক্লায়েন্ট লোকাল স্টোরেজ ক্যাশে পরীক্ষা করবে এবং ভ্যালু এখনও খুঁজে না পেলে একটি সমস্যা দেখাবে।

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

Kotlin

mDatabase.child("users").child(userId).get().addOnSuccessListener {
    Log.i("firebase", "Got value ${it.value}")
}.addOnFailureListener{
    Log.e("firebase", "Error getting data", it)
}

Java

mDatabase.child("users").child(userId).get().addOnCompleteListener(new OnCompleteListener<DataSnapshot>() {
    @Override
    public void onComplete(@NonNull Task<DataSnapshot> task) {
        if (!task.isSuccessful()) {
            Log.e("firebase", "Error getting data", task.getException());
        }
        else {
            Log.d("firebase", String.valueOf(task.getResult().getValue()));
        }
    }
});

লিসেনার ব্যবহার করে একবার পড়ুন

কিছু ক্ষেত্রে, আপনি হয়ত সার্ভারে আপডেট করা ভ্যালু চেক করার পরিবর্তে, লোকাল ক্যাশে থেকে ভ্যালু অবিলম্বে ফেরত পেতে চাইবেন। সেইসব ক্ষেত্রে, আপনি addListenerForSingleValueEvent ব্যবহার করে লোকাল ডিস্ক ক্যাশে থেকে অবিলম্বে ডেটা পেতে পারেন।

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

ডেটা আপডেট করা বা মুছে ফেলা

নির্দিষ্ট ফিল্ড আপডেট করুন

অন্য চাইল্ড নোড ওভাররাইট না করে কোনও নোডের নির্দিষ্ট চাইল্ড নোডে একসাথে লিখতে, updateChildren() পদ্ধতি ব্যবহার করুন।

updateChildren() কল করার সময়, আপনি 'কী'-এর জন্য পাথ উল্লেখ করে নিম্ন-স্তরের চাইল্ড ভ্যালু আপডেট করতে পারবেন। আরও ভালভাবে স্কেল করার জন্য একাধিক লোকেশনে ডেটা স্টোর করা থাকলে, ডেটা ফ্যান-আউট ব্যবহার করে আপনি সেই ডেটার সব ইনস্ট্যান্স আপডেট করতে পারবেন। যেমন, একটি সোশ্যাল ব্লগিং অ্যাপে এইরকম একটি Post ক্লাস থাকতে পারে:

Kotlin

@IgnoreExtraProperties
data class Post(
    var uid: String? = "",
    var author: String? = "",
    var title: String? = "",
    var body: String? = "",
    var starCount: Int = 0,
    var stars: MutableMap<String, Boolean> = HashMap(),
) {

    @Exclude
    fun toMap(): Map<String, Any?> {
        return mapOf(
            "uid" to uid,
            "author" to author,
            "title" to title,
            "body" to body,
            "starCount" to starCount,
            "stars" to stars,
        )
    }
}

Java

@IgnoreExtraProperties
public class Post {

    public String uid;
    public String author;
    public String title;
    public String body;
    public int starCount = 0;
    public Map<String, Boolean> stars = new HashMap<>();

    public Post() {
        // Default constructor required for calls to DataSnapshot.getValue(Post.class)
    }

    public Post(String uid, String author, String title, String body) {
        this.uid = uid;
        this.author = author;
        this.title = title;
        this.body = body;
    }

    @Exclude
    public Map<String, Object> toMap() {
        HashMap<String, Object> result = new HashMap<>();
        result.put("uid", uid);
        result.put("author", author);
        result.put("title", title);
        result.put("body", body);
        result.put("starCount", starCount);
        result.put("stars", stars);

        return result;
    }
}

একটি পোস্ট তৈরি করতে এবং একই সাথে সাম্প্রতিক অ্যাক্টিভিটি ফিড এবং পোস্টিং ব্যবহারকারীর অ্যাক্টিভিটি ফিডে এটি আপডেট করতে, ব্লগিং অ্যাপ্লিকেশনটি এই ধরনের কোড ব্যবহার করে:

Kotlin

private fun writeNewPost(userId: String, username: String, title: String, body: String) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    val key = database.child("posts").push().key
    if (key == null) {
        Log.w(TAG, "Couldn't get push key for posts")
        return
    }

    val post = Post(userId, username, title, body)
    val postValues = post.toMap()

    val childUpdates = hashMapOf<String, Any>(
        "/posts/$key" to postValues,
        "/user-posts/$userId/$key" to postValues,
    )

    database.updateChildren(childUpdates)
}

Java

private void writeNewPost(String userId, String username, String title, String body) {
    // Create new post at /user-posts/$userid/$postid and at
    // /posts/$postid simultaneously
    String key = mDatabase.child("posts").push().getKey();
    Post post = new Post(userId, username, title, body);
    Map<String, Object> postValues = post.toMap();

    Map<String, Object> childUpdates = new HashMap<>();
    childUpdates.put("/posts/" + key, postValues);
    childUpdates.put("/user-posts/" + userId + "/" + key, postValues);

    mDatabase.updateChildren(childUpdates);
}

এই উদাহরণে push() ব্যবহার করে নোডে পোস্ট তৈরি করা হয় যেখানে /posts/$postid-এ সব ব্যবহারকারীর জন্য পোস্ট থাকে এবং একই সাথে getKey()-এর মাধ্যমে কী ফিরিয়ে আনা হয়। তারপর, /user-posts/$userid/$postid-এ ব্যবহারকারীর পোস্টে দ্বিতীয় এন্ট্রি তৈরি করতে 'কী' ব্যবহার করা যেতে পারে।

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

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

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

Kotlin

database.child("users").child(userId).setValue(user)
    .addOnSuccessListener {
        // Write was successful!
        // ...
    }
    .addOnFailureListener {
        // Write failed
        // ...
    }

Java

mDatabase.child("users").child(userId).setValue(user)
        .addOnSuccessListener(new OnSuccessListener<Void>() {
            @Override
            public void onSuccess(Void aVoid) {
                // Write was successful!
                // ...
            }
        })
        .addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
                // Write failed
                // ...
            }
        });

ডেটা মুছে দিন

ডেটা মুছে ফেলার সবচেয়ে সহজ উপায় হল, সেই ডেটার লোকেশনের রেফারেন্সে removeValue() কল করা।

এছাড়াও, আপনি setValue() বা updateChildren()-এর মতো অন্য কোনও রাইট অপারেশনের জন্য null-কে ভ্যালু হিসেবে নির্দিষ্ট করে মুছে দিতে পারেন। আপনি এই টেকনিক ব্যবহার করে updateChildren()-এর মাধ্যমে একটি API কলের সাহায্যে একাধিক বাচ্চার তথ্য মুছে ফেলতে পারবেন।

লিসনার ডিটাচ করুন

আপনার Firebase ডেটাবেস রেফারেন্সে removeEventListener() মেথড কল করার মাধ্যমে কলব্যাক সরিয়ে দেওয়া হয়।

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

প্যারেন্ট লিসেনারে removeEventListener() কল করলে তার চাইল্ড নোডে রেজিস্টার করা লিসেনার অটোমেটিক সরিয়ে দেয় না; কলব্যাক সরাতে যেকোনও চাইল্ড লিসেনারেও removeEventListener() কল করতে হবে।

ট্রানজ্যাকশন হিসেবে ডেটা সেভ করুন

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

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

Kotlin

private fun onStarClicked(postRef: DatabaseReference) {
    // ...
    postRef.runTransaction(object : Transaction.Handler {
        override fun doTransaction(mutableData: MutableData): Transaction.Result {
            val p = mutableData.getValue(Post::class.java)
                ?: return Transaction.success(mutableData)

            if (p.stars.containsKey(uid)) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1
                p.stars.remove(uid)
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1
                p.stars[uid] = true
            }

            // Set value and report transaction success
            mutableData.value = p
            return Transaction.success(mutableData)
        }

        override fun onComplete(
            databaseError: DatabaseError?,
            committed: Boolean,
            currentData: DataSnapshot?,
        ) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError!!)
        }
    })
}

Java

private void onStarClicked(DatabaseReference postRef) {
    postRef.runTransaction(new Transaction.Handler() {
        @NonNull
        @Override
        public Transaction.Result doTransaction(@NonNull MutableData mutableData) {
            Post p = mutableData.getValue(Post.class);
            if (p == null) {
                return Transaction.success(mutableData);
            }

            if (p.stars.containsKey(getUid())) {
                // Unstar the post and remove self from stars
                p.starCount = p.starCount - 1;
                p.stars.remove(getUid());
            } else {
                // Star the post and add self to stars
                p.starCount = p.starCount + 1;
                p.stars.put(getUid(), true);
            }

            // Set value and report transaction success
            mutableData.setValue(p);
            return Transaction.success(mutableData);
        }

        @Override
        public void onComplete(DatabaseError databaseError, boolean committed,
                               DataSnapshot currentData) {
            // Transaction completed
            Log.d(TAG, "postTransaction:onComplete:" + databaseError);
        }
    });
}

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

সার্ভার-সাইড ইনক্রিমেন্ট

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

Kotlin

private fun onStarClicked(uid: String, key: String) {
    val updates: MutableMap<String, Any> = hashMapOf(
        "posts/$key/stars/$uid" to true,
        "posts/$key/starCount" to ServerValue.increment(1),
        "user-posts/$uid/$key/stars/$uid" to true,
        "user-posts/$uid/$key/starCount" to ServerValue.increment(1),
    )
    database.updateChildren(updates)
}

Java

private void onStarClicked(String uid, String key) {
    Map<String, Object> updates = new HashMap<>();
    updates.put("posts/"+key+"/stars/"+uid, true);
    updates.put("posts/"+key+"/starCount", ServerValue.increment(1));
    updates.put("user-posts/"+uid+"/"+key+"/stars/"+uid, true);
    updates.put("user-posts/"+uid+"/"+key+"/starCount", ServerValue.increment(1));
    mDatabase.updateChildren(updates);
}

এই কোড কোনও ট্রানজ্যাকশন অপারেশন ব্যবহার করে না, তাই কোনও কনফ্লিক্টিং আপডেট থাকলে এটি অটোমেটিক আবার রান করে না। তবে, যেহেতু ইনক্রিমেন্ট অপারেশন সরাসরি ডেটাবেস সার্ভারে ঘটে, তাই কোনও বিরোধের সম্ভাবনা নেই।

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

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

কোনও ক্লায়েন্ট নেটওয়ার্ক কানেকশন হারিয়ে ফেললে, আপনার অ্যাপ সঠিকভাবে কাজ করা চালিয়ে যাবে।

Firebase ডেটাবেসের সাথে কানেক্ট করা প্রতিটি ক্লায়েন্ট, যেকোনও ডেটার নিজস্ব ইন্টার্নাল ভার্সন মেইনটেইন করে, যার উপর লিসেনার ব্যবহার করা হচ্ছে অথবা সার্ভারের সাথে সিঙ্ক করে রাখার জন্য ফ্ল্যাগ করা হয়েছে। ডেটা পড়া বা লেখার সময়, ডেটার এই স্থানীয় ভার্সনটি প্রথমে ব্যবহার করা হয়। Firebase ক্লায়েন্ট তখন সেই ডেটা রিমোট ডেটাবেস সার্ভার এবং অন্যান্য ক্লায়েন্টের সাথে "সর্বোচ্চ-চেষ্টা"র ভিত্তিতে সিঙ্ক করে।

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

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

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

পরবর্তী ধাপ