Status di aplikasi adalah nilai yang dapat berubah dari waktu ke waktu. Ini adalah definisi yang sangat luas dan mencakup semua dari database Room hingga variabel di class.
Semua aplikasi Android menampilkan status kepada pengguna. Beberapa contoh status di aplikasi Android:
- Snackbar yang muncul saat koneksi jaringan tidak dapat dibuat.
- Postingan blog dan komentar terkait.
- Animasi ripple pada tombol yang diputar saat pengguna mengkliknya.
- Stiker yang dapat digambar pengguna di atas gambar.
Jetpack Compose membantu Anda menjelaskan lokasi dan cara Anda menyimpan serta menggunakan status di aplikasi Android. Panduan ini fokus pada hubungan antara status dan composable, serta pada API yang ditawarkan Jetpack Compose untuk bekerja lebih mudah dengan status.
Status dan komposisi
Compose bersifat deklaratif dan satu-satunya cara untuk mengupdatenya adalah dengan memanggil composable yang sama dengan argumen baru. Argumen ini adalah representasi
status UI. Setiap kali status diperbarui, rekomposisi akan terjadi. Akibatnya,
hal seperti TextField
tidak otomatis diperbarui seperti dalam
tampilan berbasis XML imperatif. Composable harus diberi tahu dengan jelas tentang status baru
agar dapat memperbarui sesuai status tersebut.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
Jika menjalankan ini dan mencoba memasukkan teks, Anda akan melihat bahwa tidak ada yang terjadi. Hal ini
karena TextField
tidak memperbarui sendiri—parameter ini akan diperbarui saat parameter
value
berubah. Hal ini disebabkan oleh cara kerja komposisi dan rekomposisi di
Compose.
Untuk mempelajari komposisi awal dan rekomposisi lebih lanjut, lihat Paradigma Compose.
Status dalam composable
Fungsi composable dapat menggunakan
remember
API untuk menyimpan objek di memori. Nilai yang dihitung oleh remember
disimpan dalam Komposisi selama
komposisi awal dan nilai yang disimpan ditampilkan selama rekomposisi.
remember
dapat digunakan untuk menyimpan objek yang dapat diubah dan tidak dapat diubah.
mutableStateOf
membuat
MutableState<T>
yang dapat diamati, yakni jenis yang dapat diamati dan terintegrasi dengan runtime Compose.
interface MutableState<T> : State<T> {
override var value: T
}
Setiap perubahan pada value
menjadwalkan rekomposisi fungsi composable
yang membaca value
.
Ada tiga cara untuk mendeklarasikan objek MutableState
dalam suatu composable:
val mutableState = remember { mutableStateOf(default) }
var value by remember { mutableStateOf(default) }
val (value, setValue) = remember { mutableStateOf(default) }
Deklarasi ini setara, dan diberikan sebagai sugar sintaksis untuk berbagai penggunaan status. Anda harus memilih deklarasi yang menghasilkan kode yang paling mudah dibaca dalam composable yang ditulis.
Sintaksis delegasi by
memerlukan impor berikut:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Anda dapat menggunakan nilai yang diingat sebagai parameter untuk composable lain atau bahkan sebagai
logika dalam pernyataan untuk mengubah composable mana yang ditampilkan. Misalnya, jika tidak ingin menampilkan kata sambutan saat nama kosong, gunakan status dalam pernyataan if
:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
Meskipun remember
membantu Anda mempertahankan status di seluruh rekomposisi, status tidak
dipertahankan dalam berbagai perubahan konfigurasi. Agar status dipertahankan, Anda harus menggunakan rememberSaveable
. rememberSaveable
otomatis menyimpan nilai apa pun yang dapat
disimpan di Bundle
. Untuk nilai lain, Anda dapat meneruskan objek saver kustom.
Jenis status lain yang didukung
Compose tidak mengharuskan Anda menggunakan MutableState<T>
untuk mempertahankan status; Compose
mendukung jenis lain yang dapat diamati. Sebelum membaca jenis lain yang dapat diamati di
Compose, Anda harus mengonversinya menjadi State<T>
agar composable dapat
otomatis merekomposisi saat status berubah.
Compose menghadirkan fungsi untuk membuat State<T>
dari jenis umum yang dapat diamati
yang digunakan di aplikasi Android. Sebelum menggunakan integrasi ini, tambahkan
artefak yang sesuai seperti yang dijelaskan di bawah:
Flow
:collectAsStateWithLifecycle()
collectAsStateWithLifecycle()
mengumpulkan nilai dariFlow
dengan cara yang berbasis siklus proses, sehingga aplikasi Anda dapat menghemat resource aplikasi. Hal ini mewakili nilai yang terakhir ditampilkan dariState
Compose. Gunakan API ini sebagai cara yang direkomendasikan untuk mengumpulkan alur di aplikasi Android.Dependensi berikut diperlukan dalam file
build.gradle
(harus versi 2.6.0-beta01 atau yang lebih baru):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
-
collectAsState
mirip dengancollectAsStateWithLifecycle
, karena juga mengumpulkan nilai dariFlow
dan mengubahnya menjadi ComposeState
.Gunakan
collectAsState
untuk kode yang tidak bergantung pada platform, bukancollectAsStateWithLifecycle
, yang khusus Android.Dependensi tambahan tidak diperlukan untuk
collectAsState
, karena tersedia dicompose-runtime
. -
observeAsState()
mulai mengamatiLiveData
ini dan mewakili nilainya melaluiState
.Dependensi berikut diperlukan dalam file
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.5")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.5"
}
-
subscribeAsState()
adalah fungsi ekstensi yang mengubah streaming reaktif RxJava2 (mis.Single
,Observable
,Completable
) menjadi ComposeState
.Dependensi berikut diperlukan dalam file
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.5")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.5"
}
-
subscribeAsState()
adalah fungsi ekstensi yang mengubah streaming reaktif RxJava3 (mis.Single
,Observable
,Completable
) menjadi ComposeState
.Dependensi berikut diperlukan dalam file
build.gradle
:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.5")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.5"
}
Stateful versus stateless
Composable yang menggunakan remember
untuk menyimpan objek akan membuat status internal,
sehingga menjadikan composable tersebut bersifat stateful. HelloContent
adalah contoh composable stateful
karena dapat mempertahankan dan mengubah status name
-nya secara internal. Hal ini dapat
berguna dalam situasi saat pemanggil tidak perlu mengontrol status dan dapat
menggunakannya tanpa harus mengelola status itu sendiri. Namun, composable dengan status internal cenderung kurang dapat digunakan kembali dan lebih sulit diuji.
Composable stateless adalah composable yang tidak memiliki status apa pun. Cara mudah untuk mencapai stateless adalah dengan menggunakan pengangkatan status.
Saat mengembangkan composable yang dapat digunakan kembali, Anda sering kali ingin menampilkan versi stateful dan stateless dari composable yang sama. Versi stateful praktis bagi pemanggil yang tidak peduli dengan status, dan versi stateless diperlukan untuk pemanggil yang harus mengontrol atau mengangkat status.
Pengangkatan status
Pengangkatan status di Compose adalah pola pemindahan status ke pemanggil composable untuk menjadikan composable bersifat stateless. Pola umum untuk pengangkatan status di Jetpack Compose adalah mengganti variabel status dengan dua parameter:
value: T
: nilai saat ini yang akan ditampilkanonValueChange: (T) -> Unit
: peristiwa yang meminta perubahan nilai, denganT
yang merupakan nilai baru yang diusulkan
Namun, Anda tidak terbatas pada onValueChange
. Jika peristiwa yang lebih spesifik
sesuai untuk composable, Anda harus mendefinisikannya menggunakan lambda.
Status yang diangkat dengan cara ini memiliki beberapa properti penting:
- Satu sumber kebenaran: Dengan memindahkan status dan bukan membuat duplikatnya, kita memastikan hanya ada satu sumber kebenaran. Tindakan ini membantu menghindari bug.
- Dienkapsulasi: Hanya composable stateful yang dapat mengubah statusnya. Properti ini sepenuhnya internal.
- Dapat dibagikan: Status yang diangkat dapat dibagikan dengan beberapa composable. Jika Anda
ingin membaca
name
dalam composable lainnya, pengangkatan akan memungkinkan Anda melakukannya. - Dapat dicegat: pemanggil pada composable stateless dapat memutuskan untuk mengabaikan atau mengubah peristiwa sebelum mengubah status.
- Dipisahkan: status untuk composable stateless dapat disimpan
di mana saja. Misalnya, sekarang Anda dapat memindahkan
name
keViewModel
.
Dalam contoh kasus, Anda mengekstrak name
dan onValueChange
dari
HelloContent
dan menaikkan hierarkinya ke composable HelloScreen
yang
memanggil HelloContent
.
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
Dengan mengangkat status dari HelloContent
, akan lebih mudah untuk memahami composable, menggunakannya kembali dalam situasi berbeda, dan melakukan pengujian. HelloContent
dipisahkan
dari cara status disimpan. Pemisahan berarti jika HelloScreen
diubah atau
diganti, Anda tidak perlu mengubah cara HelloContent
diterapkan.
Pola penurunan status, dan peristiwa naik disebut
aliran data searah. Dalam kasus ini, status turun dari HelloScreen
menjadi HelloContent
dan peristiwa naik dari HelloContent
menjadi HelloScreen
. Dengan
mengikuti alur data searah, Anda dapat memisahkan composable yang menampilkan status di UI dari bagian aplikasi yang menyimpan dan mengubah status.
Lihat halaman Ke mana status sebaiknya diangkat untuk mempelajari lebih lanjut.
Memulihkan status di Compose
rememberSaveable
API berperilaku mirip dengan remember
karena
mempertahankan status di seluruh rekomposisi, dan juga di seluruh pembuatan ulang
aktivitas atau proses menggunakan mekanisme status instance yang disimpan. Misalnya, hal ini terjadi,
saat layar diputar.
Cara menyimpan status
Semua jenis data yang ditambahkan ke Bundle
disimpan secara otomatis. Jika Anda
ingin menyimpan sesuatu yang tidak dapat ditambahkan ke Bundle
, ada beberapa
opsi.
Parcelize
Solusi paling sederhana adalah menambahkan
anotasi
@Parcelize
ke objek. Objek menjadi parcelable dan dapat dijadikan paket. Misalnya,
kode ini membuat jenis data City
parcelable dan menyimpannya ke
status.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
Jika karena alasan tertentu @Parcelize
tidak cocok, Anda dapat menggunakan mapSaver
untuk
menentukan aturan sendiri guna mengonversi objek menjadi kumpulan nilai yang
dapat disimpan oleh sistem ke Bundle
.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
Agar tidak perlu menentukan kunci untuk peta, Anda juga dapat menggunakan listSaver
dan menggunakan indeksnya sebagai kunci:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
Holder status di Compose
Pengangkatan status sederhana dapat dikelola dalam fungsi composable itu sendiri. Namun, jika jumlah status yang dipantau meningkat, atau muncul logika untuk menjalankan fungsi composable, praktik yang baik untuk dilakukan adalah mendelegasikan tanggung jawab logika dan status ke class lain: holder status.
Lihat dokumentasi pengangkatan status di Compose atau, secara lebih umum, halaman Holder status dan Status UI di panduan arsitektur untuk mempelajari lebih lanjut.
Memicu ulang penghitungan fungsi remember saat kunci berubah
remember
API sering digunakan bersama MutableState
:
var name by remember { mutableStateOf("") }
Di sini, penggunaan fungsi remember
akan membuat nilai MutableState
tetap ada selama
rekomposisi.
Secara umum, remember
menggunakan parameter lambda calculation
. Saat dijalankan pertama kali,
remember
akan memanggil lambda calculation
dan menyimpan hasilnya. Selama
rekomposisi, remember
menampilkan nilai yang terakhir disimpan.
Selain status caching, Anda juga dapat menggunakan remember
untuk menyimpan objek atau hasil operasi apa pun dalam Komposisi yang mahal untuk diinisialisasi atau dihitung. Anda mungkin tidak ingin mengulangi penghitungan ini di setiap rekomposisi.
Contohnya adalah membuat objek ShaderBrush
ini, yang merupakan operasi
yang mahal:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
remember
menyimpan nilai hingga keluar dari Komposisi. Namun, ada cara untuk
membatalkan nilai yang disimpan dalam cache. remember
API juga menggunakan parameter key
atau
keys
. Jika salah satu kunci ini berubah, saat berikutnya fungsi ini merekomposisi, remember
akan membatalkan cache dan menjalankan blok lambda penghitungan lagi. Mekanisme ini memberi Anda kontrol atas masa aktif
objek dalam Komposisi. Penghitungan tetap valid hingga input
berubah, bukan sampai nilai yang diingat keluar dari Komposisi.
Contoh berikut menunjukkan cara kerja mekanisme ini.
Dalam cuplikan ini, ShaderBrush
dibuat dan digunakan sebagai gambar
latar belakang composable Box
. remember
menyimpan instance ShaderBrush
karena pembuatan ulangnya mahal, seperti yang dijelaskan sebelumnya. remember
menggunakan
avatarRes
sebagai parameter key1
, yang merupakan gambar latar yang dipilih. Jika
avatarRes
berubah, kuas akan merekomposisi dengan gambar baru, dan diterapkan lagi ke
Box
. Hal ini dapat terjadi saat pengguna memilih gambar lain sebagai
latar belakang dari pemilih.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
Dalam cuplikan berikutnya, status diangkat ke class holder status biasa
MyAppState
. Class ini mengekspos fungsi rememberMyAppState
untuk melakukan inisialisasi
instance class menggunakan remember
. Mengekspos fungsi tersebut untuk membuat
instance yang tetap ada selama rekomposisi adalah pola yang umum di Compose. Fungsi
rememberMyAppState
menerima windowSizeClass
, yang berfungsi sebagai
parameter key
untuk remember
. Jika parameter ini berubah, aplikasi perlu
membuat ulang class holder status biasa dengan nilai terbaru. Hal ini dapat terjadi jika,
misalnya, pengguna memutar perangkat.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
Compose menggunakan implementasi sama dengan class untuk menentukan apakah kunci telah berubah dan membatalkan nilai yang disimpan.
Menyimpan status dengan kunci di luar rekomposisi
rememberSaveable
API adalah wrapper di sekitar remember
yang dapat menyimpan
data dalam Bundle
. API ini memungkinkan status untuk tetap ada selama
rekomposisi, tetapi juga pembuatan ulang aktivitas dan penghentian proses yang dimulai oleh sistem.
rememberSaveable
menerima parameter input
untuk tujuan yang sama seperti
remember
yang menerima keys
. Cache dibatalkan saat salah satu input
berubah. Saat berikutnya fungsi ini merekomposisi, rememberSaveable
akan mengeksekusi ulang
blok lambda penghitungan.
Dalam contoh berikut, rememberSaveable
menyimpan userTypedQuery
hingga
typedQuery
berubah:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Mempelajari lebih lanjut
Untuk mempelajari status dan Jetpack Compose lebih lanjut, lihat referensi tambahan berikut.
Contoh
Codelab
Video
Blog
Direkomendasikan untuk Anda
- Catatan: teks link ditampilkan saat JavaScript nonaktif
- Merancang Compose UI Anda
- Menyimpan status UI di Compose
- Efek samping di Compose