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

এই পৃষ্ঠায়, আপনি কম্পোজ করার উপযুক্ত আইটেমের লাইফসাইকেল এবং কোনও কম্পোজ করার উপযুক্ত আইটেমকে আবার কম্পোজ করার প্রয়োজন আছে কিনা তা Compose কীভাবে নির্ধারণ করে সেই সম্পর্কে জানবেন।

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

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

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

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

@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 কম্পোজ করার পরিবর্তে কম্পোজিশন ট্রি-তে থাকা ইনস্ট্যান্সগুলিকেও একইভাবে নতুন করে সাজাব। 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)
            }
        }
    }
}

উপরের তথ্য অনুযায়ী, তালিকায় থাকা এলিমেন্ট পরিবর্তন হলেও, কম্পোজ MovieOverview-এ করা আলাদা আলাদা কল শনাক্ত করতে পারে এবং সেগুলি আবার ব্যবহার করতে পারে।

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

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

@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-এ হোল্ড করা থাকলে, সামগ্রিকভাবে স্টেট অবজেক্টকে স্থিতিশীল হিসেবে বিবেচনা করা হয়, কারণ Compose-কে State-এর .value প্রপার্টিতে হওয়া যেকোনও পরিবর্তন সম্পর্কে বিজ্ঞপ্তি পাঠানো হবে।

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