Pengantar Coroutine di Android Studio

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.

2ee492f277625f0a.png

Anda dapat menggunakan tombol ini kapan pun untuk menjeda atau melanjutkan lomba.

50e992f4cf6836b7.png

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.

79cf74d82eacae6f.png

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:

  1. Buka class RaceParticipant yang merupakan bagian dari kode awal.
  2. Di dalam class RaceParticipant, tentukan fungsi suspend baru yang bernama run().
class RaceParticipant(
    ...
) {
    var currentProgress by mutableStateOf(initialProgress)
        private set

    suspend fun run() {
        
    }
    ...
}
  1. Untuk menyimulasikan progres perlombaan, tambahkan loop while yang berjalan hingga currentProgress mencapai nilai maxProgress yang disetel ke 100.
class RaceParticipant(
    ...
    val maxProgress: Int = 100,
    ...
) {
    var currentProgress by mutableStateOf(initialProgress)
        private set

    suspend fun run() {
        while (currentProgress < maxProgress) {
            
        }
    }
    ...
}
  1. Nilai currentProgress disetel ke initialProgress, yaitu 0. Untuk menyimulasikan progres peserta, tambahkan nilai currentProgress dengan nilai properti progressIncrement di dalam loop while. Perhatikan bahwa nilai default progressIncrement adalah 1.
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
        }
    }
}
  1. Untuk menyimulasikan berbagai interval progres dalam perlombaan, gunakan fungsi penangguhan delay(). Teruskan nilai properti progressDelayMillis 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: 11b5df57dcb744dc.png

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:

a3c314fb082a9626.png

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:

  1. Buka file RaceTrackerApp.kt yang berada dalam paket com.example.racetracker.ui.
  2. Buka composable RaceTrackerApp() dan tambahkan panggilan ke composable LaunchedEffect() di baris setelah definisi raceInProgress.
@Composable
fun RaceTrackerApp() {
    ...
    var raceInProgress by remember { mutableStateOf(false) }

    LaunchedEffect {
    
    }
    RaceTrackerScreen(...)
}
  1. Untuk memastikan bahwa jika instance playerOne atau playerTwo diganti dengan instance lain, maka LaunchedEffect() harus membatalkan dan meluncurkan kembali coroutine dasar, kemudian menambahkan objek playerOne dan playerTwo sebagai key ke LaunchedEffect. Serupa dengan cara merekomposisi composable Text() saat nilai teksnya berubah, jika salah satu argumen utama dari LaunchedEffect() berubah, coroutine dasar akan dibatalkan dan diluncurkan kembali.
LaunchedEffect(playerOne, playerTwo) {
}
  1. Tambahkan panggilan ke fungsi playerOne.run() dan playerTwo.run().
@Composable
fun RaceTrackerApp() {
    ...
    var raceInProgress by remember { mutableStateOf(false) }

    LaunchedEffect(playerOne, playerTwo) {
        playerOne.run()
        playerTwo.run()
    }
    RaceTrackerScreen(...)
}
  1. Gabungkan blok LaunchedEffect() dengan kondisi if. Nilai awal untuk status ini adalah false. Nilai untuk status raceInProgress diperbarui menjadi true saat pengguna mengklik tombol Start dan LaunchedEffect() dieksekusi.
if (raceInProgress) {
    LaunchedEffect(playerOne, playerTwo) {
        playerOne.run()
        playerTwo.run() 
    }
}
  1. Perbarui tanda raceInProgress ke false untuk menyelesaikan perlombaan. Nilai ini akan disetel ke false saat pengguna juga mengklik Pause. Jika nilai ini disetel ke false, LaunchedEffect() akan memastikan bahwa semua coroutine yang diluncurkan dibatalkan.
LaunchedEffect(playerOne, playerTwo) {
    playerOne.run()
    playerTwo.run()
    raceInProgress = false 
}
  1. Jalankan aplikasi, lalu klik Start. Anda akan melihat pemain satu menyelesaikan lomba sebelum pemain dua mulai berlari, seperti yang ditunjukkan dalam video berikut:

fa0630395ee18f21.gif

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

  1. 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 ke playerOne.run() dengan builder launch.
LaunchedEffect(playerOne, playerTwo) {
    launch { playerOne.run() }
    playerTwo.run()
    raceInProgress = false 
}
  1. Demikian pula, gabungkan panggilan ke fungsi playerTwo.run() dengan builder launch. 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 
}
  1. Jalankan aplikasi, lalu klik Start. Saat Anda menunggu lomba dimulai, teks tombol tiba-tiba langsung berubah kembali menjadi Start.

c46c2aa7c580b27b.png

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 mengeksekusi playerOne.run() dan segera kembali untuk mengeksekusi baris berikutnya dalam blok kode.
  • Alur eksekusi yang sama terjadi dengan fungsi builder launch kedua yang menjalankan fungsi playerTwo.run().
  • Segera setelah builder launch kedua ditampilkan, tanda raceInProgress 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().

  1. Untuk memastikan fungsi run() dari playerOne dan playerTwo menyelesaikan eksekusi sebelum memperbarui tanda raceInProgress, gabungkan kedua builder peluncuran dengan blok coroutineScope.
LaunchedEffect(playerOne, playerTwo) {
    coroutineScope {
        launch { playerOne.run() }
        launch { playerTwo.run() }
    }
    raceInProgress = false
}
  1. Jalankan aplikasi di emulator/perangkat Android. Anda akan melihat layar berikut:

598ee57f8ba58a52.png

  1. 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.

c1035eecc5513c58.gif

Alur eksekusi ditampilkan dalam diagram berikut:

cf724160fd66ff21.png

  • Saat blok LaunchedEffect() dieksekusi, kontrol akan ditransfer ke blok coroutineScope{..}.
  • 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:

  1. 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.
    }
}
  1. Jalankan aplikasi lalu klik tombol Start.
  2. Setelah beberapa penambahan progres, klik tombol Reset.
  3. 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:

  1. Buka file build.gradle.kts modul aplikasi yang terletak di direktori app di panel Project.

e7c9e573c41199c6.png

  1. Di dalam file, scroll ke bawah hingga Anda menemukan blok dependencies{}.
  2. Tambahkan dependensi menggunakan konfigurasi testImplementation ke library kotlinx-coroutines-test.
plugins {
    ...
}

android {
    ...
}

dependencies {
    ...
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4")
}
  1. 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:

1c20fc10750ca60c.png

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:

  1. Buka file RaceParticipantTest.kt yang berada di bagian set sumber pengujian.
  2. Untuk menentukan pengujian, setelah definisi raceParticipant, buat fungsi raceParticipant_RaceStarted_ProgressUpdated() dan anotasikan dengan anotasi @Test. Gunakan sintaksis ekspresi untuk menampilkan blok runTest() sebagai hasil pengujian karena blok pengujian harus ditempatkan di builder runTest.
class RaceParticipantTest {
    private val raceParticipant = RaceParticipant(
        ...
    )

    @Test
    fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
    }
}
  1. Tambahkan variabel expectedProgress hanya baca dan setel ke 1.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
    val expectedProgress = 1
}
  1. Untuk menyimulasikan dimulainya perlombaan, gunakan builder launch untuk meluncurkan coroutine baru dan memanggil fungsi raceParticipant.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.

  1. Gunakan fungsi bantuan advanceTimeBy() untuk memajukan waktu dengan nilai raceParticipant.progressDelayMillis. Fungsi advanceTimeBy() membantu mengurangi waktu eksekusi uji.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
    val expectedProgress = 1
    launch { raceParticipant.run() }
    advanceTimeBy(raceParticipant.progressDelayMillis)
}
  1. Anda harus memanggil fungsi runCurrent(). karena advanceTimeBy() 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()
}
  1. Untuk memastikan progres diperbarui, tambahkan panggilan ke fungsi assertEquals() untuk memeriksa apakah nilai properti raceParticipant.currentProgress cocok dengan nilai variabel expectedProgress.
@Test
fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
    val expectedProgress = 1
    launch { raceParticipant.run() }
    advanceTimeBy(raceParticipant.progressDelayMillis)
    runCurrent()
    assertEquals(expectedProgress, raceParticipant.currentProgress)
}
  1. 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:

  1. Setelah fungsi pengujian raceParticipant_RaceStarted_ProgressUpdated(), buat fungsi raceParticipant_RaceFinished_ProgressUpdated() dan anotasikan dengan anotasi @Test. Fungsi ini akan menampilkan hasil pengujian dari blok runTest{}.
class RaceParticipantTest {
    ...

    @Test
    fun raceParticipant_RaceStarted_ProgressUpdated() = runTest {
        ...
    }

    @Test
    fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
    }
}
  1. Gunakan builder launch untuk meluncurkan coroutine baru dan menambahkan panggilan ke fungsi raceParticipant.run() di dalamnya.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
    launch { raceParticipant.run() }
}
  1. Untuk menyimulasikan hasil akhir perlombaan, gunakan fungsi advanceTimeBy() untuk memajukan waktu dispatcher dengan raceParticipant.maxProgress * raceParticipant.progressDelayMillis:
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
    launch { raceParticipant.run() }
    advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
}
  1. 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()
}
  1. Untuk memastikan progres diperbarui dengan benar, tambahkan panggilan ke fungsi assertEquals() untuk memastikan nilai properti raceParticipant.currentProgress sama dengan 100.
@Test
fun raceParticipant_RaceFinished_ProgressUpdated() = runTest {
    launch { raceParticipant.run() }
    advanceTimeBy(raceParticipant.maxProgress * raceParticipant.progressDelayMillis)
    runCurrent()
    assertEquals(100, raceParticipant.currentProgress)
}
  1. 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 atau async.
  • 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.

Mempelajari lebih lanjut