Dalam halaman ini, Anda akan mempelajari siklus proses composable dan cara Compose memutuskan apakah composable memerlukan rekomposisi.
Ringkasan siklus proses
Seperti yang disebutkan dalam dokumentasi Mengelola status, Komposisi mendeskripsikan UI aplikasi Anda dan dihasilkan dengan menjalankan composable. Komposisi adalah struktur hierarki composable yang mendeskripsikan UI Anda.
Saat Jetpack Compose menjalankan composable Anda untuk pertama kalinya, selama komposisi awal, fitur ini akan terus melacak composable komponen yang Anda panggil untuk mendeskripsikan UI Anda di Komposisi. Kemudian, saat status aplikasi berubah, Jetpack Compose menjadwalkan rekomposisi. Rekomposisi adalah saat Jetpack Compose mengeksekusi ulang composable yang mungkin telah berubah sebagai respons terhadap perubahan status, lalu mengupdate Komposisi untuk mencerminkan setiap perubahan.
Komposisi hanya dapat dihasilkan oleh komposisi awal dan diperbarui dengan rekomposisi. Satu-satunya cara untuk mengubah Komposisi adalah melalui rekomposisi.
Gambar 1. Siklus proses composable dalam Komposisi. Siklus Proses tersebut memasuki Komposisi, dikomposisi ulang 0 kali atau lebih, dan keluar dari.
Rekomposisi biasanya dipicu oleh perubahan pada objek
State<T>
. Compose
melacak ini dan menjalankan semua composable di Komposisi yang membaca
State<T>
tertentu, dan semua composable yang dipanggil yang tidak dapat
dilewati.
Jika sebuah composable dipanggil beberapa kali, beberapa instance ditempatkan di Komposisi. Setiap panggilan memiliki siklus prosesnya sendiri di Komposisi.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
Gambar 2. Representasi MyComposable
dalam Komposisi. Jika sebuah
composable dipanggil beberapa kali, beberapa instance ditempatkan
di Komposisi. Elemen yang memiliki warna berbeda menunjukkan bahwa elemen tersebut adalah
instance terpisah.
Anatomi composable dalam Komposisi
Instance composable dalam Komposisi diidentifikasi oleh situs panggilan. Compiler Compose menganggap setiap situs panggilan berbeda. Memanggil composable dari beberapa situs panggilan akan membuat beberapa instance composable di Komposisi.
Jika selama rekomposisi composable memanggil composable yang berbeda dari yang dipanggil selama komposisi sebelumnya, Compose akan mengidentifikasi composable mana yang dipanggil atau tidak dipanggil dan untuk composable yang dipanggil di kedua komposisi, Compose akan menghindari pengubahan komposisi jika inputnya belum berubah.
Mempertahankan identitas sangat penting untuk mengaitkan efek samping dengan composable, sehingga dapat diselesaikan dengan baik, bukan memulai ulang untuk setiap rekomposisi.
Perhatikan contoh berikut:
@Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() // This call site affects where LoginInput is placed in Composition } @Composable fun LoginInput() { /* ... */ } @Composable fun LoginError() { /* ... */ }
Pada cuplikan kode di atas, LoginScreen
akan memanggil composable
LoginError
dan akan selalu memanggil composable LoginInput
. Setiap
panggilan memiliki situs panggilan dan posisi sumber unik, yang akan digunakan oleh compiler untuk
mengidentifikasinya secara unik.
Gambar 3. Representasi LoginScreen
dalam Komposisi saat status berubah dan terjadi rekomposisi. Warna yang sama berarti model belum dikomposisi ulang.
Meskipun LoginInput
dipanggil pertama kali lalu menjadi yang kedua, instance LoginInput
akan dipertahankan di seluruh rekomposisi. Selain itu,
karena LoginInput
tidak memiliki parameter yang telah diubah di
rekomposisi, panggilan ke LoginInput
akan dilewati oleh Compose.
Tambahkan informasi tambahan untuk membantu rekomposisi smart
Memanggil composable beberapa kali akan menambahkannya ke Komposisi beberapa kali juga. Saat memanggil composable beberapa kali dari situs panggilan yang sama, Compose tidak memiliki informasi apa pun untuk mengidentifikasi setiap panggilan secara unik ke composable tersebut, sehingga digunakan urutan eksekusi selain situs panggilan untuk menjaga instance tetap berbeda. Perilaku ini terkadang diperlukan, tetapi dalam beberapa kasus, perilaku tersebut dapat menyebabkan perilaku yang tidak diinginkan.
@Composable fun MoviesScreen(movies: List<Movie>) { Column { for (movie in movies) { // MovieOverview composables are placed in Composition given its // index position in the for loop MovieOverview(movie) } } }
Dalam contoh di atas, Compose menggunakan urutan eksekusi selain situs panggilan agar instance dalam Komposisi tetap berbeda. Jika movie
baru ditambahkan ke bagian bawah daftar, Compose dapat kembali menggunakan instance yang sudah ada pada Komposisi karena lokasinya tidak ada dalam daftar, sehingga input movie
sama untuk instance tersebut.
Gambar 4. Representasi MoviesScreen
di Komposisi saat elemen baru ditambahkan ke bagian bawah daftar. Composable MovieOverview
dalam Komposisi dapat digunakan kembali. Warna yang sama di MovieOverview
berarti composable belum dikomposisi ulang.
Namun, jika daftar movies
berubah dengan menambahkan ke bagian atas atau tengah dari daftar, menghapus atau menyusun ulang item akan menyebabkan rekomposisi di semua panggilan MovieOverview
yang posisi parameter inputnya telah berubah dalam daftar. Hal tersebut sangat penting jika, misalnya, MovieOverview
mengambil gambar film menggunakan efek samping. Jika rekomposisi terjadi saat efek sedang berlangsung, rekomposisi akan dibatalkan dan akan dimulai lagi.
@Composable fun MovieOverview(movie: Movie) { Column { // Side effect explained later in the docs. If MovieOverview // recomposes, while fetching the image is in progress, // it is cancelled and restarted. val image = loadNetworkImage(movie.url) MovieHeader(image) /* ... */ } }
Gambar 5. Representasi MoviesScreen
dalam Komposisi saat elemen baru ditambahkan ke daftar. Composable MovieOverview
tidak dapat digunakan kembali dan semua efek samping akan dimulai ulang. Warna yang berbeda di MovieOverview
berarti composable telah dikomposisi ulang.
Idealnya, kita ingin menganggap identitas instance MovieOverview
terkait dengan identitas movie
yang diteruskan ke instance tersebut. Jika kita mengurutkan ulang daftar film, idealnya kita juga akan mengurutkan ulang instance dalam hierarki Komposisi alih-alih mengomposisi ulang setiap composable MovieOverview
dengan instance film yang berbeda. Compose memberikan cara untuk memberi tahu runtime nilai apa saja yang ingin Anda gunakan untuk mengidentifikasi bagian hierarki tertentu: composable key
.
Dengan menggabungkan blok kode dengan panggilan ke composable kunci dengan satu atau beberapa nilai yang diteruskan, nilai tersebut akan digabungkan agar dapat digunakan untuk mengidentifikasi instance tersebut dalam komposisi. Nilai untuk key
tidak harus unik secara global, tetapi hanya perlu unik di antara pemanggilan composable di situs panggilan. Jadi, dalam contoh ini, setiap movie
harus memiliki key
yang unik di antara movies
; tidak menjadi masalah jika aplikasi tersebut membagikan key
dengan beberapa composable lain di tempat lain dalam aplikasi.
@Composable fun MoviesScreenWithKey(movies: List<Movie>) { Column { for (movie in movies) { key(movie.id) { // Unique ID for this movie MovieOverview(movie) } } } }
Dengan begitu, meskipun elemen dalam daftar berubah, Compose mengenali panggilan individual ke MovieOverview
dan dapat menggunakannya kembali.
Gambar 6. Representasi MoviesScreen
dalam Komposisi saat elemen baru ditambahkan ke daftar. Karena composable MovieOverview
memiliki kunci unik, Compose mengenali instance MovieOverview
yang belum berubah, dan dapat menggunakannya kembali; efek sampingnya akan terus berjalan.
Beberapa composable memiliki dukungan bawaan untuk composable key
. Misalnya, LazyColumn
menerima penetapan key
kustom di DSL items
.
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
Melewati jika input belum berubah
Selama rekomposisi, beberapa fungsi composable yang memenuhi syarat dapat memiliki eksekusi yang dilewati sepenuhnya jika inputnya belum berubah dari komposisi sebelumnya.
Fungsi composable memenuhi syarat untuk dilewati kecuali:
- Fungsi memiliki jenis nilai yang ditampilkan non-
Unit
- Fungsi dianotasi dengan
@NonRestartableComposable
atau@NonSkippableComposable
- Parameter yang diperlukan adalah jenis yang tidak stabil
Ada mode compiler eksperimental, Strong Skipping, yang melonggarkan persyaratan terakhir.
Agar jenis dianggap stabil, jenis tersebut harus mematuhi kontrak berikut:
- Hasil
equals
untuk dua instance akan selamanya sama untuk dua instance yang sama. - Jika properti publik dari jenis tersebut berubah, Komposisi akan diberi tahu.
- Semua jenis properti publik juga stabil.
Ada beberapa jenis umum penting yang termasuk dalam kontrak ini yang
akan ditangani oleh compiler Compose sebagai stabil, meskipun tidak secara eksplisit
ditandai sebagai stabil menggunakan anotasi @Stable
:
- Semua jenis nilai primitif:
Boolean
,Int
,Long
,Float
,Char
, dll. - String
- Semua jenis fungsi (lambda)
Semua jenis ini dapat mengikuti kontrak stabil karena tidak dapat diubah. Karena jenis yang tidak dapat diubah tidak pernah berubah, jenis tersebut tidak perlu memberi tahu Komposisi tentang perubahan tersebut, sehingga jauh lebih mudah untuk mengikuti kontrak ini.
Salah satu jenis penting yang stabil tetapi dapat diubah adalah jenis MutableState
dari Compose. Jika nilai disimpan dalam MutableState
, objek status secara keseluruhan dianggap stabil karena Compose akan diberi tahu tentang perubahan apa pun pada properti .value
dari State
.
Jika semua jenis yang diteruskan sebagai parameter ke composable dapat stabil, nilai parameter akan dibandingkan untuk persamaan berdasarkan posisi composable di hierarki UI. Rekomposisi dilewati jika semua nilai tidak berubah sejak panggilan sebelumnya.
Compose menganggap jenis stabil hanya jika dapat membuktikannya. Misalnya, antarmuka umumnya diperlakukan sebagai tidak stabil, dan jenis dengan properti publik yang dapat diubah yang penerapannya tidak dapat diubah juga tidak stabil.
Jika Compose tidak dapat menyimpulkan jenisnya stabil, tetapi Anda ingin memaksa Compose untuk memperlakukannya sebagai stabil, tandai dengan anotasi @Stable
.
// Marking the type as stable to favor skipping and smart recompositions. @Stable interface UiState<T : Result<T>> { val value: T? val exception: Throwable? val hasError: Boolean get() = exception != null }
Pada cuplikan kode di atas, karena UiState
adalah antarmuka, Compose biasanya dapat menganggap jenis ini tidak stabil. Dengan menambahkan anotasi @Stable
, Anda memberi tahu Compose bahwa jenis ini stabil, dengan memungkinkan Compose mendukung rekomposisi smart. Ini juga berarti bahwa Compose akan memperlakukan semua
implementasinya sebagai stabil jika antarmuka digunakan sebagai jenis parameter.
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Status dan Jetpack Compose
- Efek samping di Compose
- Menyimpan status UI di Compose