কম্পোজ করার উপযুক্ত আইটেমের লাইফসাইকেল

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

লাইফসাইকেল ওভারভিউ

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

Jetpack Compose যখন প্রথমবার আপনার কম্পোজেবল রান করে, প্রাথমিক কম্পোজিশন চলাকালীন, এটি কম্পোজিশনে আপনার UI বর্ণনা করার জন্য আপনি যে কম্পোজেবল কল করেন তার ট্র্যাক রাখবে। তারপর, আপনার অ্যাপের স্টেট পরিবর্তন হলে, Jetpack Compose একটি রিকম্পোজিশন শিডিউল করে। রিকম্পোজিশন হল যখন Jetpack Compose স্টেট পরিবর্তনের ফলে পরিবর্তিত হতে পারে এমন কম্পোজেবল আবার এক্সিকিউট করে, এবং তারপর যেকোনও পরিবর্তন দেখানোর জন্য কম্পোজিশন আপডেট করে।

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

কম্পোজ করার উপযুক্ত আইটেমের লাইফসাইকেল দেখানো ডায়াগ্রাম
ছবি ১. কম্পোজিশনে কম্পোজেবলের লাইফসাইকেল। এটি কম্পোজিশনে প্রবেশ করে, ০ বা তার বেশি বার রিকম্পোজ হয় এবং কম্পোজিশন থেকে বেরিয়ে যায়।

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

কোনও কম্পোজেবল একাধিকবার কল করা হলে, কম্পোজিশনে একাধিক ইনস্ট্যান্স রাখা হয়। কম্পোজিশনে প্রতিটি কলের নিজস্ব লাইফসাইকেল আছে।

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

আগের কোড স্নিপেটে এলিমেন্টের হায়ারার্কিক্যাল বিন্যাস দেখানো ডায়াগ্রাম
ছবি ২. কম্পোজিশনে MyComposable-এর প্রতিনিধিত্ব। কোনও কম্পোজেবল একাধিকবার কল করা হলে, কম্পোজিশনে একাধিক ইনস্ট্যান্স রাখা হয়। আলাদা রঙের এলিমেন্ট থাকলে বুঝতে হবে যে সেটি একটি পৃথক ইনস্ট্যান্স।

কম্পোজিশনে কম্পোজেবলের অ্যানাটমি

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

রিকম্পোজিশন চলাকালীন কোনও কম্পোজেবল যদি আগের কম্পোজিশনের সময় কল করা কম্পোজেবলের থেকে আলাদা কোনও কম্পোজেবলকে কল করে, তাহলে 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-এর এমন কোনও প্যারামিটার নেই যা রিকম্পোজিশন জুড়ে পরিবর্তিত হয়েছে, তাই LoginInput-এ করা কল Compose এড়িয়ে যাবে।

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

কোনও কম্পোজেবলকে একাধিকবার কল করলে সেটি কম্পোজিশনেও একাধিকবার যোগ করা হবে। একই কল সাইট থেকে একাধিকবার কম্পোজ করার উপযুক্ত আইটেম কল করার সময়, Compose-এর কাছে সেই কম্পোজ করার উপযুক্ত আইটেমের প্রতিটি কল অনন্যভাবে শনাক্ত করার জন্য কোনও তথ্য থাকে না, তাই ইনস্ট্যান্স আলাদা রাখার জন্য কল সাইটের পাশাপাশি এক্সিকিউশন অর্ডার ব্যবহার করা হয়। This behavior is sometimes all that is needed, but in some cases it can cause unwanted behavior.

@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 যোগ করা হলে, Compose টুলকিট, Composition-এ আগে থেকে থাকা ইনস্ট্যান্স আবার ব্যবহার করতে পারে, কারণ তালিকায় সেগুলির লোকেশন পরিবর্তিত হয়নি এবং তাই সেইসব ইনস্ট্যান্সের জন্য 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 কম্পোজেবলকে অন্য একটি সিনেমা ইনস্ট্যান্সের সাথে রিকম্পোজ করার পরিবর্তে। রানটাইমকে আপনি ট্রি-এর কোনও নির্দিষ্ট অংশ শনাক্ত করার জন্য কোন ভ্যালু ব্যবহার করতে চান তা জানানোর জন্য Compose আপনাকে একটি উপায় প্রদান করে: 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-এ করা প্রতিটি কল শনাক্ত করতে পারে এবং সেগুলি আবার ব্যবহার করতে পারে।

তালিকার একদম উপরে নতুন এলিমেন্ট যোগ করা হলে, আগের কোড কীভাবে রিকম্পোজ করা হয় তা ডায়াগ্রামে দেখানো হয়েছে। তালিকার আইটেমগুলি কী দ্বারা শনাক্ত করা হয় বলে, কম্পোজ জানে যে তাদের অবস্থান পরিবর্তন হলেও তাদের আবার কম্পোজ করতে হবে না।
ছবি ৬. তালিকায় নতুন এলিমেন্ট যোগ করা হলে কম্পোজিশনে MoviesScreen-এর প্রতিনিধিত্ব। MovieOverview কম্পোজেবলের অনন্য কী আছে, তাই 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 অ্যানোটেশন ব্যবহার করে স্পষ্টভাবে স্টেবল হিসেবে চিহ্নিত করা হয়নি:

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

এই ধরনের সবকটিই স্টেবল কন্ট্র্যাক্ট ফলো করতে পারে কারণ সেগুলি ইমিউটেবল। যেহেতু অপরিবর্তনীয় টাইপ কখনও পরিবর্তন হয় না, তাই কম্পোজিশনকে পরিবর্তনের ব্যাপারে কখনও বিজ্ঞপ্তি পাঠাতে হয় না, তাই এই চুক্তি মেনে চলা অনেক সহজ।

একটি উল্লেখযোগ্য ধরন যা স্টেবল কিন্তু পরিবর্তনশীল তা হল 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 সাধারণত এই ধরনের ডেটাকে স্টেবল হিসেবে বিবেচনা নাও করতে পারে। @Stable অ্যানোটেশন যোগ করার মাধ্যমে, আপনি Compose-কে জানান যে এই ধরনটি স্টেবল, যা Compose-কে স্মার্ট রিকম্পোজিশন করার অনুমতি দেয়। এর অর্থ হল, ইন্টারফেসটি প্যারামিটার টাইপ হিসেবে ব্যবহার করা হলে, Compose তার সমস্ত ইমপ্লিমেন্টেশনকে স্টেবল হিসেবে বিবেচনা করবে।