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.

Membuat class tidak dapat diubah

Pertama-tama, Anda harus mencoba membuat class yang tidak stabil sepenuhnya tidak dapat diubah.

  • Tidak dapat diubah: Menunjukkan jenis yang nilai propertinya tidak dapat berubah setelah instance jenis tersebut dibuat, dan semua metode transparan secara referensi.
    • Pastikan semua properti class adalah val, bukan var, dan 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 apa pun yang dapat berubah.
  • Stabil: Menunjukkan jenis yang dapat diubah. Runtime Compose tidak mengetahui apakah dan kapan salah satu 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 dinyatakan 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 sebagaimana mestinya. Library ini masih dalam versi alfa, jadi mungkin akan ada 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 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 class Anda oleh compiler. Operator 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 saat Anda mengharapkannya.

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

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

@Immutable
data class Snack(
…
)

Baik Anda menggunakan anotasi @Immutable maupun @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 tetap 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, dan juga tidak dapat 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 Anda senang mematuhi kontrak stabilitas di codebase Anda, maka Anda dapat memilih untuk mempertimbangkan koleksi Kotlin sebagai stabil dengan menambahkan kotlin.collections.* ke file konfigurasi stabilitas Anda.

Pengumpulan yang tidak dapat diubah

Untuk keamanan waktu kompilasi yang tidak dapat diubah, Anda dapat menggunakan koleksi tetap 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 umum 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 Anda.

@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 sekarang dapat melewati HighlightedSnacks jika tidak ada inputnya yang berubah.

File konfigurasi stabilitas

Mulai dari Compose Compiler 1.5.5, file konfigurasi class yang dianggap stabil dapat disediakan pada waktu kompilasi. Hal ini memungkinkan untuk mempertimbangkan class yang tidak Anda kontrol, misalnya 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"
  )
}

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

Beberapa modul

Masalah umum lainnya melibatkan arsitektur multi-modul. Compiler Compose hanya dapat menyimpulkan apakah class stabil jika semua jenis non-primitif yang direferensikannya ditandai secara eksplisit 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 menjadi masalah 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 di modul lapisan data, atau beri tag class dengan @Stable atau @Immutable jika sesuai.
    • Hal ini melibatkan penambahan dependensi Compose ke lapisan data Anda. Namun, ini hanyalah dependensi untuk runtime Compose, bukan untuk Compose-UI.
  3. Dalam modul UI, gabungkan class lapisan data dalam class wrapper khusus UI.

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

Tidak semua composable harus dapat dilewati

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

Ada banyak situasi saat iklan yang dapat dilewati tidak memiliki manfaat nyata dan dapat menyebabkan kesulitan dalam mempertahankan kode. Contoh:

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

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