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

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

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

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

برای اطلاعات بیشتر به پرش قوی مراجعه کنید.

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

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

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

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

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

برای حل این مشکل، می توانید از مجموعه های غیرقابل تغییر استفاده کنید. کامپایلر Compose شامل پشتیبانی از Kotlinx Immutable Collections است. این مجموعه‌ها تضمین شده‌اند که تغییر ناپذیر هستند و کامپایلر 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 است.

حاشیه نویسی یک کلاس، آنچه را که کامپایلر درباره کلاس شما استنباط می کند، نادیده می گیرد. شبیه به !! اپراتور در کاتلین شما باید در مورد نحوه استفاده از این حاشیه نویسی بسیار مراقب باشید. نادیده گرفتن رفتار کامپایلر می‌تواند شما را به باگ‌های پیش‌بینی‌نشده‌ای سوق دهد، مانند اینکه 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 به عنوان ناپایدار علامت گذاری می کند ، حتی زمانی که مجموعه ای از انواع پایدار باشد.

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

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

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

اگر از قرارداد پایداری در پایگاه کد خود راضی هستید، می‌توانید با افزودن 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 اکنون می‌تواند از HighlightedSnacks صرفنظر کند، اگر هیچ یک از ورودی‌های آن تغییر نکرده باشد.

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

با شروع 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<*,_>

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

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

As the Compose compiler runs on each module in your project separately, you can
provide different configurations to different modules if needed. Alternatively,
have one configuration at the root level of your project and pass that path to
each module.

## Multiple modules {:#multiple-modules}

Another common issue involves multi-module architecture. The Compose compiler
can only infer whether a class is stable if all of the non-primitive types that
it references are either explicitly marked as stable or in a module that was
also built with the Compose compiler.

If your data layer is in a separate module to your UI layer, which is the
recommended approach, this may be an issue you encounter.

### Solution {:#modules-solution}

To solve this issue you can take one of the following approaches:

1.  Add the classes to your [Compiler configuration file](#configuration-file).
1.  Enable the Compose compiler on your data layer modules, or tag your classes
    with `@Stable` or `@Immutable` where appropriate.
    -   This involves adding a Compose dependency to your data layer. However,
        it is just the dependency for the Compose runtime and not for
        `Compose-UI`.
1.  Within your UI module, wrap your data layer classes in UI-specific wrapper
    classes.

The same issue also occurs when using external libraries if they don't use the
Compose compiler.

## Not every composable should be skippable {:#not-composable}

When working to fix issues with stability, you shouldn't attempt to make every
composable skippable. Attempting to do so can lead to premature optimisation
that introduces more issues than it fixes.

There are many situations where being skippable doesn't have any real benefit
and can lead to hard to maintain code. For example:

-   A composable that is not recomposed often, or at all.
-   A composable that in itself just calls skippable composables.
-   A composable with a large number of parameters with expensive equals
    implementations. In this case, the cost of checking if any parameter has
    changed could outweigh the cost of a cheap recomposition.

When a composable is skippable it adds a small overhead which may not be worth
it. You can even annotate your composable to be [non-restartable][9] in cases
where you determine that being restartable is more overhead than it's worth.







،

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

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

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

برای اطلاعات بیشتر به پرش قوی مراجعه کنید.

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

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

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

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

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

برای حل این مشکل، می توانید از مجموعه های غیرقابل تغییر استفاده کنید. کامپایلر Compose شامل پشتیبانی از Kotlinx Immutable Collections است. این مجموعه‌ها تضمین شده‌اند که تغییر ناپذیر هستند و کامپایلر 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 است.

حاشیه نویسی یک کلاس، آنچه را که کامپایلر درباره کلاس شما استنباط می کند، نادیده می گیرد. شبیه به !! اپراتور در کاتلین شما باید در مورد نحوه استفاده از این حاشیه نویسی بسیار مراقب باشید. نادیده گرفتن رفتار کامپایلر می‌تواند شما را به باگ‌های پیش‌بینی‌نشده‌ای سوق دهد، مانند اینکه 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 به عنوان ناپایدار علامت گذاری می کند ، حتی زمانی که مجموعه ای از انواع پایدار باشد.

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

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

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

اگر از قرارداد پایداری در پایگاه کد خود راضی هستید، می‌توانید با افزودن 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 اکنون می‌تواند از HighlightedSnacks صرفنظر کند، اگر هیچ یک از ورودی‌های آن تغییر نکرده باشد.

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

با شروع 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<*,_>

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

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

As the Compose compiler runs on each module in your project separately, you can
provide different configurations to different modules if needed. Alternatively,
have one configuration at the root level of your project and pass that path to
each module.

## Multiple modules {:#multiple-modules}

Another common issue involves multi-module architecture. The Compose compiler
can only infer whether a class is stable if all of the non-primitive types that
it references are either explicitly marked as stable or in a module that was
also built with the Compose compiler.

If your data layer is in a separate module to your UI layer, which is the
recommended approach, this may be an issue you encounter.

### Solution {:#modules-solution}

To solve this issue you can take one of the following approaches:

1.  Add the classes to your [Compiler configuration file](#configuration-file).
1.  Enable the Compose compiler on your data layer modules, or tag your classes
    with `@Stable` or `@Immutable` where appropriate.
    -   This involves adding a Compose dependency to your data layer. However,
        it is just the dependency for the Compose runtime and not for
        `Compose-UI`.
1.  Within your UI module, wrap your data layer classes in UI-specific wrapper
    classes.

The same issue also occurs when using external libraries if they don't use the
Compose compiler.

## Not every composable should be skippable {:#not-composable}

When working to fix issues with stability, you shouldn't attempt to make every
composable skippable. Attempting to do so can lead to premature optimisation
that introduces more issues than it fixes.

There are many situations where being skippable doesn't have any real benefit
and can lead to hard to maintain code. For example:

-   A composable that is not recomposed often, or at all.
-   A composable that in itself just calls skippable composables.
-   A composable with a large number of parameters with expensive equals
    implementations. In this case, the cost of checking if any parameter has
    changed could outweigh the cost of a cheap recomposition.

When a composable is skippable it adds a small overhead which may not be worth
it. You can even annotate your composable to be [non-restartable][9] in cases
where you determine that being restartable is more overhead than it's worth.