1. Sebelum memulai
Codelab ini menjelaskan konsep inti terkait penggunaan Status di Jetpack Compose. Codelab ini menunjukkan cara status aplikasi menentukan apa yang ditampilkan di UI, cara Compose memperbarui UI saat status berubah dengan menggunakan API yang berbeda, cara mengoptimalkan struktur fungsi composable, dan menggunakan ViewModel di lingkup Compose.
Prasyarat
- Pengetahuan tentang sintaksis Kotlin.
- Pemahaman dasar tentang Compose (Anda dapat memulai dengan tutorial Jetpack Compose).
- Pemahaman dasar tentang
ViewModel
Komponen Arsitektur.
Yang akan Anda pelajari
- Cara memikirkan status dan peristiwa di UI Jetpack Compose.
- Cara Compose menggunakan status untuk menentukan elemen mana yang akan ditampilkan di layar.
- Apa itu pengangkatan status.
- Cara kerja fungsi composable stateful dan stateless.
- Cara Compose melacak status secara otomatis dengan API
State<T>
. - Cara kerja memori dan status internal dalam fungsi composable: menggunakan API
remember
danrememberSaveable
. - Cara menggunakan daftar dan status: menggunakan API
mutableStateListOf
dantoMutableStateList
. - Cara menggunakan
ViewModel
dengan Compose.
Yang akan Anda butuhkan
Direkomendasikan/Opsional
- Baca Paradigma Compose.
- Ikuti codelab dasar-dasar Jetpack Compose sebelum codelab ini. Kita akan melakukan rangkuman lengkap Status dalam codelab ini.
Yang akan Anda build
Anda akan menerapkan aplikasi Kesehatan sederhana:
Aplikasi ini memiliki dua fungsi utama:
- Penghitung air untuk melacak asupan air Anda.
- Daftar tugas kesehatan yang harus dilakukan sepanjang hari.
Untuk dukungan selengkapnya saat Anda mempelajari codelab ini, lihat kode berikut:
2. Memulai persiapan
Memulai project Compose baru
- Untuk memulai project Compose baru, buka Android Studio.
- Jika baru memulai di jendela Welcome to Android Studio, klik Start a new Android Studio project. Jika sudah membuka project Android Studio, pilih File > New > New Project dari panel menu.
- Untuk project baru, pilih Empty Activity dari template yang tersedia.
- Klik Next dan konfigurasikan project Anda, dengan nama "BasicStateCodelab".
Pastikan Anda memilih minimumSdkVersion dengan minimal level API 21, yang merupakan API Compose minimum yang didukung.
Saat Anda memilih template Empty Compose Activity, Android Studio akan menyiapkan hal berikut untuk project:
- Class
MainActivity
yang dikonfigurasi dengan fungsi composable yang menampilkan beberapa teks di layar. - File
AndroidManifest.xml
, yang menentukan izin, komponen, dan resource kustom aplikasi Anda. - File
build.gradle.kts
danapp/build.gradle.kts
berisi opsi dan dependensi yang diperlukan untuk Compose.
Solusi untuk codelab
Anda dapat memperoleh kode solusi BasicStateCodelab
dari GitHub:
$ git clone https://github.com/android/codelab-android-compose
Atau, Anda dapat mendownload repositori sebagai file ZIP.
Anda akan menemukan kode solusi di project BasicStateCodelab
. Sebaiknya ikuti codelab ini langkah demi langkah sesuai kemampuan Anda sendiri dan lihat solusinya jika memerlukan bantuan. Selama codelab, Anda akan melihat cuplikan kode yang harus ditambahkan ke project Anda.
3. Status dalam Compose
"Status" 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:
- Pesan terbaru yang diterima di aplikasi chat.
- Foto profil pengguna.
- Posisi scroll dalam daftar item.
Mari kita mulai menulis aplikasi Kesehatan Anda.
Untuk mempermudah, selama codelab:
- Anda dapat menambahkan semua file Kotlin dalam paket
com.codelabs.basicstatecodelab
root modulapp
. Namun, dalam aplikasi produksi, file harus terstruktur secara logis dalam sub-paket. - Anda akan melakukan hardcode semua string secara inline dalam cuplikan. Dalam aplikasi yang sebenarnya, string harus ditambahkan sebagai resource string dalam file
strings.xml
dan direferensikan menggunakanstringResource
API Compose.
Bagian pertama fungsi yang perlu Anda build adalah penghitung air untuk menghitung jumlah gelas air yang Anda konsumsi sepanjang hari.
Buat fungsi composable yang disebut WaterCounter
yang berisi composable Text
yang menampilkan jumlah gelas. Jumlah gelas harus disimpan dalam nilai bernama count
, yang dapat Anda hardcode untuk saat ini.
Buat file baru WaterCounter.kt
dengan fungsi composable WaterCounter
, seperti ini:
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
val count = 0
Text(
text = "You've had $count glasses.",
modifier = modifier.padding(16.dp)
)
}
Mari membuat fungsi composable yang menampilkan seluruh layar, dengan dua bagian di dalamnya, penghitung air dan daftar tugas kesehatan. Untuk saat ini, kita akan tambahkan penghitung.
- Buat file
WellnessScreen.kt
, yang mewakili layar utama, dan panggil fungsiWaterCounter
:
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
WaterCounter(modifier)
}
- Buka
MainActivity.kt
. Hapus composableGreeting
danDefaultPreview
. Panggil composableWellnessScreen
yang baru dibuat di dalam bloksetContent
Aktivitas, seperti ini:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BasicStateCodelabTheme {
// A surface container using the 'background' color from the theme
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
WellnessScreen()
}
}
}
}
}
- Jika menjalankan aplikasi sekarang, Anda akan melihat layar penghitung air dasar dengan jumlah gelas air yang di-hardcode.
Status fungsi composable WaterCounter
adalah variabel count
. Namun, memiliki status statis tidak terlalu berguna karena tidak dapat dimodifikasi. Untuk mengatasi hal ini, Anda akan menambahkan Button
untuk meningkatkan jumlah dan melacak jumlah gelas air yang Anda konsumsi sepanjang hari.
Setiap tindakan yang menyebabkan perubahan status disebut "peristiwa" dan kita akan mempelajari hal ini lebih lanjut di bagian berikutnya.
4. Peristiwa di Compose
Kita membahas status sebagai nilai apa pun yang berubah dari waktu ke waktu, misalnya, pesan terakhir yang diterima di aplikasi chat. Namun, apa yang menyebabkan status diperbarui? Di aplikasi Android, status diperbarui sebagai respons terhadap peristiwa.
Peristiwa adalah input yang dihasilkan dari luar atau di dalam aplikasi, seperti:
- Misalnya, pengguna berinteraksi dengan UI dengan menekan tombol.
- Faktor lain, seperti sensor yang mengirimkan nilai baru, atau respons jaringan.
Meskipun status aplikasi menawarkan deskripsi tentang hal yang akan ditampilkan di UI, peristiwa adalah mekanisme yang digunakan untuk mengubah status, sehingga menghasilkan perubahan pada UI.
Peristiwa memberi tahu bagian program bahwa sesuatu telah terjadi. Di semua aplikasi Android, terdapat loop update UI inti yang berjalan seperti ini:
- Peristiwa – Peristiwa dihasilkan oleh pengguna atau bagian lain dari program.
- Mengupdate Status – Pengendali peristiwa mengubah status yang digunakan oleh UI.
- Menampilkan Status – UI diupdate untuk menampilkan status baru.
Mengelola status di Compose adalah tentang memahami cara status dan peristiwa saling berinteraksi.
Sekarang, tambahkan tombol sehingga pengguna dapat mengubah status dengan menambahkan lebih banyak gelas air.
Buka fungsi composable WaterCounter
untuk menambahkan Button
di bawah label kita Text
. Column
akan membantu Anda menyelaraskan Text
secara vertikal dengan composable Button
. Anda dapat memindahkan padding eksternal ke composable Column
dan menambahkan beberapa padding tambahan ke bagian atas Button
sehingga terpisah dari Teks.
Fungsi composable Button
menerima fungsi lambdaonClick
- ini adalah peristiwa yang terjadi saat tombol diklik. Anda akan melihat contoh fungsi lambda lainnya nanti.
Ubah count
menjadi var
, bukan val
agar dapat diubah.
import androidx.compose.material3.Button
import androidx.compose.foundation.layout.Column
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var count = 0
Text("You've had $count glasses.")
Button(onClick = { count++ }, Modifier.padding(top = 8.dp)) {
Text("Add one")
}
}
}
Saat Anda menjalankan aplikasi dan mengklik tombol, perhatikan bahwa tidak ada yang terjadi. Menetapkan nilai yang berbeda untuk variabel count
tidak akan membuat Compose mendeteksinya sebagai perubahan status sehingga tidak ada yang terjadi. Hal ini terjadi karena Anda belum memberi tahu Compose bahwa Compose harus menggambar ulang layar (yaitu, "mengomposisi ulang" fungsi composable), saat status berubah. Anda akan memperbaikinya di langkah berikutnya.
5. Memori dalam fungsi composable
Aplikasi Compose mengubah data menjadi UI dengan memanggil fungsi composable. Kami merujuk pada Komposisi sebagai deskripsi UI yang dibangun oleh Compose saat menjalankan fungsi composable. Jika terjadi perubahan status, Compose akan menjalankan kembali fungsi composable yang terpengaruh dengan status baru, sehingga membuat UI yang diupdate—ini disebut rekomposisi. Compose juga melihat data yang diperlukan setiap composable, sehingga hanya merekomposisi komponen yang datanya telah berubah dan melewati komponen yang tidak terpengaruh.
Agar dapat melakukannya, Compose perlu mengetahui status yang harus dilacak, sehingga saat menerima update, Compose dapat menjadwalkan rekomposisi.
Compose memiliki sistem pelacakan status khusus yang menjadwalkan rekomposisi untuk setiap composable yang membaca status tertentu. Hal ini memungkinkan Compose menjadi terperinci dan hanya merekomposisi fungsi composable yang perlu berubah, bukan seluruh UI. Hal ini dilakukan dengan melacak tidak hanya "penulisan" (yaitu, perubahan status), tetapi juga "membaca" status.
Gunakan jenis State
dan MutableState
Compose agar status dapat diamati oleh Compose.
Compose melacak setiap composable yang membaca properti value
Status dan memicu rekomposisi saat value
-nya berubah. Anda dapat menggunakan fungsi mutableStateOf
untuk membuat MutableState
yang dapat diamati. Fungsi ini menerima nilai awal sebagai parameter yang digabungkan dalam objek State
, yang kemudian membuat value
-nya dapat diamati.
Update composable WaterCounter
, sehingga count
menggunakan mutableStateOf
API dengan 0
sebagai nilai awal. Saat mutableStateOf
menampilkan jenis MutableState
, Anda dapat memperbarui value
-nya untuk memperbarui status, dan Compose akan memicu rekomposisi ke fungsi-fungsi tempat value
-nya dibaca.
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
// Changes to count are now tracked by Compose
val count: MutableState<Int> = mutableStateOf(0)
Text("You've had ${count.value} glasses.")
Button(onClick = { count.value++ }, Modifier.padding(top = 8.dp)) {
Text("Add one")
}
}
}
Seperti yang disebutkan sebelumnya, setiap perubahan pada count
akan menjadwalkan rekomposisi fungsi composable yang membaca value
count
secara otomatis. Dalam hal ini, WaterCounter
dikomposisi ulang setiap kali tombol diklik.
Jika menjalankan aplikasi sekarang, Anda akan melihat lagi bahwa belum ada yang terjadi.
Penjadwalan rekomposisi berfungsi dengan baik. Namun, saat rekomposisi terjadi, variabel count
akan diinisialisasi ulang ke 0, sehingga kita memerlukan cara untuk mempertahankan nilai ini di seluruh rekomposisi.
Untuk itu, kita dapat menggunakan fungsi inline composable remember
. Nilai yang dihitung oleh remember
disimpan dalam Komposisi selama komposisi awal, dan nilai yang disimpan juga disimpan di seluruh rekomposisi.
Biasanya remember
dan mutableStateOf
digunakan bersama-sama dalam fungsi composable.
Ada beberapa cara yang setara untuk menulis ini seperti yang ditampilkan dalam dokumentasi Status Compose.
Ubah WaterCounter
, yang mengelilingi panggilan ke mutableStateOf
dengan fungsi composable inline remember
:
import androidx.compose.runtime.remember
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
val count: MutableState<Int> = remember { mutableStateOf(0) }
Text("You've had ${count.value} glasses.")
Button(onClick = { count.value++ }, Modifier.padding(top = 8.dp)) {
Text("Add one")
}
}
}
Atau, kita dapat menyederhanakan penggunaan count
dengan menggunakan properti yang didelegasikan Kotlin.
Anda dapat menggunakan kata kunci by untuk menentukan count
sebagai var. Menambahkan impor pengambil dan penyetel memungkinkan kita membaca dan mengubah count
secara tidak langsung tanpa secara eksplisit merujuk ke properti value
MutableState
setiap saat.
Sekarang WaterCounter
terlihat seperti ini:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var count by remember { mutableStateOf(0) }
Text("You've had $count glasses.")
Button(onClick = { count++ }, Modifier.padding(top = 8.dp)) {
Text("Add one")
}
}
}
Anda harus memilih sintaksis yang menghasilkan kode yang paling mudah dibaca dalam composable yang ditulis.
Sekarang, mari kita periksa apa yang telah kita lakukan sejauh ini:
- Menentukan variabel yang diingat dari waktu ke waktu yang disebut
count
. - Membuat tampilan teks yang memberi tahu pengguna angka yang kita ingat.
- Menambahkan tombol yang menaikkan angka yang kita ingat setiap kali diklik.
Pengaturan ini membentuk feedback loop aliran data dengan pengguna:
- UI menampilkan status kepada pengguna (jumlah saat ini ditampilkan sebagai teks).
- Pengguna menghasilkan peristiwa yang digabungkan dengan status yang ada untuk menghasilkan status baru (mengklik tombol akan menambahkan status ke jumlah saat ini)
Penghitung Anda sudah siap dan berfungsi!
6. UI berbasis status
Compose adalah framework UI deklaratif. Daripada menghapus komponen UI atau mengubah visibilitasnya saat status berubah, kami menjelaskan bagaimana UI berada dalam kondisi status tertentu. Akibat dari rekomposisi yang dipanggil dan UI yang diperbarui, composable mungkin akhirnya masuk atau keluar dari Komposisi.
Pendekatan ini menghindari kompleksitas pembaruan tampilan secara manual seperti yang Anda lakukan pada sistem View. Tampilan ini juga tidak terlalu rentan mengalami error, karena Anda tidak lupa memperbarui tampilan berdasarkan status baru, karena hal ini terjadi secara otomatis.
Jika fungsi composable dipanggil selama komposisi awal atau dalam rekomposisi, kita mengatakan bahwa fungsi tersebut ada dalam Komposisi. Fungsi composable yang tidak dipanggil—misalnya, karena fungsi dipanggil di dalam pernyataan if dan kondisi tidak terpenuhi—-tidak ada dalam Komposisi.
Anda dapat mempelajari lebih lanjut siklus proses composable dalam dokumentasi.
Output Komposisi adalah struktur hierarki yang mendeskripsikan UI.
Anda dapat memeriksa tata letak aplikasi yang dibuat oleh Compose menggunakan alat Layout Inspector Android Studio, yang akan Anda lakukan berikutnya.
Untuk mendemonstrasikan ini, ubah kode untuk menampilkan UI berdasarkan status. Buka WaterCounter
dan tampilkan Text
jika count
lebih besar dari 0:
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var count by remember { mutableStateOf(0) }
if (count > 0) {
// This text is present if the button has been clicked
// at least once; absent otherwise
Text("You've had $count glasses.")
}
Button(onClick = { count++ }, Modifier.padding(top = 8.dp)) {
Text("Add one")
}
}
}
Jalankan aplikasi dan buka alat Layout Inspector Android Studio dengan membuka Tools > Layout Inspector.
Anda akan melihat layar terpisah: hierarki komponen di sebelah kiri dan pratinjau aplikasi di sebelah kanan.
Jelajahi hierarki dengan mengetuk elemen root BasicStateCodelabTheme
di sebelah kiri layar. Luaskan seluruh hierarki komponen dengan mengklik tombol Expand all.
Mengklik elemen di layar sebelah kanan akan membuka elemen hierarki yang sesuai.
Jika Anda menekan tombol Add one di aplikasi:
- Jumlah meningkat menjadi 1 dan status berubah.
- Rekomposisi dipanggil.
- Layar dikomposisi ulang dengan elemen baru.
Saat Anda memeriksa hierarki komponen dengan alat Layout Inspector Android Studio, kini Anda juga melihat composable Text
:
Status menentukan elemen mana yang ditampilkan di UI pada waktu tertentu.
Bagian UI yang berbeda dapat bergantung pada status yang sama. Ubah Button
sehingga diaktifkan hingga count
adalah 10, lalu dinonaktifkan (dan Anda mencapai sasaran untuk hari tersebut). Gunakan parameter enabled
Button
untuk melakukannya.
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
...
Button(onClick = { count++ }, Modifier.padding(top = 8.dp), enabled = count < 10) {
...
}
Jalankan aplikasi sekarang. Perubahan pada status count
menentukan apakah Text
akan ditampilkan atau tidak, dan apakah Button
diaktifkan atau dinonaktifkan.
7. Remember dalam Komposisi
remember
menyimpan objek dalam Komposisi, dan melupakan objek jika lokasi sumber tempat remember
dipanggil tidak dipanggil lagi selama rekomposisi.
Untuk memvisualisasikan perilaku ini, Anda akan menerapkan fungsi berikut di aplikasi: saat pengguna meminum setidaknya satu gelas air, tampilkan tugas kesehatan yang dapat dilakukan pengguna, yang juga dapat ditutup. Karena composable harus berukuran kecil dan dapat digunakan kembali, buat composable baru bernama WellnessTaskItem
yang menampilkan tugas kesehatan berdasarkan string yang diterima sebagai parameter, beserta tombol ikon Close.
Buat file baru WellnessTaskItem.kt
, lalu tambahkan kode berikut. Anda akan menggunakan fungsi composable ini nanti di codelab.
import androidx.compose.foundation.layout.Row
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.compose.foundation.layout.padding
@Composable
fun WellnessTaskItem(
taskName: String,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier, verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier.weight(1f).padding(start = 16.dp),
text = taskName
)
IconButton(onClick = onClose) {
Icon(Icons.Filled.Close, contentDescription = "Close")
}
}
}
Fungsi WellnessTaskItem
menerima deskripsi tugas dan fungsi lambda onClose
(sama seperti composable Button
bawaan yang menerima onClick
).
WellnessTaskItem
terlihat seperti ini:
Untuk meningkatkan kualitas aplikasi kita dengan lebih banyak fitur, update WaterCounter
untuk menampilkan WellnessTaskItem
saat count
> 0.
Jika count
lebih besar dari 0, tentukan variabel showTask
yang menentukan apakah akan menampilkan WellnessTaskItem
atau tidak, dan lakukan inisialisasi ke benar.
Tambahkan pernyataan if baru untuk menampilkan WellnessTaskItem
jika showTask
benar. Gunakan API yang Anda pelajari di bagian sebelumnya untuk memastikan nilai showTask
bertahan setelah proses rekomposisi.
@Composable
fun WaterCounter() {
Column(modifier = Modifier.padding(16.dp)) {
var count by remember { mutableStateOf(0) }
if (count > 0) {
var showTask by remember { mutableStateOf(true) }
if (showTask) {
WellnessTaskItem(
onClose = { },
taskName = "Have you taken your 15 minute walk today?"
)
}
Text("You've had $count glasses.")
}
Button(onClick = { count++ }, enabled = count < 10) {
Text("Add one")
}
}
}
Gunakan fungsi lambda onClose
WellnessTaskItem
, sehingga saat tombol X ditekan, variabel showTask
akan berubah menjadi false
dan tugas tidak ditampilkan lagi.
...
WellnessTaskItem(
onClose = { showTask = false },
taskName = "Have you taken your 15 minute walk today?"
)
...
Selanjutnya, tambahkan Button
baru dengan teks "Clear water count" dan tempatkan di samping Button
"Add one". Row
dapat membantu menyelaraskan kedua tombol tersebut. Anda juga dapat menambahkan beberapa padding ke Row
. Saat tombol "Clear water count" ditekan, variabel count
direset ke 0.
Fungsi composable WaterCounter
yang sudah selesai akan terlihat seperti ini:
import androidx.compose.foundation.layout.Row
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var count by remember { mutableStateOf(0) }
if (count > 0) {
var showTask by remember { mutableStateOf(true) }
if (showTask) {
WellnessTaskItem(
onClose = { showTask = false },
taskName = "Have you taken your 15 minute walk today?"
)
}
Text("You've had $count glasses.")
}
Row(Modifier.padding(top = 8.dp)) {
Button(onClick = { count++ }, enabled = count < 10) {
Text("Add one")
}
Button(
onClick = { count = 0 },
Modifier.padding(start = 8.dp)) {
Text("Clear water count")
}
}
}
}
Saat Anda menjalankan aplikasi, layar akan menampilkan status awal:
Di sebelah kanan, kami memiliki versi sederhana dari hierarki komponen yang akan membantu Anda menganalisis apa yang terjadi saat perubahan status. count
dan showTask
adalah nilai yang diingat.
Sekarang Anda dapat mengikuti langkah-langkah ini di aplikasi:
- Tekan tombol Add one. Proses ini meningkatkan
count
(ini menyebabkan rekomposisi), laluWellnessTaskItem
dan penghitungText
mulai ditampilkan.
- Tekan X dari komponen
WellnessTaskItem
(ini menyebabkan rekomposisi lain).showTask
sekarang salah, yang berartiWellnessTaskItem
tidak ditampilkan lagi.
- Tekan tombol Add one (rekomposisi lain).
showTask
mengingat Anda telah menutupWellnessTaskItem
di rekomposisi berikutnya jika Anda terus menambahkan gelas.
- Tekan tombol Clear water count untuk mereset
count
ke 0 dan menyebabkan rekomposisi.Text
yang menampilkancount
, dan semua kode yang terkait denganWellnessTaskItem
, tidak dipanggil dan keluar dari Komposisi.
showTask
dilupakan karena lokasi kode tempatshowTask
remember dipanggil, tidak dipanggil. Anda kembali ke langkah pertama.
- Tekan tombol Add one yang membuat
count
lebih besar dari 0 (rekomposisi).
- Composable
WellnessTaskItem
akan ditampilkan lagi, karena nilaishowTask
sebelumnya dilupakan saat keluar dari Komposisi di atas.
Bagaimana jika kita butuh showTask
agar dipertahankan setelah count
kembali ke 0, lebih lama dari yang diizinkan oleh remember
(yaitu, meskipun lokasi kode tempat remember
dipanggil, tidak dipanggil selama rekomposisi)? Kita akan mempelajari cara memperbaiki skenario ini dan contoh lainnya di bagian berikutnya.
Setelah Anda memahami cara UI dan status direset saat keluar dari Komposisi, hapus kode dan kembali ke WaterCounter
yang Anda miliki di awal bagian ini:
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
var count by remember { mutableStateOf(0) }
if (count > 0) {
Text("You've had $count glasses.")
}
Button(onClick = { count++ }, Modifier.padding(top = 8.dp), enabled = count < 10) {
Text("Add one")
}
}
}
8. Memulihkan status di Compose
Jalankan aplikasi, tambahkan beberapa gelas air ke penghitung, lalu putar perangkat Anda. Pastikan Anda mengaktifkan setelan Putar Otomatis perangkat.
Karena Aktivitas dibuat ulang setelah perubahan konfigurasi (dalam hal ini, orientasi), status yang disimpan akan dilupakan: penghitung menghilang karena kembali ke 0.
Hal yang sama terjadi jika Anda mengubah bahasa, beralih antara mode gelap dan terang, atau perubahan konfigurasi lainnya yang menyebabkan Android membuat ulang Aktivitas yang berjalan.
Meskipun remember
membantu Anda mempertahankan status dalam berbagai rekomposisi, status tidak dipertahankan dalam berbagai perubahan konfigurasi. Untuk melakukannya, Anda harus menggunakan rememberSaveable
, bukan remember
.
rememberSaveable
otomatis menyimpan nilai apa pun yang dapat disimpan di Bundle
. Untuk nilai lain, Anda dapat meneruskan objek penghemat kustom. Untuk informasi selengkapnya tentang Memulihkan status di Compose, lihat dokumentasi.
Di WaterCounter
, ganti remember
dengan rememberSaveable
:
import androidx.compose.runtime.saveable.rememberSaveable
@Composable
fun WaterCounter(modifier: Modifier = Modifier) {
...
var count by rememberSaveable { mutableStateOf(0) }
...
}
Jalankan aplikasi sekarang dan coba beberapa perubahan konfigurasi. Anda akan melihat penghitung disimpan dengan benar.
Pembuatan ulang Aktivitas hanyalah salah satu kasus penggunaan rememberSaveable
. Kita akan menjelajahi kasus penggunaan lain nanti saat menangani daftar.
Pertimbangkan apakah akan menggunakan remember
atau rememberSaveable
, bergantung pada status aplikasi dan kebutuhan UX Anda.
9. Pengangkatan status
Composable yang menggunakan remember
untuk menyimpan objek berisi status internal, yang membuat composable tersebut menjadi stateful. Hal ini dapat berguna dalam situasi saat pemanggil tidak perlu mengontrol status dan dapat menggunakannya tanpa harus mengelola status itu sendiri. Namun, fungsi composable dengan status internal cenderung kurang dapat digunakan kembali dan lebih sulit diuji.
Composable yang tidak memiliki status apa pun disebut composable stateless. Cara mudah untuk membuat composable stateless adalah dengan menggunakan pengangkatan status.
Pengangkatan status di Compose adalah pola pemindahan status ke pemanggil fungsi composable untuk menjadikan fungsi 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 ditampilkan
- onValueChange: (T) -> Unit - peristiwa yang meminta perubahan nilai dengan nilai baru T
dengan nilai ini mewakili status apa pun yang dapat diubah.
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.
- Dapat dibagikan: Status yang diangkat dapat dibagikan ke beberapa fungsi composable.
- Dapat dicegat: Pemanggil fungsi composable stateless dapat memutuskan untuk mengabaikan atau mengubah peristiwa sebelum mengubah status.
- Dipisahkan: Status untuk fungsi composable stateless dapat disimpan di mana saja. Misalnya, di ViewModel.
Coba terapkan untuk WaterCounter
sehingga dapat memanfaatkan semua hal di atas.
Stateful vs Stateless
Jika semua status dapat diekstrak dari fungsi composable, fungsi composable yang dihasilkan akan disebut stateless.
Faktorkan ulang composable WaterCounter
dengan memisahkannya menjadi dua bagian: Penghitung stateful dan stateless.
Peran StatelessCounter
adalah menampilkan count
dan memanggil fungsi saat Anda menambahkan count
. Untuk melakukannya, ikuti pola yang dijelaskan di atas dan teruskan status, count
(sebagai parameter ke fungsi composable), dan lambda (onIncrement
), yang dipanggil saat status perlu ditingkatkan. StatelessCounter
terlihat seperti ini:
@Composable
fun StatelessCounter(count: Int, onIncrement: () -> Unit, modifier: Modifier = Modifier) {
Column(modifier = modifier.padding(16.dp)) {
if (count > 0) {
Text("You've had $count glasses.")
}
Button(onClick = onIncrement, Modifier.padding(top = 8.dp), enabled = count < 10) {
Text("Add one")
}
}
}
StatefulCounter
memiliki status. Artinya, fungsi tersebut memiliki status count
dan mengubahnya saat memanggil fungsi StatelessCounter
.
@Composable
fun StatefulCounter(modifier: Modifier = Modifier) {
var count by rememberSaveable { mutableStateOf(0) }
StatelessCounter(count, { count++ }, modifier)
}
Bagus! Anda mengangkat count
dari StatelessCounter
ke StatefulCounter
.
Anda dapat memasukkannya ke aplikasi Anda dan mengupdate WellnessScreen
dengan StatefulCounter
:
@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
StatefulCounter(modifier)
}
Seperti yang disebutkan, pengangkatan status memiliki beberapa manfaat. Kita akan menjelajahi variasi kode ini untuk menjelaskan beberapa di antaranya, Anda tidak perlu menyalin cuplikan berikut di aplikasi.
- Composable stateless Anda sekarang dapat digunakan kembali. Misalnya, perhatikan contoh berikut.
Untuk menghitung gelas air dan jus, Anda mengingat waterCount
dan juiceCount
, tetapi gunakan fungsi composable StatelessCounter
yang sama untuk menampilkan dua status independen yang berbeda.
@Composable
fun StatefulCounter() {
var waterCount by remember { mutableStateOf(0) }
var juiceCount by remember { mutableStateOf(0) }
StatelessCounter(waterCount, { waterCount++ })
StatelessCounter(juiceCount, { juiceCount++ })
}
Jika juiceCount
diubah, StatefulCounter
akan dikomposisi ulang. Selama rekomposisi, Compose mengidentifikasi fungsi mana yang membaca juiceCount
dan memicu rekomposisi fungsi tersebut saja.
Saat pengguna mengetuk untuk menambahkan juiceCount
, StatefulCounter
akan merekomposisi, dan begitu juga StatelessCounter
yang membaca juiceCount
. Namun, StatelessCounter
yang membaca waterCount
tidak akan dikomposisi ulang.
- Fungsi composable stateful Anda dapat memberikan status yang sama ke beberapa fungsi composable.
@Composable
fun StatefulCounter() {
var count by remember { mutableStateOf(0) }
StatelessCounter(count, { count++ })
AnotherStatelessMethod(count, { count *= 2 })
}
Dalam hal ini, jika hitungan diperbarui oleh StatelessCounter
atau AnotherStatelessMethod
, semuanya akan dikomposisi ulang, dan ini memang diharapkan.
Karena status yang diangkat dapat dibagikan, pastikan untuk hanya meneruskan status yang diperlukan composable untuk menghindari rekomposisi yang tidak perlu, dan untuk meningkatkan penggunaan kembali.
Untuk membaca lebih lanjut tentang status dan pengangkatan status, lihat dokumentasi Status Compose.
10. Menangani daftar
Selanjutnya, tambahkan fitur kedua di aplikasi Anda, yaitu daftar tugas kesehatan. Anda dapat melakukan dua tindakan dengan item di daftar:
- Centang item daftar untuk menandai tugas sebagai selesai.
- Hapus tugas dari daftar yang tidak ingin Anda selesaikan.
Penyiapan
- Pertama, ubah item daftar. Anda dapat menggunakan kembali
WellnessTaskItem
dari bagian Remember di Komposisi, dan memperbaruinya agar berisiCheckbox
. Pastikan Anda mengangkat statuschecked
dan callbackonCheckedChange
untuk membuat fungsi menjadi stateless.
Composable WellnessTaskItem
untuk bagian ini akan terlihat seperti ini:
import androidx.compose.material3.Checkbox
@Composable
fun WellnessTaskItem(
taskName: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier, verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
text = taskName
)
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange
)
IconButton(onClick = onClose) {
Icon(Icons.Filled.Close, contentDescription = "Close")
}
}
}
- Dalam file yang sama, tambahkan fungsi composable
WellnessTaskItem
stateful yang menentukan variabel statuscheckedState
dan meneruskannya ke metode stateless dengan nama yang sama. Untuk saat ini, jangan khawatir tentangonClose
. Anda dapat meneruskan fungsi lambda kosong.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@Composable
fun WellnessTaskItem(taskName: String, modifier: Modifier = Modifier) {
var checkedState by remember { mutableStateOf(false) }
WellnessTaskItem(
taskName = taskName,
checked = checkedState,
onCheckedChange = { newValue -> checkedState = newValue },
onClose = {}, // we will implement this later!
modifier = modifier,
)
}
- Buat
WellnessTask.kt
file untuk membuat model tugas yang berisi ID dan label. Tentukan sebagai class data.
data class WellnessTask(val id: Int, val label: String)
- Untuk daftar tugas itu sendiri, buat file baru bernama
WellnessTasksList.kt
dan tambahkan metode yang menghasilkan beberapa data palsu:
fun getWellnessTasks() = List(30) { i -> WellnessTask(i, "Task # $i") }
Perhatikan bahwa dalam aplikasi yang sebenarnya, Anda mendapatkan data dari lapisan data.
- Di
WellnessTasksList.kt
, tambahkan fungsi composable yang membuat daftar. TentukanLazyColumn
dan item dari metode daftar yang Anda buat. Lihat Dokumentasi daftar jika Anda memerlukan bantuan.
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.runtime.remember
@Composable
fun WellnessTasksList(
modifier: Modifier = Modifier,
list: List<WellnessTask> = remember { getWellnessTasks() }
) {
LazyColumn(
modifier = modifier
) {
items(list) { task ->
WellnessTaskItem(taskName = task.label)
}
}
}
- Tambahkan daftar ke
WellnessScreen
. GunakanColumn
untuk membantu menyelaraskan daftar secara vertikal dengan penghitung yang sudah Anda miliki.
import androidx.compose.foundation.layout.Column
@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
StatefulCounter()
WellnessTasksList()
}
}
- Jalankan aplikasi dan cobalah! Sekarang Anda dapat memeriksa tugas, tetapi tidak dapat menghapusnya. Anda akan menerapkannya di bagian selanjutnya.
Memulihkan status item di LazyList
Pelajari lebih lanjut beberapa hal dalam composable WellnessTaskItem
.
checkedState
termasuk dalam setiap composable WellnessTaskItem
secara independen, seperti variabel pribadi. Saat checkedState
berubah, hanya instance WellnessTaskItem
tersebut yang akan dikomposisi ulang, bukan semua instance WellnessTaskItem
di LazyColumn
.
Cobalah dengan mengikuti langkah-langkah berikut:
- Centang elemen apa pun di bagian atas daftar ini (misalnya elemen 1 dan 2).
- Scroll ke bagian bawah daftar agar instance berada di luar layar.
- Scroll kembali ke atas ke item yang Anda centang sebelumnya.
- Perhatikan bahwa keduanya tidak dicentang.
Ada masalah, seperti yang Anda lihat di bagian sebelumnya, bahwa saat item keluar dari Komposisi, status yang diingat akan dilupakan. Untuk item di LazyColumn
, item akan keluar dari Komposisi sepenuhnya saat Anda men-scroll melewatinya dan item tersebut tidak lagi terlihat.
Bagaimana cara mengatasi masalah ini? Sekali lagi, gunakan rememberSaveable
. Status Anda akan tetap ada meskipun terjadi pembuatan ulang aktivitas atau proses menggunakan mekanisme status instance yang disimpan. Berkat cara kerja rememberSaveable
dengan LazyList
, item Anda juga akan tetap ada saat keluar dari Komposisi.
Cukup ganti remember
dengan rememberSaveable
di WellnessTaskItem
stateful, dan selesai:
import androidx.compose.runtime.saveable.rememberSaveable
var checkedState by rememberSaveable { mutableStateOf(false) }
Pola umum di Compose
Perhatikan penerapan LazyColumn
:
@Composable
fun LazyColumn(
...
state: LazyListState = rememberLazyListState(),
...
Fungsi composable rememberLazyListState
membuat status awal untuk daftar menggunakan rememberSaveable
. Saat Aktivitas dibuat ulang, status scroll dipertahankan tanpa Anda harus membuat kode apa pun.
Banyak aplikasi yang perlu menanggapi dan memproses posisi scroll, perubahan tata letak item, dan peristiwa lain yang terkait dengan status daftar. Komponen lambat, seperti LazyColumn
atau LazyRow
, mendukung kasus penggunaan ini melalui pengangkatan LazyListState
. Anda dapat mempelajari lebih lanjut pola ini di dokumentasi untuk status dalam daftar.
Memiliki parameter status dengan nilai default yang disediakan oleh fungsi rememberX
publik adalah pola yang umum dalam fungsi composable bawaan. Contoh lainnya dapat ditemukan di BottomSheetScaffold
, yang mengangkat status menggunakan rememberBottomSheetScaffoldState
.
11. MutableList yang dapat diobservasi
Selanjutnya, untuk menambahkan perilaku penghapusan tugas dari daftar, langkah pertama adalah membuat daftar Anda menjadi daftar yang dapat diubah.
Penggunaan objek yang dapat diubah untuk hal ini, seperti ArrayList<T>
atau mutableListOf,
tidak akan berfungsi. Jenis ini tidak akan memberi tahu Compose bahwa item dalam daftar telah berubah dan menjadwalkan rekomposisi UI. Anda memerlukan API yang berbeda.
Anda perlu membuat instance MutableList
yang dapat diamati oleh Compose. Struktur ini memungkinkan Compose melacak perubahan untuk merekomposisi UI saat item ditambahkan atau dihapus dari daftar.
Mulai dengan menentukan MutableList
yang dapat diamati. Fungsi ekstensi toMutableStateList()
adalah cara untuk membuat MutableList
yang dapat diamati dari Collection
awal yang tidak dapat diubah atau tidak dapat diubah, seperti List
.
Atau, Anda juga dapat menggunakan metode factory mutableStateListOf
untuk membuat MutableList
yang dapat diamati, lalu menambahkan elemen untuk status awal Anda.
- Buka file
WellnessScreen.kt
. Pindahkan metodegetWellnessTasks
ke file ini agar dapat menggunakannya. Buat daftar dengan memanggilgetWellnessTasks()
terlebih dahulu, lalu menggunakan fungsi ekstensitoMutableStateList
yang Anda pelajari sebelumnya.
import androidx.compose.runtime.remember
import androidx.compose.runtime.toMutableStateList
@Composable
fun WellnessScreen(modifier: Modifier = Modifier) {
Column(modifier = modifier) {
StatefulCounter()
val list = remember { getWellnessTasks().toMutableStateList() }
WellnessTasksList(list = list, onCloseTask = { task -> list.remove(task) })
}
}
private fun getWellnessTasks() = List(30) { i -> WellnessTask(i, "Task # $i") }
- Ubah fungsi composable
WellnessTasksList
dengan menghapus nilai default daftar, karena daftar diangkat ke tingkat layar. Tambahkan parameter fungsi lambda baruonCloseTask
(menerimaWellnessTask
untuk menghapus). TeruskanonCloseTask
keWellnessTaskItem
.
Ada satu perubahan lagi yang perlu Anda lakukan. Metode items
menerima parameter key
. Secara default, setiap status item dimasukkan ke posisi item dalam daftar.
Dalam daftar yang dapat diubah, ini menyebabkan masalah saat set data berubah, karena item yang mengubah posisi secara efektif kehilangan status yang diingat.
Anda dapat memperbaikinya dengan mudah menggunakan id
dari setiap WellnessTaskItem
sebagai kunci untuk setiap item.
Untuk mempelajari lebih lanjut kunci item dalam daftar, lihat dokumentasinya.
WellnessTasksList
akan terlihat seperti ini:
@Composable
fun WellnessTasksList(
list: List<WellnessTask>,
onCloseTask: (WellnessTask) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(modifier = modifier) {
items(
items = list,
key = { task -> task.id }
) { task ->
WellnessTaskItem(taskName = task.label, onClose = { onCloseTask(task) })
}
}
}
- Ubah
WellnessTaskItem
: tambahkan fungsi lambdaonClose
sebagai parameter keWellnessTaskItem
stateful dan panggil fungsi tersebut.
@Composable
fun WellnessTaskItem(
taskName: String, onClose: () -> Unit, modifier: Modifier = Modifier
) {
var checkedState by rememberSaveable { mutableStateOf(false) }
WellnessTaskItem(
taskName = taskName,
checked = checkedState,
onCheckedChange = { newValue -> checkedState = newValue },
onClose = onClose,
modifier = modifier,
)
}
Bagus! Fungsinya sudah lengkap, dan menghapus item dari daftar akan berhasil.
Jika Anda mengklik X di setiap baris, peristiwa akan masuk ke daftar yang memiliki status, menghapus item dari daftar dan menyebabkan Compose merekomposisi layar.
Jika mencoba menggunakan rememberSaveable()
untuk menyimpan daftar di WellnessScreen
, Anda akan mendapatkan pengecualian runtime:
Error ini memberi tahu Anda bahwa Anda harus memberikan saver kustom. Namun, Anda tidak boleh menggunakan rememberSaveable
untuk menyimpan data dalam jumlah besar atau struktur data kompleks yang memerlukan serialisasi atau deserialisasi yang panjang.
Aturan serupa berlaku saat menggunakan onSaveInstanceState
Aktivitas; Anda dapat menemukan informasi selengkapnya di dokumentasi Menyimpan status UI. Jika ingin melakukannya, Anda memerlukan mekanisme penyimpanan alternatif. Anda dapat mempelajari lebih lanjut berbagai opsi untuk mempertahankan status UI dalam dokumentasi.
Selanjutnya, kita akan melihat peran ViewModel sebagai holder untuk status aplikasi.
12. Status dalam ViewModel
Layar, atau status UI, menunjukkan apa yang harus ditampilkan di layar (misalnya, daftar tugas). Status ini biasanya terhubung dengan lapisan hierarki lain karena berisi data aplikasi.
Sementara status UI menjelaskan apa yang akan ditampilkan di layar, logika aplikasi menjelaskan bagaimana aplikasi berperilaku dan harus bereaksi terhadap perubahan status. Ada dua jenis logika: perilaku UI atau logika UI, dan logika bisnis.
- Logika UI berkaitan dengan cara menampilkan perubahan status di layar (misalnya, logika navigasi atau menampilkan snackbar).
- 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.
ViewModel menyediakan status UI dan akses ke logika bisnis yang ada di lapisan lain pada aplikasi. Selain itu, ViewModel tidak terpengaruh perubahan konfigurasi, sehingga memiliki masa aktif lebih lama daripada Komposisi. ViewModel dapat mengikuti siklus proses host konten Compose—yaitu, aktivitas, fragmen, atau tujuan grafik Navigasi jika Anda menggunakan Navigasi Compose.
Untuk mempelajari arsitektur dan lapisan UI lebih lanjut, lihat dokumentasi lapisan UI.
Memigrasikan daftar dan menghapus metode
Meskipun langkah sebelumnya menunjukkan cara mengelola status secara langsung di fungsi Composable, sebaiknya biarkan logika UI dan logika bisnis terpisah dari status UI dan memigrasikannya ke ViewModel.
Mari migrasikan status UI, daftar, ke ViewModel Anda dan juga mulai mengekstrak logika bisnis ke dalamnya.
- Buat
WellnessViewModel.kt
file untuk menambahkan class ViewModel Anda.
Pindahkan getWellnessTasks()
"sumber data" ke WellnessViewModel
.
Tentukan variabel _tasks
internal, menggunakan toMutableStateList
seperti yang Anda lakukan sebelumnya, dan tampilkan tasks
sebagai daftar, sehingga tidak dapat diubah dari luar ViewModel.
Implementasikan fungsi remove
sederhana yang didelegasikan ke fungsi hapus bawaan dari daftar.
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.ViewModel
class WellnessViewModel : ViewModel() {
private val _tasks = getWellnessTasks().toMutableStateList()
val tasks: List<WellnessTask>
get() = _tasks
fun remove(item: WellnessTask) {
_tasks.remove(item)
}
}
private fun getWellnessTasks() = List(30) { i -> WellnessTask(i, "Task # $i") }
- Kita dapat mengakses ViewModel ini dari composable mana pun dengan memanggil fungsi
viewModel()
.
Untuk menggunakan fungsi ini, buka file app/build.gradle.kts
, tambahkan library berikut, dan sinkronkan dependensi baru di Android Studio:
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:{latest_version}")
Gunakan versi 2.6.2
saat menggunakan Android Studio Giraffe. Selain itu, periksa versi terbaru library di sini.
- Buka
WellnessScreen
. Buat instance ViewModelwellnessViewModel
dengan memanggilviewModel()
, sebagai parameter composable Layar, sehingga dapat diganti saat menguji composable ini, dan diangkat jika perlu. SediakanWellnessTasksList
dengan daftar tugas dan hapus fungsi ke lambdaonCloseTask
.
import androidx.lifecycle.viewmodel.compose.viewModel
@Composable
fun WellnessScreen(
modifier: Modifier = Modifier,
wellnessViewModel: WellnessViewModel = viewModel()
) {
Column(modifier = modifier) {
StatefulCounter()
WellnessTasksList(
list = wellnessViewModel.tasks,
onCloseTask = { task -> wellnessViewModel.remove(task) })
}
}
viewModel()
menampilkan ViewModel
yang sudah ada atau membuat yang baru dalam cakupan yang ditentukan. Instance ViewModel dipertahankan selama cakupan masih aktif. Misalnya, jika composable digunakan dalam suatu aktivitas, viewModel()
akan menampilkan instance yang sama sampai aktivitas itu selesai atau prosesnya diakhiri.
Dan selesai. Anda telah mengintegrasikan ViewModel dengan bagian status dan logika bisnis dengan layar. Karena status dipertahankan di luar Komposisi dan disimpan oleh ViewModel, mutasi ke daftar tetap ada setelah konfigurasi diubah.
ViewModel tidak akan mempertahankan status aplikasi secara otomatis dalam skenario apa pun (misalnya, untuk penghentian proses yang dimulai oleh sistem). Untuk informasi mendetail tentang mempertahankan status UI aplikasi, periksa dokumentasi.
Memigrasikan status yang dicentang
Pemfaktoran ulang terakhir adalah memigrasikan status dan logika yang dicentang ke ViewModel. Dengan demikian, kode menjadi lebih sederhana dan lebih mudah diuji, dengan semua status dikelola oleh ViewModel.
- Pertama, ubah class model
WellnessTask
agar dapat menyimpan status yang dicentang dan menetapkan salah sebagai nilai default.
data class WellnessTask(val id: Int, val label: String, var checked: Boolean = false)
- Di ViewModel, implementasikan metode
changeTaskChecked
yang menerima tugas untuk memodifikasi dengan nilai baru untuk status yang dicentang.
class WellnessViewModel : ViewModel() {
...
fun changeTaskChecked(item: WellnessTask, checked: Boolean) =
_tasks.find { it.id == item.id }?.let { task ->
task.checked = checked
}
}
- Di
WellnessScreen
, berikan perilaku untukonCheckedTask
daftar dengan memanggil metodechangeTaskChecked
ViewModel. Sekarang fungsi akan terlihat seperti ini:
@Composable
fun WellnessScreen(
modifier: Modifier = Modifier,
wellnessViewModel: WellnessViewModel = viewModel()
) {
Column(modifier = modifier) {
StatefulCounter()
WellnessTasksList(
list = wellnessViewModel.tasks,
onCheckedTask = { task, checked ->
wellnessViewModel.changeTaskChecked(task, checked)
},
onCloseTask = { task ->
wellnessViewModel.remove(task)
}
)
}
}
- Buka
WellnessTasksList
dan tambahkan parameter fungsi lambdaonCheckedTask
sehingga Anda dapat meneruskannya keWellnessTaskItem.
@Composable
fun WellnessTasksList(
list: List<WellnessTask>,
onCheckedTask: (WellnessTask, Boolean) -> Unit,
onCloseTask: (WellnessTask) -> Unit,
modifier: Modifier = Modifier
) {
LazyColumn(
modifier = modifier
) {
items(
items = list,
key = { task -> task.id }
) { task ->
WellnessTaskItem(
taskName = task.label,
checked = task.checked,
onCheckedChange = { checked -> onCheckedTask(task, checked) },
onClose = { onCloseTask(task) }
)
}
}
}
- Bersihkan file
WellnessTaskItem.kt
. Kita tidak lagi memerlukan metode stateful, karena status CheckBox akan diangkat ke tingkat List. File hanya memiliki fungsi composable ini:
@Composable
fun WellnessTaskItem(
taskName: String,
checked: Boolean,
onCheckedChange: (Boolean) -> Unit,
onClose: () -> Unit,
modifier: Modifier = Modifier
) {
Row(
modifier = modifier, verticalAlignment = Alignment.CenterVertically
) {
Text(
modifier = Modifier
.weight(1f)
.padding(start = 16.dp),
text = taskName
)
Checkbox(
checked = checked,
onCheckedChange = onCheckedChange
)
IconButton(onClick = onClose) {
Icon(Icons.Filled.Close, contentDescription = "Close")
}
}
}
- Jalankan aplikasi dan coba periksa tugas apa pun. Perhatikan bahwa memeriksa tugas apa pun belum berfungsi.
Hal ini karena yang dilacak Compose untuk MutableList
adalah perubahan terkait dengan penambahan dan penghapusan elemen. Inilah alasan mengapa penghapusan berfungsi. Namun, Compose tidak mengetahui perubahan pada nilai item baris (checkedState
dalam kasus kita), kecuali jika Anda memintanya untuk melacaknya juga.
Ada dua cara untuk memperbaiki masalah ini:
- Ubah class data
WellnessTask
agarcheckedState
menjadiMutableState<Boolean>
, bukanBoolean
, yang menyebabkan Compose melacak perubahan item. - Salin item yang akan Anda mutasi, hapus item dari daftar, dan tambahkan kembali item yang bermutasi ke daftar, yang menyebabkan Compose melacak perubahan daftar tersebut.
Terdapat pro dan kontra untuk kedua pendekatan ini. Misalnya, bergantung pada penerapan daftar yang Anda gunakan, menghapus dan membaca elemen mungkin mahal.
Jadi, misalnya Anda ingin menghindari operasi daftar yang mungkin mahal, dan membuat checkedState
dapat diamati karena lebih efisien dan Compose-idiomatis.
WellnessTask
baru dapat terlihat seperti ini:
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
data class WellnessTask(val id: Int, val label: String, val checked: MutableState<Boolean> = mutableStateOf(false))
Seperti yang Anda lihat sebelumnya, Anda dapat menggunakan properti yang didelegasikan, yang menghasilkan penggunaan variabel checked
yang lebih sederhana untuk kasus ini.
Ubah WellnessTask
menjadi class, bukan class data. Buat WellnessTask
agar menerima variabel initialChecked
dengan nilai default false
di konstruktor, lalu kita dapat menginisialisasi variabel checked
dengan metode factory mutableStateOf
dan mengambil initialChecked
sebagai nilai default.
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
class WellnessTask(
val id: Int,
val label: String,
initialChecked: Boolean = false
) {
var checked by mutableStateOf(initialChecked)
}
Selesai. Solusi ini berfungsi, dan semua perubahan tetap ada selama perubahan konfigurasi dan rekomposisi terjadi.
Pengujian
Setelah logika bisnis difaktorkan ulang ke ViewModel, bukan digabungkan dalam fungsi composable, pengujian unit jauh lebih sederhana.
Anda dapat menggunakan uji instrumentasi untuk memverifikasi perilaku kode Compose yang benar dan bahwa status UI berfungsi dengan benar. Sebaiknya ikuti codelab Pengujian di Compose untuk mempelajari cara menguji UI Compose Anda.
13. Selamat
Bagus! Anda berhasil menyelesaikan codelab ini dan mempelajari semua API dasar supaya berfungsi dengan status di aplikasi Jetpack Compose.
Anda telah mempelajari cara memikirkan status dan peristiwa untuk mengekstrak composable stateless di Compose, dan cara Compose menggunakan pembaruan status untuk mendorong perubahan di UI.
Apa selanjutnya?
Lihat codelab lainnya di pembelajaran Compose.
Aplikasi contoh
- JetNews menunjukkan praktik terbaik yang dijelaskan dalam codelab ini.
Dokumentasi lainnya
- Paradigma Compose
- Status dan Jetpack Compose
- Aliran data searah di Jetpack Compose
- Memulihkan status di Compose
- Ringkasan ViewModel
- Compose dan library lainnya