RememberObserver ও RetainObserver সহ কম্পোজ স্টেট কলব্যাক

Jetpack Compose-এ, কোনও অবজেক্ট RememberObserver প্রয়োগ করে কলব্যাক পেতে পারে, এটি remember-এর সাথে ব্যবহার করা হলে, কম্পোজিশন হায়ারার্কিতে এটি কখন থেকে মনে রাখা শুরু হয় এবং কখন বন্ধ হয় তা জানা যায়। একইভাবে, retain-এর সাথে ব্যবহৃত কোনও অবজেক্টের অবস্থা সম্পর্কে তথ্য পেতে আপনি RetainObserver ব্যবহার করতে পারেন।

কম্পোজিশন হায়ারার্কি থেকে এই লাইফসাইকেল তথ্য ব্যবহার করে এমন অবজেক্টের জন্য, আমরা কিছু পেশাদার পদ্ধতি সাজেস্ট করি যাতে আপনার অবজেক্ট প্ল্যাটফর্মে ভাল নাগরিক হিসেবে কাজ করে এবং অপব্যবহারের বিরুদ্ধে প্রতিরোধ গড়ে তোলে তা যাচাই করা যায়। বিশেষ করে, কাজ শুরু করার জন্য কনস্ট্রাক্টরের পরিবর্তে onRemembered (বা onRetained) কলব্যাক ব্যবহার করুন, অবজেক্ট আর মনে রাখা বা ধরে রাখা না হলে সব কাজ বাতিল করুন এবং দুর্ঘটনাবশত কল এড়াতে RememberObserver ও RetainObserver-এর ইমপ্লিমেন্টেশন লিক করা এড়িয়ে চলুন। পরের বিভাগে এইসব সাজেশন আরও বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে।

RememberObserver ও RetainObserver-এর মাধ্যমে ইনিশিয়ালাইজেশন ও ক্লিন-আপ

Compose-এ চিন্তাভাবনা গাইড কম্পোজিশনের পেছনের মানসিক মডেলের বর্ণনা দেয়। RememberObserver ও RetainObserver-এর সাথে কাজ করার সময়, কম্পোজিশনের দুটি আচরণ মনে রাখা গুরুত্বপূর্ণ:

  • আবার কম্পোজ করা আশাবাদী এবং বাতিল করা হতে পারে
  • সব কম্পোজ করার উপযুক্ত ফাংশনের কোনও সাইড-ইফেক্ট থাকা উচিত নয়

onRemembered বা onRetained চলাকালীন ইনিশিয়ালাইজেশন সাইড এফেক্ট রান করে, কনস্ট্রাকশন চলাকালীন নয়

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

এর পরিবর্তে, RememberObserver বা RetainObserver প্রয়োগ করার সময়, যাচাই করে নিন যে সব এফেক্ট ও লঞ্চ করা জব onRemembered কলব্যাকে ডিসপ্যাচ করা হয়েছে। এটি SideEffect API-এর মতো একই টাইমিং অফার করে। এছাড়াও, এটি গ্যারান্টি দেয় যে কম্পোজিশন প্রয়োগ করা হলেই শুধুমাত্র এইসব এফেক্ট এক্সিকিউট করা হবে, যা রিম্যাপিং বাতিল বা স্থগিত করা হলে অসম্পূর্ণ জব ও মেমরি লিকেজ প্রতিরোধ করে।

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    init {
        // Not recommended: This will cause work to begin during composition instead of
        // with other effects. Move this into onRemembered().
        coroutineScope.launch { loadData() }
    }

    override fun onRemembered() {
        // Recommended: Move any cancellable or effect-driven work into the onRemembered
        // callback. If implementing RetainObserver, this should go in onRetained.
        coroutineScope.launch { loadData() }
    }

    private suspend fun loadData() { /* ... */ }

    // ...
}

ভুলে গেলে, মেয়াদ শেষ হয়ে গেলে বা ব্যবহার না করলে

রিসোর্স লিক হওয়া বা ব্যাকগ্রাউন্ড জব অনাথ হয়ে যাওয়া এড়াতে, মনে রাখা অবজেক্টও ডিসপোজ করতে হবে। RememberObserver ইমপ্লিমেন্ট করা অবজেক্টের ক্ষেত্রে, এর অর্থ হল onRemembered-এ ইনিশিয়ালাইজ করা যেকোনও কিছুর সাথে onForgotten-এ ম্যাচ করা রিলিজ কল থাকতে হবে।

কম্পোজিশন বাতিল করা যেতে পারে বলে, কম্পোজিশনে RememberObserver ব্যবহার করা অবজেক্টগুলিকে কম্পোজিশন থেকে সরিয়ে দেওয়া হলে সেগুলিকে নিজেদের পরিষ্কার করে নিতে হবে। কোনও কম্পোজিশন বাতিল বা ব্যর্থ হলে, remember সেটি ফেরত দিলে কোনও অবজেক্ট পরিত্যক্ত হিসেবে বিবেচিত হয়। (এটি সাধারণত PausableComposition ব্যবহার করার সময় হয় এবং Android Studio-এর কম্পোজ করার উপযুক্ত প্রিভিউ টুলিংয়ের সাথে হট রিলোড ব্যবহার করার সময়ও হতে পারে।)

মনে রাখা কোনও অবজেক্ট বাতিল করা হলে, সেটি শুধুমাত্র onAbandoned-এ কল পায় (এবং onRemembered-এ কোনও কল পায় না)। বাতিল করার পদ্ধতি প্রয়োগ করতে, অবজেক্ট ইনিশিয়ালাইজ করা এবং অবজেক্ট onRemembered কলব্যাক পাওয়ার মধ্যবর্তী সময়ে তৈরি করা যেকোনও কিছু ডিসপোজ করুন।

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    // ...

    override fun onForgotten() {
        // Cancel work launched from onRemembered. If implementing RetainObserver, onRetired
        // should cancel work launched from onRetained.
        job.cancel()
    }

    override fun onAbandoned() {
        // If any work was launched by the constructor as part of remembering the object,
        // you must cancel that work in this callback. For work done as part of the construction
        // during retain, this code should will appear in onUnused.
        job.cancel()
    }
}

RememberObserver ও RetainObserver প্রয়োগ ব্যক্তিগত রাখুন

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

এগুলি সাজেস্ট করা হয় না:

abstract class MyManager

// Not Recommended: Exposing a public constructor (even implicitly) for an object implementing
// RememberObserver can cause unexpected invocations if it is remembered multiple times.
class MyComposeManager : MyManager(), RememberObserver { ... }

// Not Recommended: The return type may be an implementation of RememberObserver and should be
// remembered explicitly.
fun createFoo(): MyManager = MyComposeManager()

প্রস্তাবিত:

abstract class MyManager

class MyComposeManager : MyManager() {
    // Callers that construct this object must manually call initialize and teardown
    fun initialize() { /*...*/ }
    fun teardown() { /*...*/ }
}

@Composable
fun rememberMyManager(): MyManager {
    // Protect the RememberObserver implementation by never exposing it outside the library
    return remember {
        object : RememberObserver {
            val manager = MyComposeManager()
            override fun onRemembered() = manager.initialize()
            override fun onForgotten() = manager.teardown()
            override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ }
        }
    }.manager
}

অবজেক্ট মনে রাখার সময় যেসব বিষয় বিবেচনা করতে হবে

RememberObserver ও RetainObserver সংক্রান্ত আগের সাজেশন ছাড়াও, আমরা সাজেস্ট করি যে পারফর্ম্যান্স ও সঠিকতার জন্য দুর্ঘটনাবশত অবজেক্ট আবার মনে করা এড়িয়ে চলুন। নিম্নলিখিত বিভাগগুলি নির্দিষ্ট 'আবার মনে করা' পরিস্থিতি এবং কেন সেগুলি এড়ানো উচিত সেই সম্পর্কে আরও বিশদে আলোচনা করে।

অবজেক্ট একবারই মনে রাখা

কোনও অবজেক্টকে আবার মনে করা বিপজ্জনক হতে পারে। সবচেয়ে ভাল পরিস্থিতিতে, আপনি হয়ত আগে থেকেই মনে রাখা একটি ভ্যালু মনে রাখার জন্য রিসোর্স নষ্ট করছেন। কিন্তু কোনও অবজেক্ট RememberObserver প্রয়োগ করলে এবং সেটি অপ্রত্যাশিতভাবে দু'বার মনে রাখলে, সেটি প্রত্যাশিত কলব্যাকের চেয়ে বেশি কলব্যাক পাবে। এর ফলে সমস্যা হতে পারে, কারণ onRemembered ও onForgotten লজিক দু'বার এক্সিকিউট হবে এবং RememberObserver-এর বেশিরভাগ ইমপ্লিমেন্টেশনে এটি কাজ করে না। যদি দ্বিতীয় remember কলটি অন্য স্কোপে হয়, যার মেয়াদ মূল remember-এর থেকে আলাদা হয়, তাহলে RememberObserver.onForgotten-এর অনেক ইমপ্লিমেন্টেশন ব্যবহার শেষ হওয়ার আগেই অবজেক্টটি ডিসপোজ করে দেয়।

val first: RememberObserver = rememberFoo()

// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }

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

val foo: Foo = rememberFoo()

// Acceptable:
val bar: Bar = remember { Bar(foo) }

// Recommended key usage:
val barWithKey: Bar = remember(foo) { Bar(foo) }

ফাংশন আর্গুমেন্ট আগে থেকেই মনে রাখা আছে বলে ধরে নেওয়া

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

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Not Recommended: Input should be remembered by the caller.
    val rememberedParameter = remember { parameter }
}

ট্রানজিটিভভাবে মনে রাখা অবজেক্টের ক্ষেত্রে এটি প্রযোজ্য হয় না। ফাংশনের আর্গুমেন্ট থেকে প্রাপ্ত কোনও অবজেক্ট মনে রাখার সময়, remember-এর কী হিসেবে সেটি নির্দিষ্ট করার কথা বিবেচনা করুন:

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Acceptable:
    val derivedValue = remember { Bar(parameter) }

    // Also Acceptable:
    val derivedValueWithKey = remember(parameter) { Bar(parameter) }
}

আগে থেকেই মনে রাখা হয়েছে এমন কোনও অবজেক্ট রেখে দেবেন না

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