Memperbaiki masalah stabilitas

Saat dihadapkan dengan class yang tidak stabil yang menyebabkan masalah performa, Anda harus membuatnya stabil. Dokumen ini menguraikan beberapa teknik yang dapat Anda gunakan untuk melakukannya.

Aktifkan lewati dengan kuat

Pertama-tama, Anda harus mencoba mengaktifkan mode lewati yang kuat. Mode lewati yang kuat memungkinkan composable dengan parameter yang tidak stabil untuk dilewati dan merupakan metode termudah untuk memperbaiki masalah performa yang disebabkan oleh stabilitas.

Lihat Lewati kuat untuk informasi selengkapnya.

Membuat class tidak dapat diubah

Anda juga dapat mencoba membuat class yang tidak stabil sepenuhnya tidak dapat diubah.

  • Tidak dapat diubah: Menunjukkan jenis dengan nilai properti apa pun yang tidak dapat berubah setelah instance jenis tersebut dibuat, dan semua metode transparan secara referensi.
    • Pastikan semua properti class adalah val, bukan var, dan merupakan jenis yang tidak dapat diubah.
    • Jenis primitif seperti String, Int dan Float selalu tidak dapat diubah.
    • Jika tidak memungkinkan, Anda harus menggunakan status Compose untuk properti yang dapat berubah.
  • Stabil: Menunjukkan jenis yang dapat diubah. Runtime Compose tidak mengetahui apakah dan kapan properti publik atau perilaku metode jenis akan memberikan hasil yang berbeda dari pemanggilan sebelumnya.

Koleksi yang tidak dapat diubah

Alasan umum mengapa Compose menganggap class tidak stabil adalah koleksi. Seperti yang disebutkan di halaman Diagnosis masalah stabilitas, compiler Compose tidak dapat sepenuhnya memastikan bahwa koleksi seperti List, Map, dan Set benar-benar tidak dapat diubah, sehingga menandainya sebagai tidak stabil.

Untuk mengatasi hal ini, Anda dapat menggunakan koleksi yang tidak dapat diubah. Compiler Compose menyertakan dukungan untuk Koleksi Kotlinx yang Tidak Dapat Diubah. Koleksi ini dijamin tidak dapat diubah, dan compiler Compose memperlakukannya seperti itu. Library ini masih dalam versi alfa. Jadi, akan ada kemungkinan perubahan pada API-nya.

Pertimbangkan lagi class yang tidak stabil ini dari panduan Mendiagnosis masalah stabilitas:

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

Anda dapat membuat tags stabil menggunakan koleksi yang tidak dapat diubah. Di class, ubah jenis tags menjadi ImmutableSet<String>:

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

Setelah melakukannya, semua parameter class tidak dapat diubah, dan compiler Compose akan menandai class sebagai stabil.

Anotasi dengan Stable atau Immutable

Jalur yang memungkinkan untuk menyelesaikan masalah stabilitas adalah dengan menganotasi class yang tidak stabil dengan @Stable atau @Immutable.

Menganotasi class akan menggantikan apa yang akan disimpulkan tentang class Anda. Hal ini mirip dengan operator !! di Kotlin. Anda harus sangat berhati-hati dalam menggunakan anotasi ini. Mengganti perilaku compiler dapat menyebabkan Anda mengalami bug yang tidak terduga, seperti composable tidak merekomposisi seperti yang diharapkan.

Jika memungkinkan untuk membuat class Anda stabil tanpa anotasi, Anda harus berusaha mencapai stabilitas dengan cara tersebut.

Cuplikan berikut memberikan contoh minimal class data yang dianotasi sebagai tidak dapat diubah:

@Immutable
data class Snack(
…
)

Baik Anda menggunakan anotasi @Immutable atau @Stable, compiler Compose akan menandai class Snack sebagai stabil.

Class yang dianotasi dalam koleksi

Pertimbangkan composable yang menyertakan parameter jenis List<Snack>:

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

Meskipun Anda menganotasi Snack dengan @Immutable, compiler Compose masih menandai parameter snacks di HighlightedSnacks sebagai tidak stabil.

Parameter menghadapi masalah yang sama dengan class terkait jenis koleksi, compiler Compose selalu menandai parameter jenis List sebagai tidak stabil, meskipun merupakan kumpulan jenis stabil.

Anda tidak dapat menandai parameter individual sebagai stabil, atau menganotasi composable agar selalu dapat dilewati. Ada beberapa jalur ke depan.

Ada beberapa cara untuk mengatasi masalah koleksi yang tidak stabil. Sub-bagian berikut menguraikan pendekatan yang berbeda ini.

File konfigurasi

Jika ingin mematuhi kontrak stabilitas di codebase Anda, Anda dapat memilih untuk mempertimbangkan koleksi Kotlin sebagai stabil dengan menambahkan kotlin.collections.* ke file konfigurasi stabilitas Anda.

Koleksi yang tidak dapat diubah

Untuk keamanan waktu kompilasi immutability, Anda dapat menggunakan koleksi yang tidak dapat diubah kotlinx, bukan List.

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

Wrapper

Jika tidak dapat menggunakan koleksi yang tidak dapat diubah, Anda dapat membuatnya sendiri. Untuk melakukannya, gabungkan List dalam class stabil yang dianotasi. Wrapper generik kemungkinan merupakan pilihan terbaik untuk hal ini, bergantung pada persyaratan Anda.

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

Anda kemudian dapat menggunakannya sebagai jenis parameter dalam composable.

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

Solusi

Setelah melakukan salah satu pendekatan ini, compiler Compose kini menandai Composable HighlightedSnacks sebagai skippable dan 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
)

Selama rekomposisi, Compose kini dapat melewati HighlightedSnacks jika tidak ada inputnya yang berubah.

File konfigurasi stabilitas

Mulai Compose Compiler 1.5.5, file konfigurasi class yang dianggap stabil dapat diberikan pada waktu kompilasi. Hal ini memungkinkan untuk mempertimbangkan class yang tidak Anda kontrol, seperti class library standar seperti LocalDateTime, sebagai stabil.

File konfigurasi adalah file teks biasa dengan satu class per baris. Komentar, karakter pengganti tunggal dan ganda didukung. Contoh konfigurasi ditampilkan di bawah ini:

// Consider LocalDateTime stable
java.time.LocalDateTime
// Consider kotlin collections stable
kotlin.collections.*
// 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<*,_>

Untuk mengaktifkan fitur ini, teruskan jalur file konfigurasi ke opsi compiler Compose.

Groovy

kotlinOptions {
    freeCompilerArgs += [
            "-P",
            "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
                    project.absolutePath + "/compose_compiler_config.conf"
    ]
}

Kotlin

kotlinOptions {
  freeCompilerArgs += listOf(
      "-P",
      "plugin:androidx.compose.compiler.plugins.kotlin:stabilityConfigurationPath=" +
      "${project.absolutePath}/compose_compiler_config.conf"
  )
}

Karena compiler Compose berjalan di setiap modul dalam project secara terpisah, Anda dapat memberikan konfigurasi yang berbeda ke modul yang berbeda jika diperlukan. Atau, miliki satu konfigurasi di level root project dan teruskan jalur tersebut ke setiap modul.

Beberapa modul

Masalah umum lainnya terkait arsitektur multi-modul. Compiler Compose hanya dapat menyimpulkan apakah class stabil jika semua jenis non-primitif yang direferensikannya secara eksplisit ditandai sebagai stabil atau dalam modul yang juga dibuat dengan compiler Compose.

Jika lapisan data Anda berada dalam modul terpisah dari lapisan UI, yang merupakan pendekatan yang direkomendasikan, hal ini mungkin yang Anda temui.

Solusi

Untuk mengatasi masalah ini, Anda dapat melakukan salah satu pendekatan berikut:

  1. Tambahkan class ke file konfigurasi Compiler.
  2. Aktifkan compiler Compose pada modul lapisan data Anda, atau beri tag class dengan @Stable atau @Immutable jika sesuai.
    • Hal ini melibatkan penambahan dependensi Compose ke lapisan data Anda. Namun, ini hanya dependensi untuk runtime Compose, bukan untuk Compose-UI.
  3. Dalam modul UI, gabungkan class lapisan data Anda dalam class wrapper khusus UI.

Masalah yang sama juga terjadi saat menggunakan library eksternal jika tidak menggunakan compiler Compose.

Tidak semua composable dapat dilewati

Saat berusaha memperbaiki masalah stabilitas, Anda tidak boleh mencoba membuat setiap composable dapat dilewati. Jika dilakukan, akan terjadi pengoptimalan prematur yang menimbulkan lebih banyak masalah daripada yang dapat diperbaiki.

Ada banyak situasi saat iklan yang dapat dilewati tidak memiliki manfaat nyata dan dapat menyebabkan kode yang sulit dikelola. Contoh:

  • Composable yang tidak sering dikomposisi ulang, atau tidak sama sekali.
  • Composable yang dengan sendirinya hanya memanggil composable yang dapat dilewati.
  • Composable dengan sejumlah besar parameter dengan implementasi sama dengan yang mahal. Dalam hal ini, biaya pemeriksaan apakah parameter telah berubah dapat lebih besar daripada biaya rekomposisi yang murah.

Jika composable dapat dilewati, composable tersebut akan menambahkan overhead kecil yang mungkin tidak sepadan. Anda bahkan dapat menganotasi composable agar tidak dapat dimulai ulang jika Anda menentukan bahwa dapat dimulai ulang memerlukan overhead yang lebih besar daripada nilainya.