Pengantar status di Compose

1. Sebelum memulai

Codelab ini mengajarkan status, serta cara Jetpack Compose menggunakan dan memanipulasinya.

Pada intinya, status di aplikasi adalah nilai yang dapat berubah dari waktu ke waktu. Definisi ini sangat luas dan mencakup semuanya, mulai dari database hingga variabel dalam aplikasi Anda. Anda dapat mempelajari lebih lanjut tentang database di unit selanjutnya, tetapi untuk saat ini yang perlu Anda ketahui adalah bahwa database adalah kumpulan informasi terstruktur yang tertata, seperti file di komputer Anda.

Semua aplikasi Android menampilkan status kepada pengguna. Beberapa contoh status di aplikasi Android termasuk:

  • Pesan yang muncul saat koneksi jaringan tidak dapat dibuat.
  • Formulir, seperti formulir pendaftaran. Status dapat diisi dan dikirim.
  • Kontrol yang dapat diketuk, seperti tombol. Status dapat tidak diketuk, diketuk (animasi tampilan), atau diketuk (tindakan onClick).

Dalam codelab ini, Anda akan mempelajari cara menggunakan dan mempertimbangkan status saat menggunakan Compose. Untuk melakukannya, Anda membuat aplikasi kalkulator tip yang disebut Tip Time dengan elemen UI Compose bawaan ini:

  • Composable TextField untuk memasukkan dan mengedit teks.
  • Composable Text untuk menampilkan teks.
  • Composable Spacer untuk menampilkan ruang kosong di antara elemen UI.

Di akhir codelab ini, Anda akan membuat kalkulator tip interaktif yang menghitung jumlah tip secara otomatis saat Anda memasukkan jumlah layanan. Gambar ini menunjukkan tampilan aplikasi final:

e82cbb534872abcf.png

Prasyarat

  • Pemahaman dasar tentang Compose, seperti anotasi @Composable.
  • Pemahaman dasar tentang tata letak Compose, seperti composable tata letak Row dan Column.
  • Pemahaman dasar tentang pengubah, seperti fungsi Modifier.padding().
  • Pemahaman tentang composable Text.

Yang akan Anda pelajari

  • Cara mempertimbangkan status di UI.
  • Cara Compose menggunakan status untuk menampilkan data.
  • Cara menambahkan kotak teks ke aplikasi Anda.
  • Cara mengangkat status.

Yang akan Anda build

  • Aplikasi kalkulator tip yang disebut Tip Time yang menghitung jumlah tip berdasarkan jumlah layanan.

Yang akan Anda butuhkan

  • Komputer dengan akses internet dan browser web
  • Pengetahuan tentang Kotlin
  • Versi terbaru Android Studio

2. Memulai

  1. Lihat kalkulator tip online Google. Harap perhatikan bahwa ini hanyalah contoh dan bukan aplikasi Android yang akan Anda buat dalam kursus ini.

46bf4366edc1055f.png 18da3c120daa0759.png

  1. Masukkan nilai yang berbeda di kotak Bill dan Tip %. Nilai tip dan total akan berubah.

c0980ba3e9ebba02.png

Perhatikan bahwa saat Anda memasukkan nilai, Tip dan Total akan diperbarui. Di akhir codelab berikut, Anda akan mengembangkan aplikasi kalkulator tip serupa di Android.

Dalam kursus ini, Anda akan mem-build aplikasi Android kalkulator tip sederhana.

Developer sering kali akan menggunakan cara ini—menyiapkan aplikasi versi sederhana dan berfungsi (meskipun terlihat tidak bagus), kemudian menambahkan lebih banyak fitur dan membuatnya lebih menarik secara visual nanti.

Di akhir codelab ini, aplikasi kalkulator tip Anda akan terlihat seperti screenshot ini. Saat pengguna memasukkan jumlah tagihan, aplikasi Anda akan menampilkan jumlah tip yang disarankan. Untuk saat ini, persentase tip di-hardcode menjadi 15%. Pada codelab berikutnya, Anda akan terus mengerjakan aplikasi dan menambahkan lebih banyak fitur seperti menetapkan persentase tip kustom.

3. Mendapatkan Kode Awal

Kode awal adalah kode yang telah ditulis sebelumnya yang dapat digunakan sebagai titik awal untuk project baru. Kode ini juga dapat membantu Anda berfokus pada konsep baru yang diajarkan dalam codelab ini.

Mulai menggunakan kode awal dengan mendownloadnya di sini:

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 starter

Anda dapat menjelajahi kode awal di repositori GitHub TipTime.

Ringkasan aplikasi awal

Untuk memahami kode awal, selesaikan langkah-langkah berikut:

  1. Buka project dengan kode awal di Android Studio.
  2. Jalankan aplikasi di perangkat Android atau emulator.
  3. Anda akan melihat dua komponen teks; satu untuk label dan satunya lagi untuk menampilkan jumlah tip.

e85b767a43c69a97.png

Panduan kode awal

Kode awal memiliki composable teks. Dalam pembelajaran ini, Anda akan menambahkan kolom teks untuk mengambil input pengguna. Berikut adalah panduan singkat beberapa file untuk membantu Anda memulai.

res > values > 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="tip_amount">Tip Amount: %s</string>
</resources>

File ini adalah file string.xml dalam resource dengan semua string yang akan Anda gunakan dalam aplikasi ini.

MainActivity

File ini sebagian besar berisi kode yang dihasilkan oleh template dan fungsi berikut.

  • Fungsi TipTimeLayout() berisi elemen Column dengan dua composable teks yang Anda lihat di screenshot. Fungsi ini juga memiliki composable spacer untuk menambahkan ruang untuk alasan estetika.
  • Fungsi calculateTip() yang menerima jumlah tagihan dan menghitung jumlah tip 15%. Parameter tipPercent disetel ke nilai argumen default 15.0. Untuk saat ini, nilai tip default ditetapkan ke 15%. Di codelab berikutnya, Anda akan mendapatkan jumlah tip dari pengguna.
@Composable
fun TipTimeLayout() {
    Column(
        modifier = Modifier
            .statusBarsPadding()
            .padding(horizontal = 40.dp)
            .verticalScroll(rememberScrollState())
            .safeDrawingPadding(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = stringResource(R.string.calculate_tip),
            modifier = Modifier
                .padding(bottom = 16.dp, top = 40.dp)
                .align(alignment = Alignment.Start)
        )
        Text(
            text = stringResource(R.string.tip_amount, "$0.00"),
            style = MaterialTheme.typography.displaySmall
        )
        Spacer(modifier = Modifier.height(150.dp))
    }
}
private fun calculateTip(amount: Double, tipPercent: Double = 15.0): String {
   val tip = tipPercent / 100 * amount
   return NumberFormat.getCurrencyInstance().format(tip)
}

Dalam blok Surface() onCreate(), fungsi TipTimeLayout() dipanggil. Fungsi ini akan menampilkan tata letak aplikasi di perangkat atau emulator.

override fun onCreate(savedInstanceState: Bundle?) {
   //...
   setContent {
       TipTimeTheme {
           Surface(
           //...
           ) {
               TipTimeLayout()
           }
       }
   }
}

Blok TipTimeTheme fungsi TipTimeLayoutPreview(), fungsi TipTimeLayout() dipanggil. Fungsi ini akan menampilkan tata letak aplikasi di panel Design dan di panel Split.

@Preview(showBackground = true)
@Composable
fun TipTimeLayoutPreview() {
   TipTimeTheme {
       TipTimeLayout()
   }
}

ae11354e61d2a2b9.png

Mengambil input dari pengguna

Di bagian ini, Anda akan menambahkan elemen UI yang memungkinkan pengguna memasukkan jumlah tagihan di aplikasi. Anda dapat melihat tampilannya dalam gambar ini:

58671affa01fb9e1.png

Aplikasi Anda menggunakan gaya dan tema kustom.

Gaya dan tema adalah kumpulan atribut yang menentukan tampilan untuk satu elemen UI. Gaya dapat menentukan atribut seperti warna font, ukuran font, warna latar belakang, dan lainnya yang dapat diterapkan untuk seluruh aplikasi. Codelab berikutnya akan membahas cara menerapkannya di aplikasi Anda. Untuk saat ini, hal ini telah dilakukan agar Anda dapat membuat aplikasi menjadi lebih menarik.

Untuk mendapatkan pemahaman yang lebih baik, berikut adalah perbandingan per aspek dari versi solusi aplikasi dengan dan tanpa tema kustom.

Tanpa tema kustom.

Dengan tema kustom.

Fungsi composable TextField memungkinkan pengguna memasukkan teks dalam aplikasi. Misalnya, perhatikan kotak teks di layar login aplikasi Gmail pada gambar ini:

Layar ponsel dengan aplikasi Gmail dengan kolom teks untuk email

Tambahkan composable TextField ke aplikasi:

  1. Dalam file MainActivity.kt, tambahkan fungsi composable EditNumberField(), yang menggunakan parameter Modifier.
  2. Dalam isi fungsi EditNumberField() di bawah TipTimeLayout(), tambahkan TextField yang menerima parameter bernama value yang disetel ke string kosong dan parameter bernama onValueChange yang disetel ke ekspresi lambda kosong:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
   TextField(
      value = "",
      onValueChange = {},
      modifier = modifier
   )
}
  1. Perhatikan parameter yang Anda teruskan:
  • Parameter value adalah kotak teks yang menampilkan nilai string yang Anda teruskan di sini.
  • Parameter onValueChange adalah callback lambda yang dipicu saat pengguna memasukkan teks di kotak teks.
  1. Impor fungsi ini:
import androidx.compose.material3.TextField
  1. Dalam composable TipTimeLayout(), di baris setelah fungsi composable teks pertama, panggil fungsi EditNumberField(), dengan meneruskan pengubah berikut.
import androidx.compose.foundation.layout.fillMaxWidth

@Composable
fun TipTimeLayout() {
   Column(
        modifier = Modifier
            .statusBarsPadding()
            .padding(horizontal = 40.dp)
            .verticalScroll(rememberScrollState())
            .safeDrawingPadding(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
   ) {
       Text(
           ...
       )
       EditNumberField(modifier = Modifier.padding(bottom = 32.dp).fillMaxWidth())
       Text(
           ...
       )
       ...
   }
}

Tindakan ini akan menampilkan kotak teks di layar.

  1. Di panel Design, Anda akan melihat teks Calculate Tip, kotak teks kosong, dan composable teks Tip Amount.

2c208378cd4b8d41.png

4. Menggunakan status dalam Compose

Status di aplikasi adalah nilai yang dapat berubah dari waktu ke waktu. Di aplikasi ini, status adalah jumlah tagihan.

Tambahkan variabel ke status toko:

  1. Di awal fungsi EditNumberField(), gunakan kata kunci val untuk menambahkan variabel amountInput, tetapkan ke nilai "0":
val amountInput = "0"

Ini adalah status aplikasi untuk jumlah tagihan.

  1. Setel parameter bernama value ke nilai amountInput:
TextField(
   value = amountInput,
   onValueChange = {},
)
  1. Periksa pratinjau. Kotak teks menampilkan nilai yang disetel ke variabel status seperti yang dapat Anda lihat di gambar ini:

e8e24821adfd9d8c.png

  1. Jalankan aplikasi di emulator, coba masukkan nilai lain. Status hardcode tetap tidak berubah karena composable TextField tidak memperbarui sendiri. Composable ini diperbarui saat parameter value berubah, yang disetel ke properti amountInput.

Variabel amountInput mewakili status kotak teks. Memiliki status hardcode tidak berguna karena tidak dapat dimodifikasi dan tidak menampilkan input pengguna. Anda harus mengubah status aplikasi saat pengguna memperbarui jumlah tagihan.

5. Komposisi

Composable dalam aplikasi Anda menjelaskan UI yang menampilkan kolom dengan teks, pengatur jarak, dan kotak teks. Teks menunjukkan teks Calculate Tip, dan kotak teks menampilkan nilai 0 atau apa pun nilai defaultnya.

Compose adalah framework UI deklaratif, yang berarti Anda mendeklarasikan tampilan UI dalam kode Anda. Jika ingin kotak teks menampilkan nilai 100 di awal, Anda harus menetapkan nilai awal dalam kode untuk composable ke nilai 100.

Apa yang terjadi jika Anda ingin UI berubah saat aplikasi sedang berjalan atau saat pengguna berinteraksi dengan aplikasi? Misalnya, bagaimana jika Anda ingin memperbarui variabel amountInput dengan nilai yang dimasukkan oleh pengguna dan menampilkannya di kotak teks? Saat itulah Anda mengandalkan proses yang disebut rekomposisi untuk memperbarui Komposisi aplikasi.

Komposisi adalah deskripsi UI yang di-build oleh Compose saat mengeksekusi composable. Aplikasi Compose memanggil fungsi composable untuk mengubah data menjadi UI. Jika terjadi perubahan status, Compose akan mengeksekusi kembali fungsi composable yang terpengaruh dengan status baru yang membuat UI yang diupdate—hal ini disebut rekomposisi. Compose menjadwalkan rekomposisi untuk Anda.

Saat Compose menjalankan composable Anda untuk pertama kalinya, selama komposisi awal, fitur ini terus melacak composable yang Anda panggil untuk mendeskripsikan UI Anda di Komposisi. Rekomposisi adalah saat Compose mengeksekusi ulang composable yang mungkin telah berubah sebagai respons terhadap perubahan data, lalu memperbarui Komposisi untuk menampilkan setiap perubahan.

Komposisi hanya dapat dihasilkan oleh komposisi awal dan diperbarui dengan rekomposisi. Satu-satunya cara untuk mengubah Komposisi adalah melalui rekomposisi. Untuk melakukannya, Compose perlu mengetahui status yang harus dilacak sehingga dapat menjadwalkan rekomposisi saat menerima update. Dalam kasus Anda, status ini adalah variabel amountInput, sehingga setiap kali nilainya berubah, Compose menjadwalkan rekomposisi.

Anda menggunakan jenis State dan MutableState di Compose agar status di aplikasi Anda dapat diamati, atau dilacak, oleh Compose. Jenis State tidak dapat diubah, sehingga Anda hanya dapat membaca nilai di dalamnya, sedangkan jenis MutableState dapat 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.

Nilai yang ditampilkan oleh fungsi mutableStateOf():

  • Mempertahankan status, yang merupakan jumlah tagihan.
  • Dapat diubah, sehingga nilai dapat diubah.
  • Dapat diamati, jadi Compose mengamati setiap perubahan pada nilai dan memicu rekomposisi untuk mengupdate UI.

Tambahkan status biaya layanan:

  1. Dalam fungsi EditNumberField(), ubah kata kunci val sebelum variabel status amountInput menjadi kata kunci var:
var amountInput = "0"

Hal ini akan membuat amountInput dapat diubah.

  1. Gunakan jenis MutableState<String>, bukan variabel String hardcode, sehingga Compose tahu cara melacak status amountInput, lalu meneruskan string "0" yang merupakan nilai default awal untuk variabel status amountInput:
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf

var amountInput: MutableState<String> = mutableStateOf("0")

Inisialisasi amountInput juga dapat ditulis seperti ini dengan inferensi jenis:

var amountInput = mutableStateOf("0")

Fungsi mutableStateOf() menerima nilai "0" awal sebagai argumen, yang kemudian membuat amountInput dapat diamati. Ini menyebabkan peringatan kompilasi ini di Android Studio, tetapi Anda akan segera memperbaikinya:

Creating a state object during composition without using remember.
  1. Dalam fungsi composable TextField, gunakan properti amountInput.value:
TextField(
   value = amountInput.value,
   onValueChange = {},
   modifier = modifier
)

Compose melacak setiap composable yang membaca properti value status dan memicu rekomposisi saat value berubah.

Callback onValueChange dipicu saat input kotak teks berubah. Dalam ekspresi lambda, variabel it berisi nilai baru.

  1. Dalam ekspresi lambda parameter bernama onValueChange, setel properti amountInput.value ke variabel it:
@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
   var amountInput = mutableStateOf("0")
   TextField(
       value = amountInput.value,
       onValueChange = { amountInput.value = it },
       modifier = modifier
   )
}

Anda memperbarui status TextField (yaitu variabel amountInput) saat TextField memberi tahu Anda bahwa ada perubahan dalam teks melalui fungsi callback onValueChange.

  1. Jalankan aplikasi dan masukkan teks di kotak teks. Kotak teks masih menampilkan nilai 0 seperti yang dapat Anda lihat pada gambar ini:

3a2c62f8ec55e339.gif

Saat pengguna memasukkan teks di kotak teks, callback onValueChange akan dipanggil dan variabel amountInput diperbarui dengan nilai baru. Status amountInput dilacak oleh Compose, sehingga saat nilainya berubah, rekomposisi dijadwalkan dan fungsi composable EditNumberField() dieksekusi lagi. Dalam fungsi composable tersebut, variabel amountInput direset ke nilai 0 awal. Dengan demikian, kotak teks menampilkan nilai 0.

Dengan kode yang Anda tambahkan, perubahan status menyebabkan rekomposisi dijadwalkan.

Namun, Anda memerlukan cara untuk mempertahankan nilai variabel amountInput di seluruh rekomposisi sehingga tidak direset ke nilai 0 setiap kali fungsi EditNumberField() merekomposisi. Anda akan mengatasi masalah ini di bagian berikutnya.

6. Menggunakan fungsi remember untuk menyimpan status

Metode composable bisa dipanggil berkali-kali karena rekomposisi. Composable dapat mereset statusnya selama rekomposisi jika tidak disimpan.

Fungsi composable dapat menyimpan objek di seluruh rekomposisi dengan remember. Nilai yang dihitung oleh fungsi remember disimpan dalam Komposisi selama komposisi awal dan nilai yang disimpan ditampilkan selama rekomposisi. Biasanya fungsi remember dan mutableStateOf digunakan bersama dalam fungsi composable agar status dan pembaruannya ditampilkan dengan benar di UI.

Gunakan fungsi remember dalam fungsi EditNumberField():

  1. Dalam fungsi EditNumberField(), lakukan inisialisasi variabel amountInput dengan delegasi properti Kotlin by remember, dengan mengapit panggilan ke fungsi mutableStateOf() dengan remember.
  2. Dalam fungsi mutableStateOf(), teruskan string kosong, bukan string "0" statis:
var amountInput by remember { mutableStateOf("") }

Sekarang, string kosong adalah nilai default awal untuk variabel amountInput. by adalah delegasi properti Kotlin. Fungsi pengambil dan penyetel default untuk properti amountInput didelegasikan ke fungsi pengambil dan penyetel class remember.

  1. Impor fungsi ini:
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

Menambahkan impor pengambil dan penyetel memungkinkan Anda membaca dan menyetel amountInput tanpa merujuk pada properti value dari MutableState.

Fungsi EditNumberField() yang diubah akan terlihat seperti ini:

@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
   var amountInput by remember { mutableStateOf("") }
   TextField(
       value = amountInput,
       onValueChange = { amountInput = it },
       modifier = modifier
   )
}
  1. Jalankan aplikasi dan masukkan teks di kotak teks. Anda akan melihat teks yang Anda ketik sekarang.

59ac301a208b47c4.png

7. Status dan cara kerja rekomposisi

Di bagian ini, Anda menetapkan titik henti sementara dan men-debug fungsi composable EditNumberField() untuk melihat cara kerja komposisi awal dan rekomposisi.

Tetapkan titik henti sementara dan men-debug aplikasi di emulator atau perangkat:

  1. Pada fungsi EditNumberField() di samping parameter bernama onValueChange, tetapkan titik henti sementara baris.
  2. Di menu navigasi, klik Debug 'app'. Aplikasi diluncurkan di emulator atau perangkat. Eksekusi aplikasi Anda dijeda untuk pertama kalinya saat elemen TextField dibuat.

154e060231439307.png

  1. Di panel Debug, klik 2a29a3bad712bec.png Resume Program. Kotak teks telah dibuat.
  2. Di emulator atau perangkat, masukkan huruf di kotak teks. Eksekusi aplikasi Anda akan dijeda lagi saat mencapai titik henti sementara yang Anda tetapkan.

Saat Anda memasukkan teks, callback onValueChange akan dipanggil. Di dalam lambda, it memiliki nilai baru yang Anda ketik di keypad.

Setelah nilai "it" ditetapkan ke amountInput, Compose akan memicu rekomposisi dengan data baru saat nilai yang dapat diamati telah berubah.

1d5e08d32052d02e.png

  1. Di panel Debug, klik 2a29a3bad712bec.png Resume Program. Teks yang dimasukkan di emulator atau perangkat ditampilkan di samping baris dengan titik henti sementara seperti yang terlihat dalam gambar ini:

1f5db6ab5ca5b477.png

Ini adalah status kolom teks.

  1. Klik 2a29a3bad712bec.png Resume Program. Nilai yang dimasukkan akan ditampilkan di emulator atau perangkat.

8. Mengubah tampilan

Di bagian sebelumnya, Anda mendapatkan kolom teks untuk dikerjakan. Di bagian ini, Anda akan meningkatkan UI.

Menambahkan label ke kotak teks

Setiap kotak teks harus memiliki label yang memungkinkan pengguna mengetahui informasi yang dapat dimasukkan. Di bagian pertama gambar contoh berikut, teks label berada di tengah-tengah kolom teks dan diratakan dengan baris input. Di bagian kedua gambar contoh berikut, label dipindahkan lebih tinggi di kotak teks saat pengguna mengklik di kotak teks untuk memasukkan teks. Untuk mempelajari anatomi kolom teks lebih lanjut, lihat Anatomi.

a2afd6c7fc547b06.png

Ubah fungsi EditNumberField() untuk menambahkan label ke kolom teks:

  1. Dalam fungsi composable TextField() dari fungsi EditNumberField(), tambahkan parameter bernama label yang disetel ke ekspresi lambda kosong:
TextField(
//...
   label = { }
)
  1. Dalam ekspresi lambda, panggil fungsi Text() yang menerima stringResource(R.string.bill_amount):
label = { Text(stringResource(R.string.bill_amount)) },
  1. Dalam fungsi composable TextField(), tambahkan parameter bernama singleLine yang disetel ke nilai true:
TextField(
  // ...
   singleLine = true,
)

Ini akan meringkas kotak teks dari beberapa baris menjadi satu baris yang dapat di-scroll secara horizontal.

  1. Tambahkan parameter keyboardOptions yang disetel ke KeyboardOptions():
import androidx.compose.foundation.text.KeyboardOptions

TextField(
  // ...
   keyboardOptions = KeyboardOptions(),
)

Android menyediakan opsi untuk mengonfigurasi keyboard yang ditampilkan di layar guna memasukkan angka, alamat email, URL, dan sandi, serta beberapa opsi lainnya. Untuk mempelajari jenis keyboard lainnya lebih lanjut, lihat KeyboardType.

  1. Setel jenis keyboard ke keyboard angka untuk memasukkan angka. Teruskan fungsi KeyboardOptions dengan parameter bernama keyboardType yang disetel ke KeyboardType.Number:
import androidx.compose.ui.text.input.KeyboardType

TextField(
  // ...
   keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
)

Fungsi EditNumberField() yang telah selesai akan terlihat seperti cuplikan kode ini:

@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
    var amountInput by remember { mutableStateOf("") }
    TextField(
        value = amountInput,
        onValueChange = { amountInput = it },
        singleLine = true,
        label = { Text(stringResource(R.string.bill_amount)) },
        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
        modifier = modifier
    )
}
  1. Jalankan aplikasi.

Anda dapat melihat perubahan pada keypad di screenshot ini:

55936268bf007ee9.png

9. Menampilkan jumlah tip

Di bagian ini, Anda akan menerapkan fungsi utama aplikasi, yaitu kemampuan untuk menghitung dan menampilkan jumlah tip.

Dalam file MainActivity.kt, fungsi private calculateTip() diberikan kepada Anda sebagai bagian dari kode awal. Anda akan menggunakan fungsi ini untuk menghitung jumlah tip:

private fun calculateTip(amount: Double, tipPercent: Double = 15.0): String {
    val tip = tipPercent / 100 * amount
    return NumberFormat.getCurrencyInstance().format(tip)
}

Dalam metode di atas, Anda menggunakan NumberFormat untuk menampilkan format tip sebagai mata uang.

Sekarang aplikasi Anda dapat menghitung tip, tetapi Anda masih perlu memformat dan menampilkannya dengan class.

Menggunakan fungsi calculateTip()

Teks yang dimasukkan oleh pengguna dalam composable kolom teks ditampilkan ke fungsi callback onValueChange sebagai String meskipun pengguna memasukkan angka. Untuk memperbaikinya, Anda harus mengonversi nilai amountInput yang berisi jumlah yang dimasukkan oleh pengguna.

  1. Dalam fungsi composable EditNumberField(), buat variabel baru bernama amount setelah definisi amountInput. Panggil fungsi toDoubleOrNull pada variabel amountInput, untuk mengonversi String menjadi Double:
val amount = amountInput.toDoubleOrNull()

Fungsi toDoubleOrNull() adalah fungsi Kotlin yang telah ditentukan dan mengurai string sebagai angka Double serta menampilkan hasil atau null jika string bukan representasi angka yang valid.

  1. Di akhir pernyataan, tambahkan operator Elvis ?: yang menampilkan nilai 0.0 jika amountInput adalah null:
val amount = amountInput.toDoubleOrNull() ?: 0.0
  1. Setelah variabel amount, buat variabel val lain bernama tip. Lakukan inisialisasi dengan calculateTip(), dengan meneruskan parameter amount.
val tip = calculateTip(amount)

Fungsi EditNumberField() akan terlihat seperti cuplikan kode ini:

@Composable
fun EditNumberField(modifier: Modifier = Modifier) {
   var amountInput by remember { mutableStateOf("") }

   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount)

   TextField(
       value = amountInput,
       onValueChange = { amountInput = it },
       label = { Text(stringResource(R.string.bill_amount)) },
       modifier = Modifier.fillMaxWidth(),
       singleLine = true,
       keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number)
   )
}

Menampilkan jumlah tip yang dihitung

Anda telah menulis fungsi untuk menghitung jumlah tip, langkah selanjutnya adalah menampilkan jumlah tip yang dihitung:

  1. Dalam fungsi TipTimeLayout() di akhir blok Column(), perhatikan composable teks yang menampilkan $0.00. Anda akan memperbarui nilai ini ke jumlah tip yang dihitung.
@Composable
fun TipTimeLayout() {
    Column(
        modifier = Modifier
            .statusBarsPadding()
            .padding(horizontal = 40.dp)
            .verticalScroll(rememberScrollState())
            .safeDrawingPadding(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        // ...
        Text(
            text = stringResource(R.string.tip_amount, "$0.00"),
            style = MaterialTheme.typography.displaySmall
        )
        // ...
    }
}

Anda perlu mengakses variabel amountInput dalam fungsi TipTimeLayout() untuk menghitung dan menampilkan jumlah tip, tetapi variabel amountInput adalah status kolom teks yang ditentukan dalam fungsi composable EditNumberField() sehingga Anda belum dapat memanggilnya dari fungsi TipTimeLayout(). Gambar ini mengilustrasikan struktur kode:

50bf0b9d18ede6be.png

Struktur ini tidak akan memungkinkan Anda menampilkan jumlah tip di composable Text baru karena composable Text perlu mengakses variabel amount yang dihitung dari variabel amountInput. Anda perlu mengekspos variabel amount ke fungsi TipTimeLayout(). Gambar ini mengilustrasikan struktur kode yang diinginkan yang membuat composable EditNumberField() menjadi stateless:

ab4ec72388149f7c.png

Pola ini disebut pengangkatan status. Di bagian berikutnya, Anda akan mengangkat atau meningkatkan status dari composable untuk membuatnya stateless.

10. Pengangkatan status

Di bagian ini, Anda akan mempelajari cara menentukan tempat untuk menentukan status dengan cara yang dapat digunakan kembali dan membagikan composable Anda.

Dalam fungsi composable, Anda dapat menentukan variabel yang menyimpan status untuk ditampilkan di UI. Misalnya, Anda menentukan variabel amountInput sebagai status dalam composable EditNumberField().

Saat aplikasi menjadi lebih kompleks dan composable lainnya memerlukan akses ke status dalam composable EditNumberField(), Anda perlu mempertimbangkan untuk mengangkat, atau mengekstrak, status dari fungsi composable EditNumberField().

Memahami composable stateful versus stateless

Anda harus mengangkat status saat Anda perlu:

  • Membagikan status dengan beberapa fungsi composable.
  • Membuat composable stateless yang dapat digunakan kembali di aplikasi Anda.

Saat Anda mengekstrak status dari fungsi composable, hasil fungsi composable ini akan disebut stateless. Artinya, fungsi composable dapat dibuat stateless dengan mengekstrak status darinya.

Composable stateless adalah composable yang tidak memiliki status, yang berarti composable tersebut tidak memiliki, menentukan, atau memodifikasi status baru. Di sisi lain, composable stateful adalah composable yang memiliki bagian status yang dapat berubah dari waktu ke waktu.

Pengangkatan status adalah pola untuk memindahkan status ke pemanggil agar komponen menjadi stateless.

Jika diterapkan pada composable, hal ini terkadang berarti memasukkan dua parameter ke composable:

  • Parameter value: T, yang merupakan nilai saat ini untuk ditampilkan.
  • onValueChange: (T) -> Unit – lambda callback, yang dipicu saat nilai berubah sehingga status dapat diperbarui di tempat lain, seperti saat pengguna memasukkan teks di kotak teks.

Mengangkat status di fungsi EditNumberField():

  1. Perbarui definisi fungsi EditNumberField(), untuk mengangkat status dengan menambahkan parameter value dan onValueChange:
@Composable
fun EditNumberField(
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) {
//...

Parameter value berjenis String, dan parameter onValueChange berjenis (String) -> Unit, sehingga merupakan fungsi yang menggunakan nilai String sebagai input dan tidak memiliki nilai yang ditampilkan. Parameter onValueChange digunakan sebagai callback onValueChange yang diteruskan ke composable TextField.

  1. Dalam fungsi EditNumberField(), perbarui fungsi composable TextField() untuk menggunakan parameter yang diteruskan:
TextField(
   value = value,
   onValueChange = onValueChange,
   // Rest of the code
)
  1. Mengangkat status, memindahkan status yang diingat dari fungsi EditNumberField() ke fungsi TipTimeLayout():
@Composable
fun TipTimeLayout() {
   var amountInput by remember { mutableStateOf("") }

   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount)
  
   Column(
       //...
   ) {
       //...
   }
}
  1. Anda telah mengangkat status ke TipTimeLayout(), sekarang teruskan ke EditNumberField(). Dalam fungsi TipTimeLayout(), perbarui panggilan fungsi EditNumberField() untuk menggunakan status yang diangkat:
EditNumberField(
   value = amountInput,
   onValueChange = { amountInput = it },
   modifier = Modifier
       .padding(bottom = 32.dp)
       .fillMaxWidth()
)

Hal ini membuat EditNumberField menjadi stateless. Anda mengangkat status UI ke ancestornya, TipTimeLayout(). TipTimeLayout() adalah pemilik status (amountInput) sekarang.

Pemformatan posisi

Pemformatan posisi digunakan untuk menampilkan konten dinamis dalam string. Misalnya, Anda menginginkan kotak teks Jumlah tip menampilkan nilai xx.xx dalam jumlah berapa pun yang dihitung dan diformat dalam fungsi Anda. Agar dapat melakukannya dalam file strings.xml, Anda perlu menentukan resource string dengan argumen placeholder, seperti cuplikan kode ini:

// No need to copy.

// In the res/values/strings.xml file
<string name="tip_amount">Tip Amount: %s</string>

Dalam kode compose, Anda dapat memiliki beberapa argumen placeholder dengan jenis apa pun. Placeholder string adalah %s.

Perhatikan composable teks di TipTimeLayout(), Anda meneruskan tip berformat sebagai argumen ke fungsi stringResource().

// No need to copy
Text(
   text = stringResource(R.string.tip_amount, "$0.00"),
   style = MaterialTheme.typography.displaySmall
)
  1. Dalam fungsi, TipTimeLayout(), gunakan properti tip untuk menampilkan jumlah tip. Perbarui parameter text composable Text untuk menggunakan variabel tip sebagai parameter.
Text(
     text = stringResource(R.string.tip_amount, tip),
     // ...

Fungsi TipTimeLayout() dan EditNumberField() yang selesai akan terlihat seperti cuplikan kode ini:

@Composable
fun TipTimeLayout() {
   var amountInput by remember { mutableStateOf("") }
   val amount = amountInput.toDoubleOrNull() ?: 0.0
   val tip = calculateTip(amount)

   Column(
       modifier = Modifier
            .statusBarsPadding()
            .padding(horizontal = 40.dp)
            .verticalScroll(rememberScrollState())
            .safeDrawingPadding(),
       horizontalAlignment = Alignment.CenterHorizontally,
       verticalArrangement = Arrangement.Center
   ) {
       Text(
           text = stringResource(R.string.calculate_tip),
           modifier = Modifier
               .padding(bottom = 16.dp, top = 40.dp)
               .align(alignment = Alignment.Start)
       )
       EditNumberField(
           value = amountInput,
           onValueChange = { amountInput = 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))
   }
}

@Composable
fun EditNumberField(
   value: String,
   onValueChange: (String) -> Unit,
   modifier: Modifier = Modifier
) {
   TextField(
       value = value,
       onValueChange = onValueChange,
       singleLine = true,
       label = { Text(stringResource(R.string.bill_amount)) },
       keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number),
       modifier = modifier
   )
}

Singkatnya, Anda mengangkat status amountInput dari EditNumberField() ke dalam composable TipTimeLayout(). Agar kotak teks berfungsi seperti sebelumnya, Anda harus meneruskan dua argumen ke fungsi composable EditNumberField(): nilai amountInput dan callback lambda yang memperbarui nilai amountInput dari input pengguna. Perubahan ini memungkinkan Anda menghitung tip dari properti amountInput di TipTimeLayout() untuk menampilkannya kepada pengguna.

  1. Jalankan aplikasi di emulator atau perangkat, lalu masukkan nilai di kotak teks jumlah tagihan. Jumlah tip 15 persen dari jumlah tagihan ditampilkan seperti yang dapat Anda lihat pada gambar ini:

de593783dc813e24.png

11. Mendapatkan kode solusi

Untuk mendownload kode codelab yang sudah selesai, Anda dapat menggunakan perintah git berikut:

$ 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

Atau, Anda dapat mendownload repositori sebagai file zip, mengekstraknya, dan membukanya di Android Studio.

Jika Anda ingin melihat kode solusi, lihat di GitHub.

12. Kesimpulan

Selamat! Anda telah menyelesaikan codelab ini dan mempelajari cara menggunakan status dalam aplikasi Compose!

Ringkasan

  • Status di aplikasi adalah nilai yang dapat berubah dari waktu ke waktu.
  • Komposisi adalah deskripsi UI yang di-build oleh Compose saat mengeksekusi composable. Aplikasi Compose memanggil fungsi composable untuk mengubah data menjadi UI.
  • Komposisi awal adalah pembuatan UI oleh Compose saat pertama kali mengeksekusi fungsi composable.
  • Rekomposisi adalah proses menjalankan kembali composable yang sama untuk memperbarui hierarki saat datanya berubah.
  • Pengangkatan status adalah pola untuk memindahkan status ke pemanggil agar komponen menjadi stateless.

Pelajari lebih lanjut