পারফর্ম্যান্স সংক্রান্ত সমস্যা সৃষ্টি করে এমন অস্থির ক্লাস-এর সম্মুখীন হলে, Compose কীভাবে এটি তুলনা বা ইনস্ট্যান্টিয়েট করে তা আপনাকে অপ্টিমাইজ করতে হবে। এই ডকুমেন্টে এমন কয়েকটি কৌশল উল্লেখ করা হয়েছে যা আপনি ব্যবহার করতে পারেন।
স্ট্রং স্কিপিং চালু করুন
Kotlin 2.0.20 থেকে শুরু করে ডিফল্ট হিসেবে স্ট্রং স্কিপিং মোড চালু করা থাকে।
স্ট্রং স্কিপিং মোড ইনস্ট্যান্স ইকুয়ালিটি (===) ব্যবহার করে অস্থির প্যারামিটার তুলনা করার মাধ্যমে
অস্থির প্যারামিটার সহ কম্পোজেবল স্কিপ করার অনুমতি দেয়, যেখানে স্ট্রাকচারাল ইকুয়ালিটি (Object.equals())
ব্যবহার করে স্থির প্যারামিটার তুলনা করা চালিয়ে যাওয়া হয়।
আপনি 2.0.20-এর থেকে পুরনো Kotlin ভার্সন ব্যবহার করলে, প্রথমে স্ট্রং স্কিপিং মোড চালু করুন। স্ট্রং স্কিপিং মোড আগে থেকেই চালু করা থাকলে এবং কম্পোজেবল এখনও অপ্রয়োজনীয়ভাবে রিকম্পোজ করলে, প্রতিটি রিকম্পোজিশন পাসে অস্থির প্যারামিটারের নতুন ইনস্ট্যান্স অ্যালোকেট করা হচ্ছে কিনা তা চেক করুন।
আরও তথ্যের জন্য স্ট্রং স্কিপিং দেখুন।
ক্লাসটি অপরিবর্তনীয় করুন
এছাড়াও, আপনি কোনও অস্থির ক্লাসকে সম্পূর্ণ অপরিবর্তনীয় করতে পারেন যাতে কম্পাইলার অটোমেটিক এর স্থায়িত্ব অনুমান করতে পারে।
- ইমিউটেবল: এমন একটি ধরনকে বোঝায় যেখানে কোনও প্রপার্টির মান সেই ধরনের ইনস্ট্যান্স তৈরি হওয়ার পরে
কখনও পরিবর্তন করা যায় না এবং সব পদ্ধতি
রেফারেন্সিয়ালি ট্রান্সপারেন্ট হয়।
- নিশ্চিত করুন যে ক্লাসের সব প্রপার্টি
var-এর পরিবর্তেvalএবং অপরিবর্তনীয় ধরনের। String,IntওFloat-এর মতো প্রিমিটিভ টাইপ সবসময় অপরিবর্তনীয় হয়।- কম্পোজ কম্পাইলারের সাহায্যে তৈরি মডিউলে
valপ্রিমিটিভ বাStringপ্রপার্টি সহ ফ্ল্যাটdata classধরনকে আগে থেকেই স্টেবল হিসেবে অনুমান করা হয় এবং.equals()-এর সাথে অটোমেটিক তুলনা করা হয়—আপনাকে@Immutable-এর সাহায্যে সেগুলি অ্যানোটেট করতে হবে না। - ইমিউটেবিলিটি অসম্ভব হলে, আপনাকে অবশ্যই যেকোনও মিউটেবল প্রপার্টির জন্য কম্পোজ স্টেট ব্যবহার করতে হবে।
- নিশ্চিত করুন যে ক্লাসের সব প্রপার্টি
- Stable: এমন ধরনকে নির্দেশ করে যা পরিবর্তনযোগ্য। Compose রানটাইম জানতে পারে না যে কখন এবং কীভাবে কোনও ধরনের পাবলিক প্রপার্টি বা মেথড আচরণ আগের ইনভোকেশন থেকে আলাদা ফলাফল দেবে।
কালেকশন ও স্টেবিলিটি
Compose কোনও ক্লাসকে আনস্টেবল হিসেবে বিবেচনা করার একটি সাধারণ কারণ হল কালেকশন। স্টেবিলিটি সংক্রান্ত সমস্যা ডায়াগনসিস করা পৃষ্ঠায়
যেমন উল্লেখ করা হয়েছে, Compose কম্পাইলার সম্পূর্ণভাবে নিশ্চিত হতে পারে না যে
List, Map ও Set-এর মতো কালেকশন ইন্টারফেসগুলি
সত্যিই অপরিবর্তনীয় এবং তাই সেগুলিকে আনস্টেবল হিসেবে চিহ্নিত করে।
স্টেবিলিটি সংক্রান্ত সমস্যার ডায়াগনসিস করা গাইড থেকে এই ক্লাসটি বিবেচনা করুন:
unstable class Snack {
…
unstable val tags: Set<String>
…
}
স্ট্রং স্কিপিং মোড চালু থাকলে (Kotlin 2.0.20 এবং তার পরবর্তী ভার্সনে ডিফল্ট হিসেবে),
অস্থির কালেকশন এবং কালেকশন সহ ক্লাস ইতিমধ্যেই ইনস্ট্যান্স ইকুয়ালিটি (===) ব্যবহার করে
রিকম্পোজিশন এড়িয়ে যায়, যখনই একই রেফারেন্স পাস করা হয়।
Kotlinx Immutable Collections (ImmutableList, ImmutableSet, ImmutableMap) দিয়ে
kotlin.collections.List, Set বা Map রিপ্লেস করার আগে
অথবা কালেকশন-হোল্ডিং মডেলে @Immutable, @Stable বা stability_config.conf নিয়ম যোগ করার আগে
সতর্ক থাকুন। স্ট্রং স্কিপিং মোডে, স্ট্যান্ডার্ড List রেফারেন্স ইকুয়ালিটি (===) ব্যবহার করে
স্কিপ করে, যেখানে ImmutableList এবং অ্যানোটেট করা
কালেকশন মডেল প্রতিটি রিকম্পোজিশন পাসে এলিমেন্ট-বাই-এলিমেন্ট .equals() তুলনা
রান করে।
বড় কালেকশন বা লেজি লেআউটের (LazyColumn, LazyRow, LazyGrid) জন্য,
প্রতিটি আইটেমে .equals() চেক করতে রিকম্পোজ করার চেয়ে বেশি সময় লাগতে পারে। আপনি data class থেকে কোনও চাইল্ড কম্পোজেবলে ImmutableList প্রপার্টি পাস করলে
এই খরচ
দ্বিগুণ হয়ে যায়:
data class SnackCategory(
val name: String,
val snacks: ImmutableList<Snack>
)
@Composable
fun CategorySection(category: SnackCategory) {
Text(category.name)
// Compose compares category.snacks twice: once in SnackCategory.equals()
// for CategorySection, and again for HighlightedSnacks.
HighlightedSnacks(snacks = category.snacks)
}
কালেকশনে স্ট্রাকচারাল ইকুয়ালিটি জোর করে প্রয়োগ করার পরিবর্তে, এমিশন জুড়ে কালেকশনের
ইনস্ট্যান্স রেফারেন্স (যেমন, আপনার ViewModel-এ বা remember-এর মাধ্যমে ট্রান্সফর্মেশন ক্যাশে করে)
সংরক্ষণ করুন যাতে Compose ইনস্ট্যান্স ইকুয়ালিটি (===) ব্যবহার করা এড়িয়ে যেতে পারে।
কালেকশন-হোল্ডিং মডেলে ImmutableList বা স্টেবিলিটি অ্যানোটেশন ব্যবহার করার কথা তখনই বিবেচনা করুন
যখন আপনি কালেকশনের ইনস্ট্যান্স রেফারেন্স সংরক্ষণ করতে পারবেন না এবং বেঞ্চমার্ক নিশ্চিত করে যে
রিকম্পোজ করার চেয়ে স্ট্রাকচারাল ইকুয়ালিটি চেক করা দ্রুত হয়।
আপনি Kotlin-এর 2.0.20-এর থেকে পুরনো ভার্সন ব্যবহার করলে, প্রথমে স্ট্রং স্কিপিং
মোড চালু করুন।
Stable বা Immutable-এর মাধ্যমে অ্যানোটেট করুন
কম খরচে .equals() চেক সহ ছোট ক্লাসের জন্য স্থিতিশীলতা সংক্রান্ত সমস্যা সমাধানের একটি সম্ভাব্য উপায় হল
@Stable বা @Immutable-এর মাধ্যমে অস্থির ক্লাস অ্যানোটেট করা।
কোনও ক্লাস অ্যানোটেট করার অর্থ হল কম্পাইলার আপনার ক্লাস সম্পর্কে অন্যথায় যা অনুমান করত,
সেটিকে ওভাররাইড করা। এটি Kotlin-এ !! অপারেটরের মতো। এইসব অ্যানোটেশন কীভাবে ব্যবহার করবেন সেই ব্যাপারে আপনাকে
খুব সতর্ক থাকতে হবে। কম্পাইলারের আচরণ ওভাররাইড করলে আপনি অপ্রত্যাশিত সমস্যার সম্মুখীন হতে পারেন, যেমন আপনার কম্পোজেবল
আপনার প্রত্যাশা অনুযায়ী রিকম্পোজ না হওয়া।
স্কিপিং মোড চালু থাকলে, @Stable বা @Immutable যোগ করবেন কিনা তা সিদ্ধান্ত নেওয়ার সময় এইসব নির্দেশিকা অনুসরণ করুন:
- স্টেবল হিসেবে আগে থেকেই অনুমান করা ক্লাস অ্যানোটেট করবেন না: Compose কম্পাইলার দিয়ে তৈরি মডিউলে
স্টেবল ধরনের
valপ্রপার্টি সহ ক্লাস আগে থেকেই স্টেবল হিসেবে অনুমান করা হয়। - ইনস্ট্যান্স আবার তৈরি করা হলে, শুধুমাত্র সস্তা
.equals()চেকের মাধ্যমে ক্লাস অ্যানোটেট করুন: কম্পাইলার স্টেবিলিটি অনুমান করতে না পারলে (যেমন, নন-কম্পোজ মডিউল থেকে ইন্টারফেস বা ক্লাস)@Stableবা@Immutableযোগ করুন এবং প্রতিটি এমিশনে অপরিবর্তিত আইটেমের জন্য ডেটা সোর্স নতুন অবজেক্ট ইনস্ট্যান্স বরাদ্দ করে, যদি ক্লাসটি ছোট হয় এবং এতে সস্তা.equals()চেক থাকে। - কালেকশন আছে এমন মডেলের ক্ষেত্রে সতর্ক থাকুন: বড় কালেকশন (
List,Set,Map) আছে এমন ডেটা মডেলে@Stableবা@Immutableযোগ করা এড়িয়ে চলুন, বিশেষ করে লেজি লেআউটে (LazyColumn,LazyRow,LazyGrid,Pager) ব্যবহার করা মডেলের ক্ষেত্রে। এইসব মডেল অ্যানোটেট না করলে, Compose প্রতিটি এলিমেন্ট জুড়ে স্ট্রাকচারাল.equals()চেক করার পরিবর্তে ইনস্ট্যান্স ইকুয়ালিটি (===) ব্যবহার করে সেগুলি তুলনা করতে পারে।
নিম্নলিখিত স্নিপেটটি একটি ফ্ল্যাট ডেটা ক্লাসের (সংগ্রহের বৈশিষ্ট্য ছাড়া) একটি ন্যূনতম উদাহরণ প্রদান করে যা একটি মডিউলে সংজ্ঞায়িত করার সময় অপরিবর্তনীয় হিসাবে টীকা করা হয় যেখানে কম্পাইলার স্থায়িত্ব অনুমান করতে পারে না:
@Immutable
data class SnackSummary(
val id: Long,
val name: String,
val price: Long
)
আপনি @Immutable বা @Stable অ্যানোটেশন ব্যবহার করুন না কেন, Compose কম্পাইলার
SnackSummary ক্লাসকে স্টেবল হিসেবে চিহ্নিত করে এবং Object.equals() ব্যবহার করে
এটি তুলনা করে।
সংগ্রহে অ্যানোটেট করা ক্লাস
এমন একটি কম্পোজেবল বিবেচনা করুন যাতে List<Snack> ধরনের প্যারামিটার অন্তর্ভুক্ত থাকে:
restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
stable index: Int
unstable snacks: List<Snack>
stable onSnackClick: Function1<Long, Unit>
stable modifier: Modifier? = @static Companion
)
Snack স্টেবল বা @Immutable দিয়ে অ্যানোটেট করা থাকলেও, Compose কম্পাইলার
এখনও HighlightedSnacks-এ snacks প্যারামিটারকে unstable হিসেবে চিহ্নিত করে কারণ
List হল একটি ইন্টারফেস।
স্ট্রং স্কিপিং মোড চালু থাকলে, HighlightedSnacks এখনও
skippable হিসেবে চিহ্নিত থাকে এবং একই List ইনস্ট্যান্স পাস করা হলে ইনস্ট্যান্স ইকুয়ালিটি (===) ব্যবহার করে রিকম্পোজিশন স্কিপ করে। আপনি List<Snack>-কে @Immutable র্যাপার ক্লাসে র্যাপ করে, List<Snack>-কে ImmutableList<Snack> দিয়ে পরিবর্তন করে অথবা আপনার স্টেবিলিটি
কনফিগারেশন ফাইলে kotlin.collections.* যোগ করে snacks প্যারামিটারকে stable করতে পারবেন।
তবে, এটি করলে Compose প্রতিটি রিকম্পোজিশন পাসে এলিমেন্ট-বাই-এলিমেন্ট .equals()
তুলনা করে। আপনি তালিকার ইনস্ট্যান্স রেফারেন্স সংরক্ষণ করতে না পারলে এবং বেঞ্চমার্ক থেকে সুবিধা পাওয়া গেলে, শুধুমাত্র তখনই এই পদ্ধতিগুলি ব্যবহার করুন।
স্টেবিলিটি কনফিগারেশন ফাইল
Compose Compiler 1.5.5 থেকে শুরু করে, কম্পাইল করার সময় স্টেবল হিসেবে বিবেচনা করার জন্য ক্লাসের কনফিগারেশন ফাইল প্রদান করা যেতে পারে। এটি আপনাকে
LocalDateTime-এর মতো স্ট্যান্ডার্ড লাইব্রেরি ক্লাস সহ আপনার কন্ট্রোল করা যায় না এমন ক্লাসকে
স্টেবল হিসেবে বিবেচনা করার অনুমতি দেয়।
কনফিগারেশন ফাইল হল একটি প্লেন টেক্সট ফাইল, যার প্রতিটি সারিতে একটি করে ক্লাস থাকে। কমেন্ট, সিঙ্গেল ও ডবল ওয়াইল্ডকার্ড কাজ করে।
কনফিগারেশনের একটি উদাহরণ:
// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider my datalayer stable
com.datalayer.*
// Consider my datalayer and all submodules stable
com.datalayer.**
// Consider my generic type stable based off it's first type parameter only
com.example.GenericClass<*,_>
এই ফিচার চালু করতে, কনফিগারেশন ফাইলের পাথ Compose কম্পাইলার Gradle প্লাগ-ইন
কনফিগারেশনের composeCompiler বিকল্প ব্লকে পাস করুন।
composeCompiler {
stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}
Compose কম্পাইলার আপনার প্রোজেক্টের প্রতিটি মডিউলে আলাদাভাবে রান করে, তাই আপনি প্রয়োজন হলে বিভিন্ন মডিউলে বিভিন্ন কনফিগারেশন প্রদান করতে পারবেন। অন্যথায়, আপনার প্রোজেক্টের রুট লেভেলে একটি কনফিগারেশন রাখুন এবং সেই পাথ প্রতিটি মডিউলে পাস করুন।
একাধিক মডিউল
মাল্টি-মডিউল আর্কিটেকচার সংক্রান্ত সমস্যাও প্রায়ই দেখা যায়। Compose কম্পাইলার শুধুমাত্র তখনই কোনও ক্লাস স্টেবল কিনা তা অনুমান করতে পারে, যদি এটি রেফারেন্স করে এমন সমস্ত নন-প্রিমিটিভ টাইপ স্পষ্টভাবে স্টেবল হিসেবে চিহ্নিত করা হয় অথবা এমন একটি মডিউলে থাকে যা Compose কম্পাইলারের মাধ্যমে তৈরি করা হয়েছে।
আপনার ডেটা লেয়ার যদি UI লেয়ার থেকে আলাদা মডিউলে থাকে, যা হল
প্রস্তাবিত পদ্ধতি, তাহলে আপনি এই সমস্যার সম্মুখীন হতে পারেন। স্ট্রং স্কিপিং
মোড-এর অধীনে, নন-কম্পোজ মডিউলের ক্লাসগুলিকে অস্থির হিসেবে বিবেচনা করা হয় এবং একই ইনস্ট্যান্স পাস করা হলে ইনস্ট্যান্স ইকুয়ালিটি (===) ব্যবহার করে রিকম্পোজিশন
এড়িয়ে যায়। তবে, আপনার ডেটা লেয়ার যদি প্রায়শই প্রতিটি নির্গমনে অভিন্ন মান সহ ছোট,
ফ্ল্যাট মডেলের নতুন ইনস্ট্যান্স বরাদ্দ করে, তাহলে আপনি স্ট্রাকচারাল ইকুয়ালিটি (Object.equals())
বিকল্পে অপ্ট-ইন করতে পারেন।
সমাধান
এই সমস্যার সমাধান করতে, আপনি নিম্নলিখিত পদ্ধতিগুলির মধ্যে একটি ব্যবহার করতে পারেন:
- আপনার কম্পাইলার
কনফিগারেশন ফাইলে সস্তা
.equals()চেক সহ ছোট, ফ্ল্যাট ক্লাস যোগ করুন। - আপনার ডেটা লেয়ার মডিউলে কম্পোজ কম্পাইলার চালু করুন অথবা যেখানে উপযুক্ত সেখানে
@Stableবা@Immutableদিয়ে আপনার ক্লাস ট্যাগ করুন।- এর মধ্যে আপনার ডেটা লেয়ারে একটি কম্পোজ ডিপেন্ডেন্সি যোগ করা অন্তর্ভুক্ত। তবে,
এটি শুধু Compose রানটাইমের জন্য ডিপেন্ডেন্সি,
Compose-UI-এর জন্য নয়।
- এর মধ্যে আপনার ডেটা লেয়ারে একটি কম্পোজ ডিপেন্ডেন্সি যোগ করা অন্তর্ভুক্ত। তবে,
এটি শুধু Compose রানটাইমের জন্য ডিপেন্ডেন্সি,
- আপনার UI মডিউলের মধ্যে, UI-নির্দিষ্ট র্যাপার ক্লাসে আপনার ডেটা লেয়ার ক্লাসগুলি র্যাপ করুন।
এক্সটার্নাল লাইব্রেরি ব্যবহার করার সময়ও একই সমস্যা হয়, যদি সেগুলি Compose কম্পাইলার ব্যবহার না করে।
প্রতিটি কম্পোজেবল এড়িয়ে যাওয়া যাবে না
স্টেবিলিটি সংক্রান্ত সমস্যার সমাধান করার সময়, প্রতিটি কম্পোজেবলকে স্কিপ করার চেষ্টা করবেন না অথবা প্রতিটি প্যারামিটারে স্ট্রাকচারাল ইকুয়ালিটি জোর করে প্রয়োগ করবেন না। কোনও কম্পোজেবল এড়িয়ে যাওয়া সহজ নয়: এড়িয়ে যাওয়া যায় এমন কম্পোজেবলের প্রতিটি প্যারামিটার কম্পোজ রানটাইম এমনভাবে স্টোর করে যেন সেটি মনে রাখা হয়েছে এবং ফাংশনটি যখনই রিকম্পোজ করে, তখনই কম্পোজকে প্রতিটি প্যারামিটার তার আগের ভ্যালুর সাথে তুলনা করে দেখতে হয় যে সেটি এড়িয়ে যাওয়া উচিত কিনা। সব জায়গায় জোর করে স্কিপ করার চেষ্টা করলে অপরিণত অপ্টিমাইজেশন হতে পারে যা বাঁচানোর চেয়ে বেশি ওভারহেড নিয়ে আসে।
এমন অনেক পরিস্থিতি আছে যেখানে স্ট্রাকচারাল স্টেবিলিটি জোর করে বজায় রাখলে কোনও আসল সুবিধা পাওয়া যায় না এবং পারফর্ম্যান্স বা রক্ষণাবেক্ষণযোগ্যতা কমে যেতে পারে। যেমন:
- এমন একটি কম্পোজেবল যা প্রায়শই বা একেবারেই রিকম্পোজ করা হয় না।
- একটি কম্পোজেবল যা নিজেই এড়িয়ে যাওয়া যায় এমন কম্পোজেবলকে কল করে।
- অনেক প্যারামিটার বা কালেকশন মডেল সহ কম্পোজেবল,
যার
.equals()প্রয়োগ করতে অনেক খরচ হয়। এই ক্ষেত্রে, কোনও প্যারামিটার পরিবর্তন হয়েছে কিনা তা চেক করার খরচ, সস্তা রিকম্পোজিশনের খরচের চেয়ে বেশি হতে পারে।
শুধুমাত্র রিকম্পোজিশন সংখ্যার উপর অতিরিক্ত ইন্ডেক্স করবেন না। কম্পোজেবলকে হালকা করার দিকে মনোযোগ দিন যাতে দ্রুত কার্যকর করা যায়, দ্রুত পরিবর্তনশীল স্টেট রিডকে লেআউট বা ড্র ফেজে স্থগিত করা যায় এবং বেঞ্চমার্কের মাধ্যমে পরিবর্তনগুলি যাচাই করা যায়। কোনও কম্পোজেবল এড়িয়ে যাওয়ার মতো হলে, এটি একটি ছোট ওভারহেড যোগ করে যা হয়ত উপযুক্ত নয়। এমনকি আপনি নিজের কম্পোজেবলকে রিস্টার্ট করা যাবে না বা এড়িয়ে যাওয়া যাবে না হিসেবে অ্যানোটেট করতে পারেন, যদি আপনি মনে করেন যে রিস্টার্ট করা বা এড়িয়ে যাওয়ার সুবিধা দেওয়ার জন্য প্রয়োজনের চেয়ে বেশি ওভারহেড খরচ হবে।