1. Sebelum memulai
Dalam codelab ini, Anda akan menggunakan kode solusi dari codelab Pengantar status di Compose guna membuat kalkulator tip interaktif yang dapat secara otomatis menghitung dan membulatkan jumlah tip saat Anda memasukkan jumlah tagihan dan persentase tip. Anda dapat melihat aplikasi final dalam gambar ini:
Prasyarat
- Codelab Pengantar status di Compose.
- Kemampuan untuk menambahkan composable
Text
danTextField
ke aplikasi. - Pengetahuan tentang fungsi
remember()
, status, pengangkatan status, dan perbedaan antara fungsi composable stateful dan stateless.
Yang akan Anda pelajari
- Cara menambahkan tombol tindakan ke keyboard virtual.
- Pengertian composable
Switch
dan cara menggunakannya. - Menambahkan ikon utama ke kolom teks.
Yang akan Anda build
- Aplikasi Tip Time yang menghitung jumlah tip berdasarkan jumlah tagihan yang dimasukkan pengguna dan persentase tip.
Yang akan Anda butuhkan
- Versi terbaru Android Studio
- Kode solusi dari codelab Pengantar status di Compose
2. Mendapatkan kode awal
Untuk memulai, download kode awal:
Atau, Anda dapat membuat clone repositori GitHub untuk kode tersebut:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git $ cd basic-android-kotlin-compose-training-tip-calculator $ git checkout state
Anda dapat menjelajahi kode di repositori GitHub Tip Time
.
3. Ringkasan aplikasi awal
Codelab ini dimulai dengan aplikasi Tip Time dari codelab sebelumnya, yaitu Pengantar status di Compose, yang menyediakan antarmuka pengguna yang diperlukan untuk menghitung tip dengan persentase tip tetap. Kotak teks Jumlah tagihan memungkinkan pengguna memasukkan biaya layanan. Aplikasi ini akan menghitung dan menampilkan jumlah tip dalam composable Text
.
Menjalankan Aplikasi Tip Time
- Buka project Tip Time di Android Studio dan jalankan aplikasi di emulator atau perangkat.
- Masukkan jumlah tagihan. Aplikasi akan secara otomatis menghitung dan menampilkan jumlah tip.
Dalam penerapan saat ini, persentase tip di-hardcode menjadi 15%. Dalam codelab ini, Anda akan memperluas fitur ini menggunakan kolom teks yang memungkinkan aplikasi menghitung persentase tip kustom dan membulatkan jumlah tip.
Menambahkan resource string yang diperlukan
- Di tab Project, klik res > values > strings.xml.
- Di antara tag
<resources>
filestrings.xml
, tambahkan resource string berikut:
<string name="how_was_the_service">Tip Percentage</string>
<string name="round_up_tip">Round up tip?</string>
File strings.xml
akan terlihat seperti cuplikan kode ini, yang menyertakan string dari codelab sebelumnya:
strings.xml
<resources>
<string name="app_name">Tip Time</string>
<string name="calculate_tip">Calculate Tip</string>
<string name="bill_amount">Bill Amount</string>
<string name="how_was_the_service">Tip Percentage</string>
<string name="round_up_tip">Round up tip?</string>
<string name="tip_amount">Tip Amount: %s</string>
</resources>
4. Menambahkan kolom teks persentase tip
Pelanggan mungkin ingin memberikan lebih banyak atau lebih sedikit berdasarkan kualitas layanan yang diberikan dan berbagai alasan lainnya. Untuk mengakomodasi hal ini, aplikasi harus memungkinkan pengguna menghitung tip kustom. Di bagian ini, Anda akan menambahkan kolom teks bagi pengguna untuk memasukkan persentase tip kustom seperti yang dapat Anda lihat pada gambar ini:
Anda sudah memiliki composable kolom teks Bill Amount di aplikasi, dan ini merupakan fungsi composable EditNumberField()
stateless. Di codelab sebelumnya, Anda telah mengangkat status amountInput
dari composable EditNumberField()
ke composableTipTimeLayout()
, sehingga composable EditNumberField()
menjadi stateless.
Untuk menambahkan kolom teks, Anda dapat menggunakan kembali composable EditNumberField()
yang sama, tetapi dengan label yang berbeda. Untuk melakukan perubahan ini, Anda harus meneruskan label sebagai parameter, bukan melakukan hardcode pada fungsi composable EditNumberField()
.
Buat fungsi composable EditNumberField()
dapat digunakan kembali:
- Dalam file
MainActivity.kt
di parameter fungsi composableEditNumberField()
, tambahkan resource stringlabel
dari jenisInt
:
@Composable
fun EditNumberField(
label: Int,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
)
- Dalam isi fungsi, ganti ID resource string hardcode dengan parameter
label
:
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
label = { Text(stringResource(label)) },
//...
)
}
- Untuk menunjukkan bahwa parameter
label
diharapkan akan menjadi referensi resource string, anotasikan parameter fungsi dengan anotasi@StringRes
:
@Composable
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
)
- Impor hal berikut:
import androidx.annotation.StringRes
- Pada panggilan fungsi
EditNumberField()
fungsi composableTipTimeLayout()
, tetapkan parameterlabel
ke resource stringR.string.bill_amount
:
EditNumberField(
label = R.string.bill_amount,
value = amountInput,
onValueChanged = { amountInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth()
)
- Di panel Pratinjau seharusnya tidak ada perubahan visual apa pun.
- Pada fungsi composable
TipTimeLayout()
setelah panggilan fungsiEditNumberField()
, tambahkan kolom teks lain untuk persentase tip kustom. Lakukan panggilan ke fungsi composableEditNumberField()
dengan parameter ini:
EditNumberField(
label = R.string.how_was_the_service,
value = "",
onValueChanged = { },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth()
)
Tindakan ini akan menambahkan kotak teks lain untuk persentase tip kustom.
- Pratinjau aplikasi kini menampilkan kolom teks Tip Percentage seperti yang dapat Anda lihat dalam gambar ini:
- Di bagian atas fungsi composable
TipTimeLayout()
, tambahkan propertivar
yang disebuttipInput
untuk variabel status kolom teks yang ditambahkan. GunakanmutableStateOf("")
untuk melakukan inisialisasi pada variabel dan mengapit panggilan dengan fungsiremember
:
var tipInput by remember { mutableStateOf("") }
- Pada panggilan fungsi
EditNumberField
()
baru, setel parameter bernamavalue
ke variabeltipInput
, lalu perbarui variabeltipInput
dalam ekspresi lambdaonValueChanged
:
EditNumberField(
label = R.string.how_was_the_service,
value = tipInput,
onValueChanged = { tipInput = it },
modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth()
)
- Dalam fungsi
TipTimeLayout()
setelah definisi variabeltipInput
. Tentukanval
bernamatipPercent
yang mengonversi variabeltipInput
menjadi jenisDouble
. Gunakan operator Elvis dan tampilkan0
jika nilainya adalahnull
. Nilai ini dapat berupanull
jika kolom teks kosong.
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
- Pada fungsi
TipTimeLayout()
, update panggilan fungsicalculateTip()
, teruskan variabeltipPercent
sebagai parameter kedua:
val tip = calculateTip(amount, tipPercent)
Kode untuk fungsi TipTimeLayout()
kini akan terlihat seperti cuplikan kode ini:
@Composable
fun TipTimeLayout() {
var amountInput by remember { mutableStateOf("") }
var tipInput by remember { mutableStateOf("") }
val amount = amountInput.toDoubleOrNull() ?: 0.0
val tipPercent = tipInput.toDoubleOrNull() ?: 0.0
val tip = calculateTip(amount, tipPercent)
Column(
modifier = Modifier.padding(40.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(R.string.calculate_tip),
modifier = Modifier
.padding(bottom = 16.dp)
.align(alignment = Alignment.Start)
)
EditNumberField(
label = R.string.bill_amount,
value = amountInput,
onValueChanged = { amountInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
EditNumberField(
label = R.string.how_was_the_service,
value = tipInput,
onValueChanged = { tipInput = it },
modifier = Modifier
.padding(bottom = 32.dp)
.fillMaxWidth()
)
Text(
text = stringResource(R.string.tip_amount, tip),
style = MaterialTheme.typography.displaySmall
)
Spacer(modifier = Modifier.height(150.dp))
}
}
- Jalankan aplikasi di emulator atau perangkat, lalu masukkan jumlah tagihan dan persentase tip. Apakah aplikasi menghitung jumlah tip dengan benar?
5. Menyetel tombol tindakan
Pada codelab sebelumnya, Anda telah mempelajari cara menggunakan class KeyboardOptions
untuk menetapkan jenis keyboard. Di bagian ini, Anda akan mempelajari cara menyetel tombol tindakan keyboard dengan KeyboardOptions
yang sama. Tombol tindakan keyboard adalah tombol di bagian akhir keyboard. Anda dapat melihat beberapa contoh dalam tabel ini:
Properti | Tombol tindakan di keyboard |
| |
| |
|
Dalam tugas ini, Anda menyetel dua tombol tindakan yang berbeda untuk kotak teks:
- Tombol tindakan Next untuk kotak teks Bill Amount yang menunjukkan bahwa pengguna telah selesai memasukkan input saat ini dan ingin pindah ke kotak teks berikutnya.
- Tombol tindakan Done untuk kotak teks Tip Percentage yang menunjukkan bahwa pengguna selesai memberikan input.
Anda dapat melihat contoh keyboard dengan tombol tindakan ini dalam gambar berikut:
Menambahkan opsi keyboard:
- Pada panggilan fungsi
TextField()
dari fungsiEditNumberField()
, teruskan argumen bernamaimeAction
pada konstruktorKeyboardOptions
ke nilaiImeAction.Next
. Gunakan fungsiKeyboardOptions.Default.copy()
untuk memastikan Anda menggunakan opsi default lainnya.
import androidx.compose.ui.text.input.ImeAction
@Composable
fun EditNumberField(
//...
) {
TextField(
//...
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
)
)
}
- Menjalankan aplikasi di emulator atau perangkat. Sekarang keyboard dapat menampilkan tombol tindakan Next seperti yang dapat Anda lihat dalam gambar ini:
Perhatikan bahwa keyboard menampilkan tombol tindakan Next yang sama saat kolom teks Tip Percentage dipilih. Namun, Anda ingin dua tombol tindakan yang berbeda untuk kolom teks. Anda akan segera memperbaiki masalah ini.
- Periksa fungsi
EditNumberField()
. ParameterkeyboardOptions
dalam fungsiTextField()
di-hardcode. Untuk membuat tombol tindakan yang berbeda untuk kolom teks, Anda harus meneruskan objekKeyboardOptions
sebagai argumen, yang akan Anda lakukan di langkah berikutnya.
// No need to copy, just examine the code.
fun EditNumberField(
@StringRes label: Int,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
) {
TextField(
//...
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
)
)
}
- Pada definisi fungsi
EditNumberField()
, tambahkan parameterkeyboardOptions
dari jenisKeyboardOptions
. Dalam isi fungsi, tetapkan ke parameter bernamakeyboardOptions
dari fungsiTextField()
:
@Composable
fun EditNumberField(
@StringRes label: Int,
keyboardOptions: KeyboardOptions,
// ...
){
TextField(
//...
keyboardOptions = keyboardOptions
)
}
- Pada fungsi
TipTimeLayout()
, update panggilan fungsiEditNumberField()
pertama, teruskan parameter bernamakeyboardOptions
untuk kolom teks Bill Amount:
EditNumberField(
label = R.string.bill_amount,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Next
),
// ...
)
- Pada panggilan fungsi
EditNumberField()
kedua, ubahimeAction
kolom teks Tip Percentage menjadiImeAction.Done
. Fungsi Anda akan terlihat seperti cuplikan kode ini:
EditNumberField(
label = R.string.how_was_the_service,
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number,
imeAction = ImeAction.Done
),
// ...
)
- Jalankan aplikasi. Kode ini akan menampilkan tombol tindakan Next dan Done seperti yang dapat Anda lihat dalam gambar berikut:
- Masukkan jumlah tagihan apa pun, lalu klik tombol tindakan Next, lalu masukkan persentase tip dan klik tombol tindakan Done. Tindakan ini akan menutup keypad.
6. Menambahkan tombol
Tombol akan mengaktifkan atau menonaktifkan status suatu item.
Ada dua status dalam tombol yang memungkinkan pengguna memilih antara dua opsi. Tombol terdiri dari track, thumb, dan ikon opsional seperti yang dapat Anda lihat dalam gambar ini:
Tombol adalah kontrol pemilihan yang dapat digunakan untuk memasukkan keputusan atau mendeklarasikan preferensi, seperti setelan yang dapat Anda lihat dalam gambar ini:
Pengguna dapat menarik thumb bolak-balik untuk memilih opsi pilihan, atau cukup mengetuk tombol untuk beralih. Anda dapat melihat contoh lain tombol dalam GIF ini dengan setelan Visual options dialihkan ke Dark mode:
Untuk mempelajari tombol lebih lanjut, lihat dokumentasi Tombol.
Anda menggunakan composable Switch
sehingga pengguna dapat memilih untuk membulatkan tip ke bilangan bulat terdekat, seperti yang Anda lihat dalam gambar ini:
Tambahkan baris untuk composable Text
dan Switch
:
- Setelah fungsi
EditNumberField()
, tambahkan fungsi composableRoundTheTipRow()
, lalu teruskanModifier
default sebagai argumen yang serupa dengan fungsiEditNumberField()
:
@Composable
fun RoundTheTipRow(modifier: Modifier = Modifier) {
}
- Terapkan fungsi
RoundTheTipRow()
, tambahkan composable tata letakRow
denganmodifier
berikut untuk menetapkan lebar elemen turunan ke maksimum di layar, memosisikan perataan di tengah, dan memastikan ukuran48dp
:
Row(
modifier = modifier
.fillMaxWidth()
.size(48.dp),
verticalAlignment = Alignment.CenterVertically
) {
}
- Impor hal berikut:
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
- Di blok lambda composable tata letak
Row
, tambahkan composableText
yang menggunakan resource stringR.string.round_up_tip
untuk menampilkan stringRound up tip?
:
Text(text = stringResource(R.string.round_up_tip))
- Setelah composable
Text
, tambahkan composableSwitch
, dan teruskan parameter bernamachecked
, tetapkan keroundUp
dan parameter bernamaonCheckedChange
menetapkannya keonRoundUpChanged
.
Switch(
checked = roundUp,
onCheckedChange = onRoundUpChanged,
)
Tabel ini berisi informasi tentang parameter tersebut, yang merupakan parameter yang sama, yang Anda tentukan untuk fungsi RoundTheTipRow()
:
Parameter | Deskripsi |
| Apakah tombol diperiksa. Ini adalah status composable |
| Callback yang akan dipanggil saat tombol diklik. |
- Impor hal berikut:
import androidx.compose.material3.Switch
- Dalam fungsi
RoundTheTipRow()
, tambahkan parameterroundUp
dari jenisBoolean
dan fungsi lambdaonRoundUpChanged
yang menggunakanBoolean
dan tidak menampilkan apa pun:
@Composable
fun RoundTheTipRow(
roundUp: Boolean,
onRoundUpChanged: (Boolean) -> Unit,
modifier: Modifier = Modifier
)
Tindakan ini akan mengangkat status tombol.
- Pada composable
Switch
, tambahkanmodifier
ini untuk meratakan composableSwitch
ke akhir layar:
Switch(
modifier = modifier
.fillMaxWidth()
.wrapContentWidth(Alignment.End),
//...
)
- Impor hal berikut:
import androidx.compose.foundation.layout.wrapContentWidth
- Dalam fungsi
TipTimeLayout()
, tambahkan variabel var untuk status composableSwitch
. Buat variabelvar
bernamaroundUp
, tetapkan kemutableStateOf()
, denganfalse
sebagai nilai awal. Gabungkan panggilan denganremember { }
.
fun TipTimeLayout() {
//...
var roundUp by remember { mutableStateOf(false) }
//...
Column(
...
) {
//...
}
}
Ini adalah variabel untuk status composable Switch
, dan false akan menjadi status default.
- Di blok
Column
dari fungsiTipTimeLayout()
setelah kolom teks Tip Percentage. Panggil fungsiRoundTheTipRow()
dengan argumen berikut: parameter bernamaroundUp
ditetapkan keroundUp
dan parameter bernamaonRoundUpChanged
ditetapkan ke callback lambda yang memperbarui nilairoundUp
:
@Composable
fun TipTimeLayout() {
//...
Column(
...
) {
Text(
...
)
Spacer(...)
EditNumberField(
...
)
EditNumberField(
...
)
RoundTheTipRow(
roundUp = roundUp,
onRoundUpChanged = { roundUp = it },
modifier = Modifier.padding(bottom = 32.dp)
)
Text(
...
)
}
}
Tindakan ini akan menampilkan baris Round up tip?.
- Jalankan aplikasi. Aplikasi menampilkan tombol Round up tip?.
- Masukkan jumlah tagihan dan persentase tip, lalu pilih tombol Round up tip?. Jumlah tip tidak dibulatkan karena Anda masih perlu mengupdate fungsi
calculateTip()
, yang akan Anda lakukan di bagian berikutnya.
Update fungsi calculateTip()
untuk membulatkan tip
Ubah fungsi calculateTip()
untuk menerima variabel Boolean
agar membulatkan tip ke bilangan bulat terdekat:
- Untuk membulatkan tip, fungsi
calculateTip()
harus mengetahui status tombol, yaituBoolean
. Di fungsicalculateTip()
, tambahkan parameterroundUp
dari jenisBoolean
:
private fun calculateTip(
amount: Double,
tipPercent: Double = 15.0,
roundUp: Boolean
): String {
//...
}
- Dalam fungsi
calculateTip()
sebelum pernyataanreturn
, tambahkan kondisiif()
yang memeriksa nilairoundUp
. JikaroundUp
bernilaitrue
, tentukan variabeltip
dan tetapkan ke fungsikotlin.math.
ceil
()
, lalu teruskan fungsitip
sebagai argumen:
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
Fungsi calculateTip()
yang telah selesai akan terlihat seperti cuplikan kode ini:
private fun calculateTip(amount: Double, tipPercent: Double = 15.0, roundUp: Boolean): String {
var tip = tipPercent / 100 * amount
if (roundUp) {
tip = kotlin.math.ceil(tip)
}
return NumberFormat.getCurrencyInstance().format(tip)
}
- Di fungsi
TipTimeLayout()
, update panggilan fungsicalculateTip()
, lalu teruskan parameterroundUp
:
val tip = calculateTip(amount, tipPercent, roundUp)
- Jalankan aplikasi. Sekarang, jumlah tip dibulatkan seperti yang dapat Anda lihat pada gambar berikut:
7. Menambahkan dukungan untuk orientasi lanskap
Perangkat Android hadir dalam berbagai faktor bentuk—ponsel, tablet, perangkat foldable, dan perangkat ChromeOS—yang memiliki berbagai ukuran layar. Aplikasi Anda harus mendukung orientasi potret dan lanskap.
- Uji aplikasi Anda dalam mode lanskap, aktifkan Putar otomatis.
- Putar emulator atau perangkat ke kiri, perhatikan bahwa Anda tidak dapat melihat jumlah tip. Untuk mengatasi hal ini, Anda memerlukan scrollbar vertikal, yang membantu Anda men-scroll layar aplikasi.
- Tambahkan
.verticalScroll(rememberScrollState())
ke pengubah untuk mengaktifkan kolom agar men-scroll secara vertikal.rememberScrollState()
membuat dan mengingat status scroll secara otomatis.
@Composable
fun TipTimeLayout() {
// ...
Column(
modifier = Modifier
.padding(40.dp)
.verticalScroll(rememberScrollState()),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
//...
}
}
- Impor hal berikut:
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
- Jalankan kembali aplikasi. Coba scroll dalam mode lanskap!
8. Menambahkan ikon utama ke kolom teks (opsional)
Ikon dapat membuat kolom teks lebih menarik secara visual dan memberikan informasi tambahan tentang kolom teks. Ikon dapat digunakan untuk menyampaikan informasi tentang tujuan kolom teks, seperti jenis data yang diharapkan atau jenis input yang diperlukan. Misalnya, ikon telepon di samping kolom teks dapat menunjukkan bahwa pengguna diminta memasukkan nomor telepon.
Ikon dapat digunakan untuk memandu input pengguna dengan memberikan petunjuk visual tentang hal yang diharapkan. Misalnya, ikon kalender di samping kolom teks mungkin menunjukkan bahwa pengguna diharapkan memasukkan tanggal.
Berikut adalah contoh kolom teks dengan ikon penelusuran, yang menunjukkan untuk memasukkan istilah penelusuran.
Tambahkan parameter lain ke composable EditNumberField()
yang disebut leadingIcon
dari jenis Int
. Anotasikan dengan @DrawableRes
.
@Composable
fun EditNumberField(
@StringRes label: Int,
@DrawableRes leadingIcon: Int,
keyboardOptions: KeyboardOptions,
value: String,
onValueChanged: (String) -> Unit,
modifier: Modifier = Modifier
)
- Impor hal berikut:
import androidx.annotation.DrawableRes
import androidx.compose.material3.Icon
- Tambahkan ikon utama ke kolom teks.
leadingIcon
mengambil composable, Anda akan meneruskan composableIcon
berikut.
TextField(
value = value,
leadingIcon = { Icon(painter = painterResource(id = leadingIcon), null) },
//...
)
- Teruskan ikon utama ke kolom teks. Ikon sudah ada dalam kode awal untuk memudahkan Anda.
EditNumberField(
label = R.string.bill_amount,
leadingIcon = R.drawable.money,
// Other arguments
)
EditNumberField(
label = R.string.how_was_the_service,
leadingIcon = R.drawable.percent,
// Other arguments
)
- Jalankan aplikasi.
Selamat! Aplikasi Anda kini memiliki kemampuan untuk menghitung tip kustom.
9. Mendapatkan kode solusi
Guna mendownload kode untuk codelab yang sudah selesai, Anda dapat menggunakan perintah git ini:
$ git clone https://github.com/google-developer-training/basic-android-kotlin-compose-training-tip-calculator.git
Atau, Anda dapat mendownload repositori sebagai file zip, mengekstraknya, dan membukanya di Android Studio.
Jika Anda ingin melihat kode solusi, lihat di GitHub.
10. Kesimpulan
Selamat! Anda telah menambahkan fungsi tip kustom ke aplikasi Tip Time. Sekarang, dengan aplikasi tersebut, pengguna dapat memasukkan persentase tip kustom dan membulatkan jumlah tip. Bagikan hasil karya Anda di media sosial dengan hashtag #AndroidBasics!