কম্পোজেবলের জীবনচক্র

এই পৃষ্ঠায়, আপনি একটি কম্পোজেবলের জীবনচক্র এবং কম্পোজ কীভাবে নির্ধারণ করে যে একটি কম্পোজেবলের রিকম্পোজিশন প্রয়োজন কিনা, সে সম্পর্কে জানতে পারবেন।

জীবনচক্রের সংক্ষিপ্ত বিবরণ

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

Jetpack Compose যখন প্রথমবার আপনার কম্পোজেবলগুলো রান করে, অর্থাৎ ইনিশিয়াল কম্পোজিশনের সময়, তখন এটি একটি কম্পোজিশনে আপনার UI বর্ণনা করার জন্য ব্যবহৃত কম্পোজেবলগুলোর হিসাব রাখে। এরপর, যখন আপনার অ্যাপের স্টেট পরিবর্তিত হয়, Jetpack Compose একটি রিকম্পোজিশন শিডিউল করে। রিকম্পোজিশন হলো এমন একটি প্রক্রিয়া যেখানে Jetpack Compose স্টেট পরিবর্তনের ফলে পরিবর্তিত হতে পারে এমন কম্পোজেবলগুলোকে পুনরায় এক্সিকিউট করে এবং তারপর যেকোনো পরিবর্তন প্রতিফলিত করার জন্য কম্পোজিশনটি আপডেট করে।

একটি কম্পোজিশন শুধুমাত্র একটি প্রাথমিক কম্পোজিশন দ্বারা তৈরি করা যায় এবং রিকম্পোজিশনের মাধ্যমে হালনাগাদ করা যায়। একটি কম্পোজিশন পরিবর্তন করার একমাত্র উপায় হলো রিকম্পোজিশন।

একটি কম্পোজেবলের জীবনচক্র দেখানো ডায়াগ্রাম
চিত্র ১। কম্পোজিশনের মধ্যে একটি কম্পোজেবলের জীবনচক্র। এটি কম্পোজিশনে প্রবেশ করে, ০ বা তার বেশি বার পুনর্গঠিত হয় এবং কম্পোজিশন থেকে বেরিয়ে যায়।

সাধারণত একটি State<T> অবজেক্টের পরিবর্তনের ফলে রিকম্পোজিশন শুরু হয়। কম্পোজ এই পরিবর্তনগুলো ট্র্যাক করে এবং কম্পোজিশনের মধ্যে থাকা সেই নির্দিষ্ট State<T> থেকে ডেটা গ্রহণকারী সমস্ত কম্পোজেবল এবং সেই অবজেক্ট দ্বারা কল করা এমন যেকোনো কম্পোজেবল যা স্কিপ করা যায় না, সেগুলোকে রান করে।

যদি কোনো কম্পোজেবল একাধিকবার কল করা হয়, তাহলে কম্পোজিশনটিতে একাধিক ইনস্ট্যান্স তৈরি হয়। কম্পোজিশনের মধ্যে প্রতিটি কলের নিজস্ব জীবনচক্র থাকে।

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

পূর্ববর্তী কোড স্নিপেটে উপাদানগুলির শ্রেণিবিন্যাসগত বিন্যাস দেখানো ডায়াগ্রাম।
চিত্র ২. কম্পোজিশনে MyComposable এর উপস্থাপনা। যদি একটি কম্পোজেবল একাধিকবার কল করা হয়, তবে কম্পোজিশনে এর একাধিক ইনস্ট্যান্স রাখা হয়। কোনো এলিমেন্টের রঙ ভিন্ন হলে তা একটি পৃথক ইনস্ট্যান্স নির্দেশ করে।

কম্পোজিশনে একটি কম্পোজেবলের গঠন

কম্পোজিশনে একটি কম্পোজেবলের ইনস্ট্যান্স তার কল সাইট দ্বারা চিহ্নিত করা হয়। কম্পোজ কম্পাইলার প্রতিটি কল সাইটকে স্বতন্ত্র হিসেবে বিবেচনা করে। একাধিক কল সাইট থেকে কম্পোজেবল কল করলে কম্পোজিশনে সেই কম্পোজেবলের একাধিক ইনস্ট্যান্স তৈরি হবে।

যদি কোনো রিকম্পোজিশনের সময় একটি কম্পোজেবল পূর্ববর্তী কম্পোজিশনের চেয়ে ভিন্ন কম্পোজেবলকে কল করে, তাহলে Compose শনাক্ত করবে কোন কম্পোজেবলগুলো কল করা হয়েছে বা হয়নি এবং যে কম্পোজেবলগুলো উভয় কম্পোজিশনেই কল করা হয়েছিল, সেগুলোর ইনপুট অপরিবর্তিত থাকলে Compose সেগুলোকে রিকম্পোজ করা থেকে বিরত থাকবে।

পরিচয় রক্ষা করা অত্যন্ত গুরুত্বপূর্ণ, যাতে পার্শ্ব প্রতিক্রিয়াগুলোকে তাদের গঠনযোগ্যতার সাথে যুক্ত করা যায়, ফলে প্রতিটি পুনর্গঠনের জন্য পুনরায় শুরু না করে সেগুলোকে সফলভাবে সম্পন্ন করা যায়।

নিম্নলিখিত উদাহরণটি বিবেচনা করুন:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

উপরের কোড স্নিপেটে, LoginScreen শর্তসাপেক্ষে LoginError কম্পোজেবলকে কল করবে এবং সর্বদা LoginInput কম্পোজেবলকে কল করবে। প্রতিটি কলের একটি অনন্য কল সাইট এবং সোর্স পজিশন থাকে, যা কম্পাইলার এটিকে স্বতন্ত্রভাবে শনাক্ত করতে ব্যবহার করবে।

ডায়াগ্রামটিতে দেখানো হয়েছে যে, showError ফ্ল্যাগটি true-তে পরিবর্তন করা হলে পূর্ববর্তী কোডটি কীভাবে পুনর্গঠিত হয়। LoginError কম্পোজেবলটি যুক্ত করা হয়, কিন্তু অন্য কম্পোজেবলগুলো পুনর্গঠিত হয় না।
চিত্র ৩। অবস্থার পরিবর্তন এবং পুনর্গঠন ঘটলে কম্পোজিশনে LoginScreen উপস্থাপনা। একই রঙ মানে এটি পুনর্গঠিত হয়নি।

যদিও LoginInput প্রথমে কল করা থেকে দ্বিতীয় স্থানে কল করা হয়েছে, তবুও পুনর্গঠনের পরেও LoginInput ইনস্ট্যান্সটি সংরক্ষিত থাকবে। এছাড়াও, যেহেতু পুনর্গঠনের ফলে LoginInput কোনো প্যারামিটার পরিবর্তিত হয়নি, তাই Compose দ্বারা LoginInput এর কলটি এড়িয়ে যাওয়া হবে।

স্মার্ট পুনর্গঠনে সহায়তার জন্য অতিরিক্ত তথ্য যোগ করুন।

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

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

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

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

তবে, যদি movies তালিকায় কোনো আইটেম উপরে বা মাঝখানে যোগ করা হয়, সরানো হয় বা পুনর্বিন্যাস করা হয়, তাহলে MovieOverview সেই সমস্ত কলে একটি পুনর্গঠন ঘটবে, যাদের ইনপুট প্যারামিটারের অবস্থান তালিকায় পরিবর্তিত হয়েছে। এটি অত্যন্ত গুরুত্বপূর্ণ, উদাহরণস্বরূপ, যদি MovieOverview কোনো সাইড এফেক্ট ব্যবহার করে একটি মুভির ছবি ফেচ করে। এফেক্টটি চলাকালীন যদি পুনর্গঠন ঘটে, তবে এটি বাতিল হয়ে যাবে এবং আবার নতুন করে শুরু হবে।

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

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

আদর্শগতভাবে, আমরা MovieOverview ইনস্ট্যান্সের পরিচয়কে এর কাছে পাঠানো movie পরিচয়ের সাথে সংযুক্ত বলে ভাবতে চাই। যদি আমরা মুভির তালিকাটি পুনর্বিন্যাস করি, তবে আদর্শগতভাবে আমাদের প্রতিটি MovieOverview কম্পোজেবলকে একটি ভিন্ন মুভি ইনস্ট্যান্সের সাথে পুনরায় কম্পোজ করার পরিবর্তে কম্পোজিশন ট্রি-তে ইনস্ট্যান্সগুলোকেও একইভাবে পুনর্বিন্যাস করা উচিত। কম্পোজ আপনাকে রানটাইমকে বলে দেওয়ার একটি উপায় প্রদান করে যে আপনি ট্রি-এর একটি নির্দিষ্ট অংশ শনাক্ত করার জন্য কোন মানগুলো ব্যবহার করতে চান: সেটি হলো `composable` key

এক বা একাধিক ভ্যালু পাস করে কী কম্পোজেবল-এর একটি কল দিয়ে কোনো কোড ব্লক র‍্যাপ করার মাধ্যমে, সেই ভ্যালুগুলো একত্রিত হয়ে কম্পোজিশনের মধ্যে ওই ইনস্ট্যান্সটিকে শনাক্ত করতে ব্যবহৃত হবে। একটি key এর ভ্যালুকে বিশ্বব্যাপী অনন্য হওয়ার প্রয়োজন নেই, এটিকে শুধুমাত্র কল সাইটে কম্পোজেবল-এর ইনভোকেশনগুলোর মধ্যে অনন্য হতে হবে। সুতরাং এই উদাহরণে, প্রতিটি movie জন্য এমন একটি key থাকতে হবে যা movies মধ্যে অনন্য; যদি অ্যাপের অন্য কোথাও থাকা অন্য কোনো কম্পোজেবল-এর সাথে সেই key শেয়ার করা হয়, তাতেও কোনো সমস্যা নেই।

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

উপরোক্ত ব্যবস্থার ফলে, তালিকার উপাদানগুলো পরিবর্তিত হলেও Compose, MovieOverview এর প্রতিটি কলকে শনাক্ত করতে পারে এবং সেগুলোকে পুনরায় ব্যবহার করতে পারে।

তালিকার শীর্ষে একটি নতুন উপাদান যুক্ত করা হলে পূর্ববর্তী কোডটি কীভাবে পুনর্গঠিত হয়, তা এই ডায়াগ্রামে দেখানো হয়েছে। যেহেতু তালিকার আইটেমগুলো কী (key) দ্বারা চিহ্নিত করা হয়, তাই Compose জানে যে তাদের অবস্থান পরিবর্তিত হলেও সেগুলোকে পুনর্গঠিত করতে হবে না।
চিত্র ৬। তালিকায় একটি নতুন উপাদান যুক্ত করা হলে কম্পোজিশনে MoviesScreen এর উপস্থাপনা। যেহেতু MovieOverview কম্পোজেবলগুলোর অনন্য কী (key) আছে, Compose বুঝতে পারে কোন MovieOverview ইনস্ট্যান্সগুলো পরিবর্তিত হয়নি এবং সেগুলোকে পুনরায় ব্যবহার করতে পারে; তাদের সাইড ইফেক্টগুলো কার্যকর হতে থাকবে।

কিছু কম্পোজেবলে key কম্পোজেবলের জন্য বিল্ট-ইন সাপোর্ট থাকে। উদাহরণস্বরূপ, LazyColumn তার items DSL-এ একটি কাস্টম key নির্দিষ্ট করা গ্রহণ করে।

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

ইনপুটগুলো পরিবর্তিত না হলে এড়িয়ে যাওয়া হচ্ছে।

পুনর্গঠনের সময়, কিছু উপযুক্ত সংযোজনযোগ্য ফাংশনের কার্য সম্পাদন সম্পূর্ণরূপে এড়িয়ে যাওয়া যেতে পারে, যদি তাদের ইনপুট পূর্ববর্তী গঠন থেকে পরিবর্তিত না হয়।

একটি কম্পোজেবল ফাংশন বাদ দেওয়ার যোগ্য হবে, যদি না :

  • ফাংশনটির রিটার্ন টাইপ Unit নয়।
  • ফাংশনটি @NonRestartableComposable অথবা @NonSkippableComposable দিয়ে টীকাযুক্ত।
  • একটি প্রয়োজনীয় প্যারামিটার অস্থিতিশীল ধরনের।

স্ট্রং স্কিপিং নামে একটি পরীক্ষামূলক কম্পাইলার মোড রয়েছে, যা শেষ শর্তটি শিথিল করে।

কোনো একটি প্রকারকে স্থিতিশীল বলে গণ্য করার জন্য তাকে নিম্নলিখিত চুক্তিটি মেনে চলতে হবে:

  • একই দুটি ইনস্ট্যান্সের ক্ষেত্রে equals ফাংশনের ফলাফল চিরকাল একই থাকবে।
  • যদি এই ধরনের কোনো সরকারি সম্পত্তির পরিবর্তন হয়, তাহলে কম্পোজিশনকে অবহিত করা হবে।
  • সকল প্রকার সরকারি সম্পত্তিও স্থিতিশীল।

কিছু গুরুত্বপূর্ণ সাধারণ টাইপ আছে যা এই চুক্তির আওতায় পড়ে এবং Compose কম্পাইলার সেগুলোকে স্থিতিশীল (stable) হিসেবে গণ্য করে, যদিও @Stable অ্যানোটেশন ব্যবহার করে সেগুলোকে স্পষ্টভাবে স্থিতিশীল হিসেবে চিহ্নিত করা হয় না:

  • সকল প্রিমিটিভ ভ্যালু টাইপ: Boolean , Int , Long , Float , Char , ইত্যাদি।
  • স্ট্রিং
  • সকল ফাংশন প্রকার (ল্যাম্বডা)

এই সমস্ত প্রকারই স্থিতিশীল (stable) চুক্তি অনুসরণ করতে সক্ষম, কারণ এগুলো অপরিবর্তনীয় (immutable)। যেহেতু অপরিবর্তনীয় প্রকারগুলো কখনও পরিবর্তিত হয় না, তাই পরিবর্তনের বিষয়ে কম্পোজিশনকে (Composition) জানানোর প্রয়োজন হয় না, ফলে এই চুক্তি অনুসরণ করা অনেক সহজ।

একটি উল্লেখযোগ্য টাইপ যা স্থিতিশীল কিন্তু পরিবর্তনযোগ্য , তা হলো Compose-এর MutableState টাইপ। যদি কোনো MutableState এ কোনো মান রাখা হয়, তবে সামগ্রিকভাবে স্টেট অবজেক্টটিকে স্থিতিশীল বলে মনে করা হয়, কারণ State এর .value প্রপার্টির যেকোনো পরিবর্তনের বিষয়ে Compose-কে অবহিত করা হয়।

যখন একটি কম্পোজেবল-এ প্যারামিটার হিসেবে পাস করা সমস্ত টাইপ স্থিতিশীল থাকে, তখন UI ট্রি-তে কম্পোজেবলটির অবস্থানের উপর ভিত্তি করে প্যারামিটার মানগুলির সমতা তুলনা করা হয়। যদি পূর্ববর্তী কলের পর থেকে সমস্ত মান অপরিবর্তিত থাকে, তাহলে পুনর্গঠন এড়িয়ে যাওয়া হয়।

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

যদি Compose কোনো টাইপকে স্টেবল হিসেবে শনাক্ত করতে না পারে, কিন্তু আপনি Compose-কে সেটিকে স্টেবল হিসেবে গণ্য করতে বাধ্য করতে চান, তাহলে সেটিকে @Stable অ্যানোটেশন দিয়ে চিহ্নিত করুন।

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

উপরের কোড স্নিপেটে, যেহেতু UiState একটি ইন্টারফেস, Compose সাধারণত এই টাইপটিকে অস্থিতিশীল (non stable) হিসেবে বিবেচনা করতে পারে। @Stable অ্যানোটেশনটি যোগ করার মাধ্যমে, আপনি Compose-কে জানিয়ে দেন যে এই টাইপটি স্থিতিশীল, যা Compose-কে স্মার্ট রিকম্পোজিশন (smart recompositions) অগ্রাধিকার দিতে সাহায্য করে। এর আরও একটি অর্থ হলো, যদি ইন্টারফেসটি প্যারামিটার টাইপ হিসেবে ব্যবহৃত হয়, তবে Compose এর সমস্ত ইমপ্লিমেন্টেশনকে স্থিতিশীল হিসেবে গণ্য করবে।

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}