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 fungsi yang dapat dikomposisi, 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
fungsi yang dapat dikomposisi 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. Fungsi yang dapat dikomposisi harus diberi tahu dengan jelas tentang status baru
agar dapat memperbarui sesuai status tersebut.
@Composable
fun HelloContent() {
Column(modifier = Modifier.padding(16.dp)) {
Text(
text = "Hello!",
modifier = Modifier.padding(bottom = 8.dp),
style = MaterialTheme.typography.h5
)
OutlinedTextField(
value = "",
onValueChange = { },
label = { Text("Name") }
)
}
}
Jika menjalankan ini, Anda akan melihat bahwa tidak ada yang terjadi. Hal itu karena TextField
tidak diupdate sendiri—parameter akan diupdate saat parameter value
berubah. Hal ini
terjadi karena cara kerja komposisi dan rekomposisi di Compose.
Untuk mempelajari komposisi awal dan rekomposisi lebih lanjut, lihat Berpikir dalam Compose.
Status dalam komponen
Fungsi yang dapat dikomposisi bisa menyimpan satu objek dalam memori menggunakan
komponen
remember
. Nilai yang dihitung oleh remember
disimpan dalam Komposisi selama
komposisi awal, dan nilai yang disimpan dikembalikan 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
akan menjadwalkan rekomposisi fungsi yang dapat dikomposisi
yang membaca value
. Dalam kasus ExpandingCard
, setiap perubahan yang terjadi pada expanded
menyebabkan ExpandingCard
direkomposisi.
Ada tiga cara untuk mendeklarasikan objek MutableState
dalam komponen:
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 komponen 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 komponen lain atau bahkan sebagai
logika dalam pernyataan untuk mengubah composable mana yang ditampilkan. Misalnya, jika
tidak ingin menampilkan salam 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.h5
)
}
OutlinedTextField(
value = name,
onValueChange = { name = it },
label = { Text("Name") }
)
}
}
Meskipun remember
membantu Anda mempertahankan status di seluruh rekomposisi, status tidak
dipertahankan di seluruh perubahan konfigurasi. Untuk melakukannya, Anda harus menggunakan
rememberSaveable
. rememberSaveable
otomatis menyimpan nilai apa pun yang dapat
disimpan di Bundle
. Untuk nilai lain, Anda dapat meneruskan objek penghemat kustom.
Jenis status lain yang didukung
Jetpack Compose tidak mengharuskan Anda menggunakan MutableState<T>
untuk mempertahankan status.
Jetpack Compose mendukung jenis lain yang dapat diamati. Sebelum membaca jenis lain
yang dapat diamati di Jetpack Compose, Anda harus mengonversinya menjadi State<T>
agar
Jetpack Compose dapat otomatis merekomposisi saat status berubah.
Compose menghadirkan fungsi untuk membuat State<T>
dari jenis umum yang dapat diamati
yang digunakan di aplikasi Android:
Anda dapat membuat fungsi ekstensi untuk Jetpack Compose guna membaca jenis lain yang dapat
diamati jika aplikasi menggunakan class kustom yang dapat diamati. Lihat penerapan
bawaan untuk contoh cara melakukannya. Objek apa pun yang mengizinkan Jetpack Compose
untuk berlangganan ke setiap perubahan dapat dikonversi menjadi State<T>
dan dibaca
komponen.
Stateful versus stateless
Fungsi yang dapat dikomposisi yang menggunakan remember
untuk menyimpan objek akan membuat status internal,
sehingga menjadikan fungsi yang dapat dikomposisi tersebut bersifat stateful. HelloContent
adalah contoh fungsi stateful yang dapat dikomposisi
karena dapat mempertahankan dan mengubah status name
secara internal. Hal ini dapat
berguna dalam situasi saat pemanggil tidak perlu mengontrol status dan dapat
menggunakannya tanpa harus mengelola status itu sendiri. Namun, fungsi yang dapat dikomposisi dengan
status internal cenderung kurang dapat digunakan kembali dan lebih sulit diuji.
Fungsi stateless yang dapat dikomposisi adalah fungsi yang dapat dikomposisi yang tidak memiliki status apa pun. Cara mudah untuk mencapai stateless adalah dengan menggunakan pengangkatan status.
Saat mengembangkan fungsi yang dapat dikomposisi dan digunakan kembali, Anda sering kali ingin menampilkan versi stateful dan stateless fungsi yang dapat dikomposisi 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 fungsi yang dapat dikomposisi untuk menjadikan fungsi yang dapat dikomposisi bersifat stateless. Pola umum untuk status 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 seperti ExpandingCard
dengan onExpand
dan onCollapse
.
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. Ini sepenuhnya internal.
- Dapat dibagikan: Status yang diangkat dapat dibagikan dengan beberapa fungsi yang dapat dikomposisi. Misalnya
kita ingin
name
dalam fungsi yang dapat dikomposisi lainnya, pengangkatan akan memungkinkan kita melakukannya. - Dapat dicegat: pemanggil fungsi stateless yang dapat dikomposisi dapat memutuskan untuk mengabaikan atau mengubah peristiwa sebelum mengubah status.
- Dipisahkan: status untuk
ExpandingCard
stateless dapat disimpan di mana pun. Misalnya, sekarang Anda dapat memindahkanname
keViewModel
.
Dalam contoh kasus, Anda mengekstrak name
dan onValueChange
dari
HelloContent
dan menaikkan hierarkinya ke fungsi HelloScreen
yang dapat dikomposisi 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.h5
)
OutlinedTextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
}
Dengan menarik status keluar dari HelloContent
, akan lebih mudah untuk memahami tentang
fungsi yang dapat dikomposisi, 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 menurun dari HelloScreen
menjadi HelloContent
dan peristiwa naik dari HelloContent
menjadi HelloScreen
. Dengan
mengikuti alur data searah, Anda dapat memisahkan fungsi yang dapat dikomposisi yang menampilkan
status di UI dari bagian aplikasi yang menyimpan dan mengubah status.
Memulihkan status di Compose
Gunakan rememberSaveable
untuk memulihkan status UI Anda setelah suatu aktivitas atau proses
dibuat ulang. rememberSaveable
mempertahankan status di seluruh rekomposisi.
Selain itu, rememberSaveable
juga mempertahankan status
pada seluruh pembuatan ulang aktivitas dan proses.
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"))
}
}
Mengelola status dalam Compose
Pengangkatan status sederhana dapat dikelola dalam fungsi yang dapat dikomposisi itu sendiri. Namun, jika jumlah status untuk memantau peningkatan, atau muncul logika untuk menjalankan fungsi yang dapat dikomposisi, praktik yang baik untuk mendelegasikan tanggung jawab logika dan status ke class lain: pemegang status.
Bagian ini membahas cara mengelola status dengan berbagai cara di Compose. Bergantung pada kompleksitas komponen, terdapat berbagai alternatif untuk dipertimbangkan:
- Composable untuk pengelolaan status elemen UI sederhana.
- Pemegang status untuk pengelolaan status elemen UI yang kompleks. Keduanya memiliki status elemen UI dan logika UI.
- ViewModels Komponen Arsitektur sebagai jenis pemegang status khusus yang bertugas untuk menyediakan akses ke logika bisnis dan layar atau status UI.
Pemegang status tersedia dalam berbagai ukuran, bergantung pada cakupan elemen UI terkait yang dikelola, mulai dari satu widget seperti panel aplikasi bawah hingga seluruh layar. Pemegang status dapat digabung; yaitu, pemegang status dapat diintegrasikan ke pemegang status yang lain, terutama saat mengumpulkan status.
Diagram berikut menunjukkan ringkasan hubungan antara entity yang terlibat dalam pengelolaan status Compose. Bagian selanjutnya mencakup setiap entitas secara detail:
- Composable dapat bergantung pada 0 pemegang status atau lebih (yang dapat berupa objek biasa, ViewModel, atau keduanya) bergantung pada kompleksitasnya.
- Pemegang status biasa mungkin bergantung pada ViewModel jika memerlukan akses ke logika bisnis atau status layar.
- ViewModel bergantung pada lapisan bisnis atau data.
Ringkasan dependensi (opsional) untuk setiap entitas yang terlibat dalam pengelolaan status Compose.
Jenis status dan logika
Di aplikasi Android, ada berbagai jenis status yang perlu dipertimbangkan:
Status elemen UI adalah status mengangkat elemen UI. Misalnya,
ScaffoldState
menangani status komponenScaffold
.Status layar atau UI adalah hal yang perlu ditampilkan di layar. Misalnya, class
CartUiState
yang dapat berisi item Keranjang, pesan untuk ditampilkan kepada pengguna, atau tanda pemuatan. Status ini biasanya terhubung dengan lapisan hierarki lain karena berisi data aplikasi.
Selain itu, ada berbagai jenis logika:
Logika perilaku UI atau logika UI berkaitan dengan cara menampilkan perubahan status di layar. Misalnya, logika navigasi menentukan layar mana yang akan ditampilkan berikutnya, atau logika UI yang menentukan cara menampilkan pesan pengguna pada layar yang mungkin menggunakan snackbar atau toast. Logika perilaku UI harus selalu berada dalam Komposisi.
Logika bisnis adalah apa yang harus dilakukan dengan perubahan status. Misalnya, melakukan pembayaran atau menyimpan preferensi pengguna. Logika ini biasanya ditempatkan di lapisan bisnis atau data, bukan di lapisan UI.
Composable sebagai sumber kebenaran
Memiliki logika UI dan status elemen UI dalam komponen adalah pendekatan yang baik jika
status dan logikanya sederhana. Misalnya, berikut adalah penanganan MyApp
yang dapat dikomposisi
ScaffoldState
dan CoroutineScope
:
@Composable
fun MyApp() {
MyTheme {
val scaffoldState = rememberScaffoldState()
val coroutineScope = rememberCoroutineScope()
Scaffold(scaffoldState = scaffoldState) {
MyContent(
showSnackbar = { message ->
coroutineScope.launch {
scaffoldState.snackbarHostState.showSnackbar(message)
}
}
)
}
}
}
Karena ScaffoldState
berisi properti yang dapat berubah, semua interaksi dengannya
harus terjadi dalam composable MyApp
. Jika tidak, jika diteruskan ke
komponen lain,
komponen tersebut dapat memutasikan statusnya, yang tidak sesuai dengan satu sumber
prinsip kebenaran dan membuat pelacakan bug menjadi lebih sulit.
Pemegang status sebagai sumber kebenaran
Jika composable berisi logika UI kompleks yang melibatkan beberapa status elemen UI, elemen tersebut harus mendelegasikan tanggung jawab tersebut ke pemegang status. Hal ini membuat logika ini lebih mudah diuji secara terpisah, dan mengurangi kompleksitas composable. Pendekatan ini mendukung prinsip pemisahan fokus: komponen bertanggung jawab mengirimkan elemen UI, dan pemegang status berisi logika UI dan elemen UI' .
Pemegang status adalah class biasa yang dibuat dan diingat dalam Komposisi. Karena mengikuti siklus proses composable, pemegang status dapat menggunakan dependensi Compose.
Jika composable MyApp
dari bagian Composables sebagai sumber kebenaran semakin berkembang dalam tanggung jawab, kita dapat membuat
pemegang status MyAppState
untuk mengelola kompleksitasnya:
// Plain class that manages App's UI logic and UI elements' state
class MyAppState(
val scaffoldState: ScaffoldState,
val navController: NavHostController,
private val resources: Resources,
/* ... */
) {
val bottomBarTabs = /* State */
// Logic to decide when to show the bottom bar
val shouldShowBottomBar: Boolean
get() = /* ... */
// Navigation logic, which is a type of UI logic
fun navigateToBottomBarRoute(route: String) { /* ... */ }
// Show snackbar using Resources
fun showSnackbar(message: String) { /* ... */ }
}
@Composable
fun rememberMyAppState(
scaffoldState: ScaffoldState = rememberScaffoldState(),
navController: NavHostController = rememberNavController(),
resources: Resources = LocalContext.current.resources,
/* ... */
) = remember(scaffoldState, navController, resources, /* ... */) {
MyAppState(scaffoldState, navController, resources, /* ... */)
}
Karena MyAppState
mengambil dependensi, praktik yang baik adalah menyediakan
metode yang mengingat instance MyAppState
dalam Komposisi. Dalam hal
ini, fungsi rememberMyAppState
.
Sekarang, MyApp
difokuskan untuk memunculkan elemen UI, dan mendelegasikan semua logika UI
dan status elemen UI ke MyAppState
:
@Composable
fun MyApp() {
MyTheme {
val myAppState = rememberMyAppState()
Scaffold(
scaffoldState = myAppState.scaffoldState,
bottomBar = {
if (myAppState.shouldShowBottomBar) {
BottomBar(
tabs = myAppState.bottomBarTabs,
navigateToRoute = {
myAppState.navigateToBottomBarRoute(it)
}
)
}
}
) {
NavHost(navController = myAppState.navController, "initial") { /* ... */ }
}
}
}
Seperti yang dapat Anda lihat, meningkatkan tanggung jawab composable meningkatkan kebutuhan untuk pemegang status. Tanggung jawab dapat berada di logika UI, atau hanya dalam jumlah status yang harus dilacak.
ViewModel sebagai sumber kebenaran
Jika class pemegang status biasa bertanggung jawab atas logika UI dan status elemen UI, ViewModel adalah jenis pemegang status khusus yang bertanggung jawab atas:
- memberikan akses ke logika bisnis aplikasi yang biasanya ditempatkan di lapisan hierarki lain seperti lapisan bisnis dan data, serta
- menyiapkan data aplikasi untuk presentasi di layar tertentu, yang menjadi layar atau status UI.
ViewModel memiliki masa aktif yang lebih lama daripada Komposisi karena bertahan terhadap perubahan konfigurasi. ViewModel dapat mengikuti siklus proses host konten Compose–yaitu, aktivitas atau fragmen–atau siklus proses tujuan atau grafik Navigasi jika Anda menggunakan Library navigasi. Karena masa lamanya, ViewModel tidak boleh menyimpan referensi berumur panjang untuk status terikat dengan masa aktif Komposisi. Jika melakukannya, kebocoran memori bisa terjadi.
Sebaiknya composable tingkat layar menggunakan instance ViewModel untuk menyediakan akses ke logika bisnis dan menjadi sumber kebenaran untuk status UI-nya. Anda tidak boleh meneruskan instance ViewModel ke composable lain. Lihat bagian ViewModel dan pemegang status untuk mengetahui alasan ViewModel dapat digunakan untuk hal ini.
Berikut adalah contoh ViewModel yang digunakan dalam composable tingkat layar:
data class ExampleUiState(
val dataToDisplayOnScreen: List<Example> = emptyList(),
val userMessages: List<Message> = emptyList(),
val loading: Boolean = false
)
class ExampleViewModel(
private val repository: MyRepository,
private val savedState: SavedStateHandle
) : ViewModel() {
var uiState by mutableStateOf(ExampleUiState())
private set
// Business logic
fun somethingRelatedToBusinessLogic() { /* ... */ }
}
@Composable
fun ExampleScreen(viewModel: ExampleViewModel = viewModel()) {
val uiState = viewModel.uiState
/* ... */
ExampleReusableComponent(
someData = uiState.dataToDisplayOnScreen,
onDoSomething = { viewModel.somethingRelatedToBusinessLogic() }
)
}
@Composable
fun ExampleReusableComponent(someData: Any, onDoSomething: () -> Unit) {
/* ... */
Button(onClick = onDoSomething) {
Text("Do something")
}
}
ViewModel dan pemegang status
Manfaat ViewModel dalam pengembangan Android membuatnya cocok untuk memberikan akses ke logika bisnis dan menyiapkan data aplikasi untuk presentasi di layar. Yaitu, manfaatnya adalah:
- Operasi yang dipicu oleh ViewModel bertahan dari perubahan konfigurasi.
- Integrasi dengan Navigation:
- Navigation men-cache ViewModels saat layar berada di data sebelumnya. Hal ini penting agar data yang sebelumnya dimuat langsung tersedia saat Anda kembali ke tujuan. Hal ini lebih sulit dilakukan dengan pemegang status yang mengikuti siklus proses layar yang dapat dikomposisi.
- ViewModel juga dihapus saat tujuan dikeluarkan dari data sebelumnya, sehingga memastikan status Anda dibersihkan secara otomatis. Hal ini berbeda dengan memproses penghapusan yang dapat dikomposisi yang dapat terjadi karena beberapa alasan seperti membuka layar baru, karena perubahan konfigurasi, dll.
- Integrasi dengan library Jetpack lainnya, seperti Hilt.
Karena pemegang status dapat digabung dan ViewModels serta pemegang status biasa memiliki tanggung jawab yang berbeda, Anda dapat membuat composable tingkat layar untuk memiliki ViewModel yang menyediakan akses ke logika bisnis DAN pemegang status yang mengelola logika UI dan status elemen UI-nya. Karena ViewModel memiliki masa aktif lebih lama daripada pemegang status, pemegang status dapat menganggap ViewModel sebagai dependensi jika diperlukan.
Kode berikut menunjukkan ViewModel dan pemegang status biasa yang bekerja sama pada ExampleScreen
:
class ExampleState(
val lazyListState: LazyListState,
private val resources: Resources,
private val expandedItems: List<Item> = emptyList()
) {
fun isExpandedItem(item: Item): Boolean = TODO()
/* ... */
}
@Composable
fun rememberExampleState(/* ... */): ExampleState { TODO() }
@Composable
fun ExampleScreen(viewModel: ExampleViewModel = viewModel()) {
val uiState = viewModel.uiState
val exampleState = rememberExampleState()
LazyColumn(state = exampleState.lazyListState) {
items(uiState.dataToDisplayOnScreen) { item ->
if (exampleState.isExpandedItem(item)) {
/* ... */
}
/* ... */
}
}
}
Pelajari lebih lanjut
Untuk mempelajari status dan Jetpack Compose lebih lanjut, lihat referensi tambahan berikut.