رفع مشکلات پایداری

وقتی با کلاس ناپایداری مواجه شدید که باعث مشکلات عملکرد می‌شود، باید آن را پایدار کنید. این سند چندین تکنیک را که می‌توانید برای انجام این کار استفاده کنید، شرح می‌دهد.

فعال کردن رد شدن قوی

ابتدا باید حالت پرش قوی را فعال کنید. حالت رد کردن قوی به ترکیب‌پذیرهایی با پارامترهای ناپایدار اجازه می‌دهد رد شوند و آسان‌ترین روش برای رفع مشکلات عملکردی ناشی از ناپایداری است.

برای اطلاعات بیشتر، رد شدن قوی را ببینید.

تغییرناپذیر کردن کلاس

همچنین می‌توانید کلاس ناپایدار را کاملاً تغییرناپذیر کنید.

  • تغییرناپذیر: نوعی را نشان می‌دهد که در آن مقدار هیچ‌یک از دارایی‌ها نمی‌تواند پس‌از ساخت نمونه‌ای از آن نوع تغییر کند و همه روش‌ها ازنظر ارجاعی شفاف هستند.
    • مطمئن شوید همه دارایی‌های کلاس هم val به‌جای var باشند، و هم از انواع تغییرناپذیر باشند.
    • انواع اولیه مانند String, Int و Float همیشه تغییرناپذیر هستند.
    • اگر این کار غیرممکن است، باید برای هر دارایی تغییرپذیر از حالت «نوشتن» استفاده کنید.
  • Stable: نوعی را نشان می‌دهد که قابل‌تغییر است. زمان اجرای Compose متوجه نمی‌شود که آیا و چه زمانی هریک از ویژگی‌های عمومی نوع یا رفتار روش نتایج متفاوتی نسبت‌به فراخوانی قبلی ارائه می‌دهد.

مجموعه‌های تغییرناپذیر

یکی از دلایل رایج اینکه Compose یک کلاس را ناپایدار درنظر می‌گیرد، مجموعه‌ها هستند. همان‌طور که در صفحه تشخیص مشکلات پایداری ذکر شده است، کامپایلر Compose نمی‌تواند کاملاً مطمئن باشد که مجموعه‌هایی مانند List, Map و Set واقعاً تغییرناپذیر هستند و بنابراین آن‌ها را به‌عنوان ناپایدار علامت‌گذاری می‌کند.

برای حل این مشکل، می‌توانید از مجموعه‌های تغییرناپذیر استفاده کنید. کامپایلر Compose از مجموعه‌های تغییرناپذیر Kotlinx پشتیبانی می‌کند. این مجموعه‌ها به‌گونه‌ای طراحی شده‌اند که تغییرناپذیر باشند و کامپایلر Compose آن‌ها را به همین شکل درنظر می‌گیرد. این کتابخانه هنوز در مرحله آلفا است، بنابراین انتظار می‌رود که API آن تغییر کند.

این کلاس ناپایدار را از راهنمای تشخیص مشکلات پایداری دوباره درنظر بگیرید:

unstable class Snack {
  …
  unstable val tags: Set<String>
  …
}

می‌توانید بااستفاده از مجموعه تغییرناپذیر، tags را پایدار کنید. در کلاس، نوع tags را به ImmutableSet<String> تغییر دهید:

data class Snack{
    …
    val tags: ImmutableSet<String> = persistentSetOf()
    …
}

پس‌از انجام این کار، همه پارامترهای کلاس تغییرناپذیر می‌شوند و کامپایلر Compose کلاس را به‌عنوان پایدار علامت‌گذاری می‌کند.

حاشیه‌نویسی با Stable یا Immutable

یکی از مسیرهای احتمالی برای حل مشکلات پایداری، حاشیه‌نویسی کردن کلاس‌های ناپایدار با @Stable یا @Immutable است.

حاشیه‌نویسی کلاس باعث می‌شود آنچه را که کامپایلر درغیراین‌صورت درباره کلاس شما استنباط می‌کند ملغی کنید. این عملگر شبیه به !! عملگر در Kotlin است. باید در نحوه استفاده از این حاشیه‌نویسی‌ها بسیار دقت کنید. لغو رفتار کامپایلر می‌تواند منجر به اشکالات پیش‌بینی‌نشده‌ای شود، مانند اینکه composable شما زمانی که انتظار دارید بازترکیب نشود.

اگر امکان‌پذیر است که کلاس خود را بدون شرح پایدار کنید، باید تلاش کنید به این روش به پایداری دست یابید.

قطعه کد زیر نمونه‌ای حداقلی از کلاس داده‌ای را ارائه می‌دهد که به‌عنوان تغییرناپذیر حاشیه‌نویسی شده است:

@Immutable
data class Snack(
…
)

چه از @Immutable یا @Stable گزارمان استفاده کنید، گردآورنده Compose کلاس Snack را به‌عنوان پایدار علامت‌گذاری می‌کند.

کلاس‌های حاشیه‌نویسی‌شده در مجموعه‌ها

یک composable را درنظر بگیرید که شامل پارامتری از نوع List<Snack> است:

restartable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  …
  unstable snacks: List<Snack>
  …
)

حتی اگر Snack را با @Immutable حاشیه‌نویسی کنید، کامپایلر Compose همچنان پارامتر snacks را در HighlightedSnacks به‌عنوان ناپایدار علامت‌گذاری می‌کند.

پارامترها هنگام مواجهه با انواع مجموعه با همان مشکل کلاس‌ها مواجه می‌شوند، کامپایلر Compose همیشه پارامتر نوع List را به‌عنوان ناپایدار علامت‌گذاری می‌کند، حتی زمانی که مجموعه‌ای از انواع پایدار باشد.

نمی‌توانید پارامترهای تکی را به‌عنوان پایدار علامت‌گذاری کنید، همچنین نمی‌توانید یک composable را حاشیه‌نویسی کنید تا همیشه قابل‌ردشدن باشد. چندین مسیر پیش رو وجود دارد.

چندین راه برای حل مشکل مجموعه‌های ناپایدار وجود دارد. زیربخش‌های زیر این رویکردهای مختلف را شرح می‌دهند.

فایل پیکربندی

اگر مایلید از قرارداد پایداری در پایگاه کد خود پیروی کنید، می‌توانید با افزودن kotlin.collections.* به فایل پیکربندی پایداری خود، مجموعه‌های Kotlin را به‌عنوان پایدار درنظر بگیرید.

مجموعه تغییرناپذیر

برای ایمنی زمان ترجمه تغییرناپذیری، می‌توانید به‌جای List از مجموعه تغییرناپذیر kotlinx استفاده کنید.

@Composable
private fun HighlightedSnacks(
    …
    snacks: ImmutableList<Snack>,
    …
)

بسته‌بندی

اگر نمی‌توانید از مجموعه تغییرناپذیر استفاده کنید، می‌توانید مجموعه خودتان را بسازید. برای انجام این کار، List را در کلاس پایدار حاشیه‌نویسی‌شده قرار دهید. بسته به نیازهای شما، یک پوشاننده عمومی احتمالاً بهترین انتخاب برای این کار است.

@Immutable
data class SnackCollection(
   val snacks: List<Snack>
)

سپس می‌توانید از این به‌عنوان نوع پارامتر در composable خود استفاده کنید.

@Composable
private fun HighlightedSnacks(
    index: Int,
    snacks: SnackCollection,
    onSnackClick: (Long) -> Unit,
    modifier: Modifier = Modifier
)

راه‌حل

پس‌از اتخاذ هریک از این رویکردها، اکنون کامپایلر Compose تابع HighlightedSnacks Composable را هم به‌عنوان skippable و هم به‌عنوان restartable علامت‌گذاری می‌کند.

restartable skippable scheme("[androidx.compose.ui.UiComposable]") fun HighlightedSnacks(
  stable index: Int
  stable snacks: ImmutableList<Snack>
  stable onSnackClick: Function1<Long, Unit>
  stable modifier: Modifier? = @static Companion
)

درطول بازآرایی، اکنون اگر هیچ‌یک از ورودی‌های Compose تغییر نکرده باشد، Compose می‌تواند از HighlightedSnacks رد شود.

فایل پیکربندی پایداری

از «گردآورنده Compose» نسخه ۱.۵.۵، فایل پیکربندی کلاس‌هایی که باید پایدار درنظر گرفته شوند را می‌توان در زمان گردآوری ارائه کرد. این کار امکان می‌دهد کلاس‌هایی را که کنترل نمی‌کنید، مثل کلاس‌های کتابخانه استاندارد مثل 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<*,_>

برای فعال کردن این ویژگی، مسیر فایل پیکربندی را به بلوک گزینه‌های composeCompiler پیکربندی افزایه Compose compiler Gradle ارسال کنید.

composeCompiler {
  stabilityConfigurationFile = rootProject.layout.projectDirectory.file("stability_config.conf")
}

ازآنجایی‌که کامپایلر Compose به‌طور جداگانه روی هر پیمانه در پروژه شما اجرا می‌شود، می‌توانید درصورت نیاز پیکربندی‌های متفاوتی را برای پیمانه‌های مختلف ارائه دهید. یا اینکه یک پیکربندی در سطح ریشه پروژه‌تان داشته باشید و آن مسیر را به هر واحد ارسال کنید.

چندین واحد

مشکل رایج دیگر مربوط به معماری چند ماژولی است. کامپایلر Compose تنها درصورتی می‌تواند پایدار بودن کلاس را استنباط کند که همه انواع غیرابتدایی که به آن‌ها ارجاع می‌دهد یا به‌طور صریح به‌عنوان پایدار علامت‌گذاری شده باشند یا در ماژولی باشند که با کامپایلر Compose ساخته شده باشد.

اگر لایه داده شما در واحدی جدا از لایه میانای کاربری شما قرار دارد (که رویکرد توصیه‌شده است)، ممکن است با این مشکل مواجه شوید.

راه‌حل

برای حل این مشکل می‌توانید یکی از رویکردهای زیر را اتخاذ کنید:

  1. کلاس‌ها را به فایل پیکربندی کامپایلر اضافه کنید.
  2. کامپایلر Compose را در پیمانه‌های لایه داده‌تان فعال کنید، یا کلاس‌هایتان را با @Stable یا @Immutable در جاهای مناسب برچسب‌گذاری کنید.
    • این کار شامل افزودن وابستگی Compose به لایه داده شما است. بااین‌حال، این فقط وابستگی برای زمان اجرای Compose است و نه برای Compose-UI.
  3. در واحد واسط کاربر، کلاس‌های لایه داده را در کلاس‌های بسته‌بندی مختص واسط کاربر بپیچید.

همین مشکل هنگام استفاده از کتابخانه‌های خارجی نیز رخ می‌دهد، اگر از کامپایلر Compose استفاده نکنند.

همه عناصر ترکیبی نباید قابل رد شدن باشند

هنگام تلاش برای رفع مشکلات پایداری، نباید سعی کنید هر عنصر ترکیبی را قابل‌پرش کنید. تلاش برای انجام این کار می‌تواند منجر به بهینه‌سازی زودرس شود که مشکلات بیشتری نسبت‌به آنچه برطرف می‌کند ایجاد می‌کند.

موقعیت‌های زیادی وجود دارد که در آن‌ها قابل‌رد شدن بودن هیچ مزیت واقعی ندارد و می‌تواند منجر به کدی شود که نگهداری آن دشوار است. برای مثال:

  • یک composable که اغلب یا اصلاً بازترکیب نمی‌شود.
  • یک ترکیب‌پذیر که خودش فقط ترکیب‌پذیرهای قابل‌ردشدن را فراخوانی می‌کند.
  • ترکیب‌پذیری با تعداد زیادی پارامتر با پیاده‌سازی‌های گران‌قیمت equals. در این حالت، هزینه بررسی اینکه آیا پارامتری تغییر کرده است یا نه می‌تواند از هزینه بازترکیب ارزان بیشتر باشد.

وقتی یک composable قابل‌رد شدن باشد، سربار کوچکی اضافه می‌کند که ممکن است ارزشش را نداشته باشد. حتی می‌توانید در مواردی که تشخیص می‌دهید قابل‌راه‌اندازی مجدد بودن بیشتر از ارزشش سربار دارد، ترکیب‌پذیرتان را به‌عنوان غیرقابل‌راه‌اندازی مجدد حاشیه‌نویسی کنید.