1. Sebelum memulai
Di codelab sebelumnya, Anda telah mempelajari coroutine. Anda telah menggunakan Kotlin Playground untuk menulis kode serentak menggunakan coroutine. Dalam codelab ini, Anda akan menerapkan pengetahuan tentang coroutine dalam aplikasi Android dan siklus prosesnya. Anda akan menambahkan kode untuk meluncurkan coroutine baru secara serentak dan mempelajari cara mengujinya.
Prasyarat
- Pengetahuan tentang dasar-dasar bahasa Kotlin, termasuk fungsi dan lambda
- Mampu membuat tata letak di Jetpack Compose
- Mampu menulis pengujian unit di Kotlin (lihat codelab Menulis pengujian unit untuk ViewModel)
- Cara kerja thread dan konkurensi
- Pengetahuan dasar tentang coroutine dan CoroutineScope
Yang akan Anda bangun
- Aplikasi Race Tracker yang menyimulasikan progres perlombaan di antara dua pemain. Pertimbangkan aplikasi ini sebagai peluang untuk bereksperimen dan mempelajari lebih lanjut berbagai aspek coroutine.
Yang akan Anda pelajari
- Menggunakan coroutine dalam siklus proses aplikasi Android.
- Prinsip-prinsip konkurensi terstruktur.
- Cara menulis pengujian unit untuk menguji coroutine.
Yang akan Anda butuhkan
- Versi stabil terbaru Android Studio
2. Ringkasan aplikasi
Aplikasi Race Tracker menyimulasikan dua pemain yang berlari dalam sebuah perlombaan. UI aplikasi terdiri dari dua tombol, Start/Pause (Mulai/Jeda) dan Reset, serta dua status progres untuk menampilkan progres pembalap. Pemain 1 dan 2 bersiap untuk memulai perlombaan dengan kecepatan yang berbeda. Saat perlombaan dimulai, Pemain 2 melaju dua kali lebih cepat dari Pemain 1.
Anda akan menggunakan coroutine di aplikasi ini untuk memastikan:
- Kedua pemain memulai perlombaan secara bersamaan.
- UI aplikasi bersifat responsif dan status progres akan bertambah selama perlombaan.
Kode awal memiliki kode UI yang siap untuk aplikasi Race Tracker. Fokus utama bagian codelab ini adalah membantu Anda memahami coroutine Kotlin di dalam aplikasi Android.
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-race-tracker.git $ cd basic-android-kotlin-compose-training-race-tracker $ git checkout starter
Anda dapat menjelajahi kode awal di repositori GitHub Race Tracker.
Panduan kode awal
Anda dapat memulai perlombaan dengan mengklik tombol Start. Teks tombol Start berubah menjadi Pause saat lomba berlangsung.
Anda dapat menggunakan tombol ini kapan pun untuk menjeda atau melanjutkan lomba.
Saat lomba dimulai, Anda dapat melihat progres setiap pemain melalui indikator status. Fungsi composable StatusIndicator
menampilkan status progres setiap pemain. Dalam perlombaan ini, composable LinearProgressIndicator
digunakan untuk menampilkan status progres. Anda akan menggunakan coroutine untuk memperbarui nilai progres.
RaceParticipant
menyediakan data untuk penambahan progres. Class ini merupakan holder status untuk setiap pemain dan mempertahankan name
peserta, maxProgress
yang harus dicapai untuk menyelesaikan perlombaan, durasi penundaan di antara penambahan progres, currentProgress
dalam perlombaan, dan initialProgress
.
Di bagian berikutnya, Anda akan menggunakan coroutine untuk menerapkan fungsi simulasi progres perlombaan tanpa memblokir UI aplikasi.
3. Menerapkan progres perlombaan
Anda memerlukan fungsi run()
yang membandingkan antara currentProgress
dan maxProgress
pemain, menampilkan progres total perlombaan, dan menggunakan fungsi penangguhan delay()
untuk menambahkan sedikit penundaan di antara penambahan progres. Fungsi ini harus berupa fungsi suspend
karena memanggil fungsi penangguhan delay()
lain. Selain itu, Anda juga akan memanggil fungsi ini nanti di codelab dari coroutine. Ikuti langkah-langkah berikut untuk menerapkan fungsi:
- Buka class
RaceParticipant
yang merupakan bagian dari kode awal. - Di dalam class
RaceParticipant
, tentukan fungsisuspend
baru yang bernamarun()
.
class RaceParticipant(
...
) {
var currentProgress by mutableStateOf(initialProgress)
private set
suspend fun run() {
}
...
}
- Untuk menyimulasikan progres perlombaan, tambahkan loop
while
yang berjalan hinggacurrentProgress
mencapai nilaimaxProgress
yang disetel ke100
.
class RaceParticipant(
...
val maxProgress: Int = 100,
...
) {
var currentProgress by mutableStateOf(initialProgress)
private set
suspend fun run() {
while (currentProgress < maxProgress) {
}
}
...
}
- Nilai
currentProgress
disetel keinitialProgress
, yaitu0
. Untuk menyimulasikan progres peserta, tambahkan nilaicurrentProgress
dengan nilai propertiprogressIncrement
di dalam loop while. Perhatikan bahwa nilai defaultprogressIncrement
adalah1
.
class RaceParticipant(
...
val maxProgress: Int = 100,
...
private val progressIncrement: Int = 1,
private val initialProgress: Int = 0
) {
...
var currentProgress by mutableStateOf(initialProgress)
private set
suspend fun run() {
while (currentProgress < maxProgress) {
currentProgress += progressIncrement
}
}
}
- Untuk menyimulasikan berbagai interval progres dalam perlombaan, gunakan fungsi penangguhan
delay()
. Teruskan nilai propertiprogressDelayMillis
sebagai argumen.
suspend fun run() {
while (currentProgress < maxProgress) {
delay(progressDelayMillis)
currentProgress += progressIncrement
}
}
Saat melihat kode yang baru saja ditambahkan, Anda akan melihat ikon di sebelah kiri panggilan ke fungsi delay()
di Android Studio, seperti yang ditunjukkan dalam screenshot di bawah:
Ikon ini menunjukkan titik penangguhan saat fungsi mungkin ditangguhkan dan dilanjutkan lagi nanti.
Thread utama tidak diblokir saat coroutine menunggu untuk menyelesaikan durasi penundaan, seperti yang ditunjukkan dalam diagram berikut:
Coroutine menangguhkan (tetapi tidak memblokir) eksekusi setelah memanggil fungsi delay()
dengan nilai interval yang diinginkan. Setelah penundaan selesai, coroutine akan melanjutkan eksekusi dan memperbarui nilai properti currentProgress
.
4. Memulai perlombaan
Saat pengguna menekan tombol Start, Anda harus "memulai perlombaan" dengan memanggil fungsi penangguhan run()
di kedua instance pemain. Agar dapat melakukannya, Anda harus meluncurkan coroutine untuk memanggil fungsi run()
.
Saat meluncurkan coroutine untuk memulai perlombaan, Anda harus memastikan aspek berikut untuk kedua peserta:
- Kedua peserta mulai berlari setelah tombol Start diklik—yang berarti coroutine diluncurkan.
- Keduanya akan dijeda atau berhenti berjalan saat tombol Pause atau Reset diklik—yang berarti coroutine dibatalkan.
- Saat pengguna menutup aplikasi, pembatalan akan dikelola dengan benar—yang berarti semua coroutine dibatalkan dan terikat pada siklus proses.
Dalam codelab pertama, Anda telah mempelajari bahwa Anda hanya dapat memanggil fungsi penangguhan dari fungsi penangguhan lainnya. Untuk memanggil fungsi penangguhan dengan aman dari dalam composable, Anda harus menggunakan composable LaunchedEffect()
. Composable LaunchedEffect()
menjalankan fungsi penangguhan yang disediakan selama elemen tersebut tetap berada dalam komposisi. Anda dapat menggunakan fungsi composable LaunchedEffect()
untuk menyelesaikan semua tindakan berikut:
- Dengan composable
LaunchedEffect()
, Anda dapat memanggil fungsi penangguhan dengan aman dari composable. - Saat memasuki Komposisi, fungsi
LaunchedEffect()
akan meluncurkan coroutine dengan blok kode yang diteruskan sebagai parameter. Fungsi ini menjalankan fungsi penangguhan yang disediakan selama elemen tersebut tetap berada dalam komposisi. Saat pengguna mengklik tombol Start di aplikasi RaceTracker,LaunchedEffect()
akan memasuki komposisi dan meluncurkan coroutine untuk memperbarui progres. - Coroutine dibatalkan saat
LaunchedEffect()
keluar dari komposisi. Jika pengguna mengklik tombol Reset/Pause di aplikasi,LaunchedEffect()
akan dihapus dari komposisi dan coroutine dasar akan dibatalkan.
Untuk aplikasi RaceTracker, Anda tidak perlu menyediakan Dispatcher secara eksplisit karena akan ditangani oleh LaunchedEffect()
.
Untuk memulai perlombaan, panggil fungsi run()
untuk setiap peserta dan lakukan langkah-langkah berikut:
- Buka file
RaceTrackerApp.kt
yang berada dalam paketcom.example.racetracker.ui
. - Buka composable
RaceTrackerApp()
dan tambahkan panggilan ke composableLaunchedEffect()
di baris setelah definisiraceInProgress
.
@Composable
fun RaceTrackerApp() {
...
var raceInProgress by remember { mutableStateOf(false) }
LaunchedEffect {
}
RaceTrackerScreen(...)
}
- Untuk memastikan bahwa jika instance
playerOne
atauplayerTwo
diganti dengan instance lain, makaLaunchedEffect()
harus membatalkan dan meluncurkan kembali coroutine dasar, kemudian menambahkan objekplayerOne
danplayerTwo
sebagaikey
keLaunchedEffect
. Serupa dengan cara merekomposisi composableText()
saat nilai teksnya berubah, jika salah satu argumen utama dariLaunchedEffect()
berubah, coroutine dasar akan dibatalkan dan diluncurkan kembali.
LaunchedEffect(playerOne, playerTwo) {
}
- Tambahkan panggilan ke fungsi
playerOne.run()
danplayerTwo.run()
.
@Composable
fun RaceTrackerApp() {
...
var raceInProgress by remember { mutableStateOf(false) }
LaunchedEffect(playerOne, playerTwo) {
playerOne.run()
playerTwo.run()
}
RaceTrackerScreen(...)
}
- Gabungkan blok
LaunchedEffect()
dengan kondisiif
. Nilai awal untuk status ini adalahfalse
. Nilai untuk statusraceInProgress
diperbarui menjaditrue
saat pengguna mengklik tombol Start danLaunchedEffect()
dieksekusi.
if (raceInProgress) {
LaunchedEffect(playerOne, playerTwo) {
playerOne.run()
playerTwo.run()
}
}
- Perbarui tanda
raceInProgress
kefalse
untuk menyelesaikan perlombaan. Nilai ini akan disetel kefalse
saat pengguna juga mengklik Pause. Jika nilai ini disetel kefalse
,LaunchedEffect()
akan memastikan bahwa semua coroutine yang diluncurkan dibatalkan.
LaunchedEffect(playerOne, playerTwo) {
playerOne.run()
playerTwo.run()
raceInProgress = false
}
- Jalankan aplikasi, lalu klik Start. Anda akan melihat pemain satu menyelesaikan lomba sebelum pemain dua mulai berlari, seperti yang ditunjukkan dalam video berikut:
Sepertinya bukan lomba yang adil. Di bagian berikutnya, Anda akan mempelajari cara meluncurkan tugas serentak sehingga kedua pemain dapat berlari secara bersamaan, memahami konsep, dan menerapkan perilaku ini.
5. Konkurensi terstruktur
Cara Anda menulis kode menggunakan coroutine disebut konkurensi terstruktur. Gaya pemrograman ini akan meningkatkan keterbacaan dan waktu pengembangan kode Anda. Konkurensi terstruktur ini dimaksudkan agar coroutine dapat memiliki hierarki—tugas dapat meluncurkan subtugas, yang mungkin meluncurkan subtugas secara bergantian. Unit hierarki ini disebut sebagai cakupan coroutine. Cakupan Coroutine harus selalu dikaitkan dengan siklus proses.
Coroutine API mematuhi desain konkurensi terstruktur ini. Anda tidak dapat memanggil fungsi penangguhan dari fungsi yang tidak ditandai sebagai ditangguhkan. Batasan ini memastikan Anda memanggil fungsi penangguhan dari builder coroutine, seperti launch
. Builder ini selanjutnya akan terikat dengan CoroutineScope
.
6. Meluncurkan tugas serentak
- Agar kedua peserta dapat berlari secara bersamaan, Anda harus meluncurkan dua coroutine terpisah dan memindahkan setiap panggilan ke fungsi
run()
di dalam coroutine tersebut. Gabungkan panggilan keplayerOne.run()
dengan builderlaunch
.
LaunchedEffect(playerOne, playerTwo) {
launch { playerOne.run() }
playerTwo.run()
raceInProgress = false
}
- Demikian pula, gabungkan panggilan ke fungsi
playerTwo.run()
dengan builderlaunch
. Dengan perubahan ini, aplikasi akan meluncurkan dua coroutine yang dieksekusi serentak. Sekarang kedua pemain dapat dijalankan secara bersamaan.
LaunchedEffect(playerOne, playerTwo) {
launch { playerOne.run() }
launch { playerTwo.run() }
raceInProgress = false
}
- Jalankan aplikasi, lalu klik Start. Saat Anda menunggu lomba dimulai, teks tombol tiba-tiba langsung berubah kembali menjadi Start.
Saat kedua pemain menyelesaikan lomba lari, aplikasi Race Tracker harus mereset teks tombol Pause kembali ke Start. Namun, sekarang aplikasi akan langsung memperbarui raceInProgress
setelah meluncurkan coroutine tanpa menunggu pemain menyelesaikan perlombaan:
LaunchedEffect(playerOne, playerTwo) {
launch {playerOne.run() }
launch {playerTwo.run() }
raceInProgress = false // This will update the state immediately, without waiting for players to finish run() execution.
}
Flag raceInProgress
segera diperbarui karena:
- Fungsi builder
launch
meluncurkan coroutine untuk mengeksekusiplayerOne.run()
dan segera kembali untuk mengeksekusi baris berikutnya dalam blok kode. - Alur eksekusi yang sama terjadi dengan fungsi builder
launch
kedua yang menjalankan fungsiplayerTwo.run()
. - Segera setelah builder
launch
kedua ditampilkan, tandaraceInProgress
akan diperbarui. Pembaruan flag ini akan langsung mengubah teks tombol menjadi Start dan perlombaan tidak dimulai.
Cakupan Coroutine
Fungsi penangguhan coroutineScope
membuat CoroutineScope
dan memanggil blok penangguhan yang ditentukan dengan cakupan saat ini. Cakupan mewarisi coroutineContext
dari cakupan LaunchedEffect()
.
Cakupan akan segera ditampilkan setelah blok yang diberikan dan semua coroutine turunannya selesai. Untuk aplikasi RaceTracker
, cakupan akan ditampilkan setelah kedua objek peserta selesai menjalankan fungsi run()
.
- Untuk memastikan fungsi
run()
dariplayerOne
danplayerTwo
menyelesaikan eksekusi sebelum memperbarui tandaraceInProgress
, gabungkan kedua builder peluncuran dengan blokcoroutineScope
.
LaunchedEffect(playerOne, playerTwo) {
coroutineScope {
launch { playerOne.run() }
launch { playerTwo.run() }
}
raceInProgress = false
}
- Jalankan aplikasi di emulator/perangkat Android. Anda akan melihat layar berikut:
- Klik tombol Start. Pemain 2 berlari lebih cepat daripada Pemain 1. Setelah perlombaan selesai, yaitu saat kedua pemain mencapai progres 100%, label untuk tombol Pause akan berubah menjadi Start. Anda dapat mengklik tombol Reset untuk mereset perlombaan dan menjalankan kembali simulasi. Lomba ditampilkan dalam video berikut.
Alur eksekusi ditampilkan dalam diagram berikut:
- Saat blok
LaunchedEffect()
dieksekusi, kontrol akan ditransfer ke blokcoroutineScope{..}
. - Blok
coroutineScope
akan meluncurkan kedua coroutine secara serentak dan menunggu hingga selesai dieksekusi. - Setelah eksekusi selesai, flag
raceInProgress
akan diperbarui.
Blok coroutineScope
hanya kembali dan bergerak setelah semua kode di dalam blok menyelesaikan eksekusi. Untuk kode di luar blok, ada atau tidaknya konkurensi hanya akan menjadi detail penerapan. Gaya coding ini memberikan pendekatan terstruktur untuk pemrograman serentak dan disebut sebagai konkurensi terstruktur.
Saat Anda mengklik tombol Reset setelah perlombaan selesai, coroutine akan dibatalkan dan progres kedua pemain akan direset ke 0
.
Untuk mengetahui cara membatalkan coroutine saat pengguna mengklik tombol Reset, ikuti langkah-langkah berikut:
- Gabungkan isi metode
run()
dalam blok try-catch seperti yang ditunjukkan pada kode berikut:
suspend fun run() {
try {
while (currentProgress < maxProgress) {
delay(progressDelayMillis)
currentProgress += progressIncrement
}
} catch (e: CancellationException) {
Log.e("RaceParticipant", "$name: ${e.message}")
throw e // Always re-throw CancellationException.
}
}
- Jalankan aplikasi lalu klik tombol Start.
- Setelah beberapa penambahan progres, klik tombol Reset.
- Pastikan Anda melihat pesan yang dicetak di Logcat berikut:
Player 1: StandaloneCoroutine was cancelled Player 2: StandaloneCoroutine was cancelled
7. Menulis pengujian unit untuk menguji coroutine
Kode pengujian unit yang menggunakan coroutine memerlukan perhatian tambahan karena eksekusinya dapat terjadi secara asinkron dan terjadi di beberapa thread.
Untuk memanggil fungsi penangguhan dalam pengujian, Anda harus berada di coroutine. Fungsi pengujian JUnit itu sendiri bukan merupakan fungsi penangguhan. Oleh karena itu, Anda harus menggunakan builder coroutine runTest
. Builder ini adalah bagian dari library kotlinx-coroutines-test
dan dirancang untuk menjalankan pengujian. Builder menjalankan isi pengujian dalam coroutine baru.
runTest
adalah bagian dari library kotlinx-coroutines-test
sehingga Anda perlu menambahkan dependensinya.
Untuk menambahkan dependensi, selesaikan langkah-langkah berikut:
- Buka file
build.gradle.kts
modul aplikasi yang terletak di direktoriapp
di panel Project.
- Di dalam file, scroll ke bawah hingga Anda menemukan blok
dependencies{}
. - Tambahkan dependensi menggunakan konfigurasi
testImplementation
ke librarykotlinx-coroutines-test
.
plugins {
...
}
android {
...
}
dependencies {
...
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
- Di baris notifikasi pada bagian atas file build.gradle.kts, klik Sync Now agar proses impor dan build selesai seperti yang ditunjukkan pada screenshot berikut:
Setelah build selesai, Anda dapat mulai menulis pengujian.
Menerapkan pengujian unit untuk memulai dan menyelesaikan perlombaan
Untuk memastikan progres perlombaan diperbarui dengan benar selama berbagai fase perlombaan, pengujian unit Anda harus mencakup berbagai skenario. Untuk codelab ini, ada dua skenario yang dibahas:
- Progres setelah perlombaan dimulai.
- Progres setelah perlombaan selesai.
Untuk memeriksa apakah progres perlombaan diperbarui dengan benar setelah perlombaan dimulai, Anda harus menyatakan bahwa progres saat ini disetel ke 1 setelah durasi raceParticipant.progressDelayMillis
dilewati.
Untuk menerapkan skenario pengujian, ikuti langkah-langkah berikut:
- Buka file
RaceParticipantTest.kt
yang berada di bagian set sumber pengujian. - Untuk menentukan pengujian, setelah definisi
raceParticipant
, buat fungsiraceParticipant_RaceStarted_ProgressUpdated()
dan anotasikan dengan anotasi@Test
. Gunakan sintaksis ekspresi untuk menampilkan blokrunTest()
sebagai hasil pengujian karena blok pengujian harus ditempatkan di builderrunTest
.
class RaceParticipantTest {
private val raceParticipant = RaceParticipant(
...
)
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
}
}
- Tambahkan variabel
expectedProgress
hanya baca dan setel ke1
.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
}
- Untuk menyimulasikan dimulainya perlombaan, gunakan builder
launch
untuk meluncurkan coroutine baru dan memanggil fungsiraceParticipant.run()
.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
}
Nilai properti raceParticipant.progressDelayMillis
menentukan durasi progres perlombaan setelah diperbarui. Untuk menguji progres setelah waktu progressDelayMillis
berlalu, Anda perlu menambahkan beberapa bentuk penundaan ke pengujian.
- Gunakan fungsi bantuan
advanceTimeBy()
untuk memajukan waktu dengan nilairaceParticipant.progressDelayMillis
. FungsiadvanceTimeBy()
membantu mengurangi waktu eksekusi uji.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.progressDelayMillis)
}
- Anda harus memanggil fungsi
runCurrent()
. karenaadvanceTimeBy()
tidak menjalankan tugas yang dijadwalkan pada durasi tertentu. Fungsi ini akan menjalankan semua tugas yang tertunda pada saat ini.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.progressDelayMillis)
runCurrent()
}
- Untuk memastikan progres diperbarui, tambahkan panggilan ke fungsi
assertEquals()
untuk memeriksa apakah nilai propertiraceParticipant.currentProgress
cocok dengan nilai variabelexpectedProgress
.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
val expectedProgress = 1
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.progressDelayMillis)
runCurrent()
assertEquals(expectedProgress, raceParticipant.currentProgress)
}
- Jalankan pengujian untuk mengonfirmasi bahwa pengujian berhasil.
Untuk memeriksa apakah progres perlombaan diperbarui dengan benar setelah perlombaan selesai, Anda perlu menegaskan bahwa ketika perlombaan telah selesai, progres saat ini akan disetel ke 100
.
Ikuti langkah-langkah berikut untuk menerapkan pengujian:
- Setelah fungsi pengujian
raceParticipant_RaceStarted_ProgressUpdated()
, buat fungsiraceParticipant_RaceFinished_ProgressUpdated()
dan anotasikan dengan anotasi@Test
. Fungsi ini akan menampilkan hasil pengujian dari blokrunTest{}
.
class RaceParticipantTest {
...
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
...
}
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
}
}
- Gunakan builder
launch
untuk meluncurkan coroutine baru dan menambahkan panggilan ke fungsiraceParticipant.run()
di dalamnya.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
}
- Untuk menyimulasikan hasil akhir perlombaan, gunakan fungsi
advanceTimeBy()
untuk memajukan waktu dispatcher denganraceParticipant.maxProgress * raceParticipant.progressDelayMillis
:
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
}
- Tambahkan panggilan ke fungsi
runCurrent()
untuk menjalankan tugas yang tertunda.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
runCurrent()
}
- Untuk memastikan progres diperbarui dengan benar, tambahkan panggilan ke fungsi
assertEquals()
untuk memastikan nilai propertiraceParticipant.currentProgress
sama dengan100
.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
launch { raceParticipant.run() }
advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
runCurrent()
assertEquals(100, raceParticipant.currentProgress)
}
- Jalankan pengujian untuk mengonfirmasi bahwa pengujian berhasil.
Coba tantangan ini
Terapkan strategi pengujian yang telah dibahas dalam codelab Menulis pengujian unit untuk ViewModel. Tambahkan pengujian untuk mencakup jalur tanpa error, kasus error, dan kasus batas.
Bandingkan pengujian yang Anda tulis dengan pengujian yang tersedia dalam kode solusi.
8. 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-race-tracker.git cd basic-android-kotlin-compose-training-race-tracker
Atau, Anda dapat mendownload repositori sebagai file ZIP, lalu mengekstraknya, dan membukanya di Android Studio.
Jika Anda ingin melihat kode solusi, lihat di GitHub.
9. Kesimpulan
Selamat! Anda baru saja mempelajari cara menggunakan coroutine untuk menangani konkurensi. Coroutine membantu mengelola tugas berdurasi panjang yang mungkin memblokir thread utama dan menyebabkan aplikasi tidak responsif. Anda juga telah mempelajari cara menulis pengujian unit untuk menguji coroutine.
Fitur berikut merupakan beberapa manfaat coroutine:
- Keterbacaan: Kode yang Anda tulis dengan coroutine memberikan pemahaman jelas terkait urutan yang mengeksekusi baris kode.
- Integrasi Jetpack: Banyak library Jetpack, seperti Compose dan ViewModel, menyertakan ekstensi yang memberikan dukungan penuh coroutine. Beberapa library juga menyediakan cakupan coroutine sendiri yang dapat Anda gunakan untuk membuat konkurensi terstruktur.
- Konkurensi terstruktur: Coroutine membuat kode serentak menjadi aman dan mudah diterapkan, menghilangkan kode boilerplate yang tidak perlu, dan memastikan coroutine yang diluncurkan oleh aplikasi tidak hilang atau menyia-nyiakan resource.
Ringkasan
- Coroutine memungkinkan Anda menulis kode berdurasi panjang dan berjalan serentak tanpa mempelajari gaya pemrograman baru. Eksekusi coroutine memiliki desain yang berurutan.
- Kata kunci
suspend
digunakan untuk menandai fungsi, atau jenis fungsi, guna menunjukkan ketersediaannya untuk mengeksekusi, menjeda, dan melanjutkan kumpulan petunjuk kode. - Fungsi
suspend
hanya dapat dipanggil dari fungsi penangguhan lainnya. - Anda dapat memulai coroutine baru menggunakan fungsi builder
launch
atauasync
. - Konteks coroutine, builder coroutine, Tugas, cakupan coroutine, dan Dispatcher adalah komponen utama untuk menerapkan coroutine.
- Coroutine menggunakan dispatcher untuk menentukan thread yang akan digunakan untuk eksekusi.
- Tugas memainkan peran penting untuk memastikan konkurensi terstruktur dengan mengelola siklus proses coroutine dan mempertahankan hubungan induk-turunan.
CoroutineContext
menentukan perilaku coroutine menggunakan Tugas dan dispatcher coroutine.CoroutineScope
mengontrol masa aktif coroutine melalui Tugasnya dan menerapkan pembatalan serta aturan lainnya untuk turunannya dan turunan berikutnya secara berulang.- Peluncuran, penyelesaian, pembatalan, dan kegagalan merupakan empat operasi umum dalam eksekusi coroutine.
- Coroutine mengikuti prinsip konkurensi terstruktur.