Coroutine adalah pola desain serentak yang dapat Anda gunakan di Android untuk menyederhanakan kode yang dieksekusi secara asinkron. Coroutine ditambahkan ke Kotlin di versi 1.3 dan didasarkan pada konsep dari bahasa lain.
Di Android, coroutine berguna untuk mengelola tugas yang berjalan lama dan mungkin memblokir thread utama serta menyebabkan aplikasi tidak responsif. Lebih dari 50% developer profesional yang menggunakan coroutine telah melaporkan peningkatan produktivitas. Topik ini menjelaskan bagaimana Anda dapat menggunakan coroutine Kotlin untuk mengatasi masalah ini, sehingga Anda dapat menulis kode aplikasi yang lebih rapi dan ringkas.
Fitur
Coroutine adalah solusi yang direkomendasikan untuk pemrograman asinkron di Android. Fitur penting meliputi:
- Ringan: Anda dapat menjalankan banyak coroutine pada satu thread karena adanya dukungan untuk penangguhan, yang tidak memblokir thread tempat coroutine berjalan. Penangguhan menghemat memori melalui pemblokiran sekaligus mendukung banyak operasi serentak.
- Lebih sedikit kebocoran memori: Menggunakan konkurensi terstruktur untuk menjalankan operasi dalam suatu ruang lingkup.
- Dukungan pembatalan bawaan: Pembatalan otomatis disebarkan melalui hierarki coroutine yang berjalan.
- Integrasi Jetpack: Banyak library Jetpack dilengkapi ekstensi yang menyediakan dukungan penuh coroutine. Beberapa library juga menyediakan cakupan coroutine sendiri yang dapat Anda gunakan untuk membuat struktur serentak.
Ringkasan contoh
Berdasarkan Panduan arsitektur aplikasi, contoh dalam topik ini membuat permintaan jaringan dan menampilkan hasilnya ke thread utama, tempat aplikasi kemudian dapat menampilkan hasilnya kepada pengguna.
Secara khusus, komponen Arsitektur ViewModel
memanggil lapisan repositori pada thread utama untuk
memicu permintaan jaringan. Panduan ini melakukan iterasi melalui berbagai solusi
yang menggunakan coroutine untuk membuat thread utama tidak diblokir.
ViewModel menyertakan serangkaian ekstensi KTX yang berfungsi langsung dengan
coroutine. Ekstensi tersebut adalah
library lifecycle-viewmodel-ktx dan digunakan
dalam panduan ini.
Info dependensi
Untuk menggunakan coroutine dalam project Android, tambahkan dependensi berikut
ke file build.gradle aplikasi Anda:
Groovy
dependencies { implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9' }
Kotlin
dependencies { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9") }
Menjalankan eksekusi pada thread latar belakang
Pembuatan permintaan jaringan pada thread utama akan menyebabkannya menunggu, atau memblokir,
hingga menerima respons. Karena thread diblokir, OS tidak dapat memanggil onDraw(), yang menyebabkan aplikasi berhenti berfungsi dan berpotensi
memunculkan dialog Aplikasi Tidak Merespons (ANR). Untuk pengalaman pengguna yang lebih baik,
jalankan operasi ini di thread latar belakang.
Pertama, mari kita lihat class Repository dan lihat caranya
membuat permintaan jaringan:
sealed class Result<out R> {
data class Success<out T>(val data: T) : Result<T>()
data class Error(val exception: Exception) : Result<Nothing>()
}
class LoginRepository(private val responseParser: LoginResponseParser) {
private const val loginUrl = "https://example.com/login"
// Function that makes the network request, blocking the current thread
fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
val url = URL(loginUrl)
(url.openConnection() as? HttpURLConnection)?.run {
requestMethod = "POST"
setRequestProperty("Content-Type", "application/json; utf-8")
setRequestProperty("Accept", "application/json")
doOutput = true
outputStream.write(jsonBody.toByteArray())
return Result.Success(responseParser.parse(inputStream))
}
return Result.Error(Exception("Cannot open HttpURLConnection"))
}
}
makeLoginRequest bersifat sinkron dan memblokir thread pemanggil. Untuk mencontohkan
respons permintaan jaringan, kami memiliki class Result sendiri.
ViewModel memicu permintaan jaringan saat pengguna mengklik, misalnya, sebuah tombol:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
Dengan kode sebelumnya, LoginViewModel memblokir UI thread saat
membuat permintaan jaringan. Solusi yang paling sederhana untuk mengeluarkan eksekusi
dari thread utama adalah membuat coroutine baru dan menjalankan permintaan
jaringan pada thread I/O:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine to move the execution off the UI thread
viewModelScope.launch(Dispatchers.IO) {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
loginRepository.makeLoginRequest(jsonBody)
}
}
}
Mari kita pelajari kode coroutine dalam fungsi login:
viewModelScopeadalahCoroutineScopeyang telah ditetapkan dan disertakan dengan ekstensi KTXViewModel. Perlu diingat bahwa semua coroutine harus dijalankan dalam cakupan.CoroutineScopemengelola satu atau beberapa coroutine terkait.launchadalah fungsi yang membuat coroutine dan mengirimkan eksekusi isi fungsinya ke dispatcher yang sesuai.Dispatchers.IOmenunjukkan bahwa coroutine ini harus dieksekusi pada thread yang telah disiapkan untuk operasi I/O.
Fungsi login dieksekusi sebagai berikut:
- Aplikasi memanggil fungsi
logindari lapisanViewpada thread utama. launchmembuat coroutine baru, dan permintaan jaringan dibuat secara terpisah pada thread yang telah disiapkan untuk operasi I/O.- Saat coroutine berjalan, fungsi
loginakan melanjutkan eksekusi dan kembali, mungkin sebelum permintaan jaringan selesai. Perlu diketahui bahwa respons jaringan diabaikan untuk saat ini agar lebih praktis.
Karena dimulai dengan viewModelScope, coroutine ini dieksekusi dalam
cakupan ViewModel. Jika ViewModel dihapus karena
pengguna keluar dari layar, viewModelScope akan dibatalkan
secara otomatis, dan semua coroutine yang berjalan juga akan dibatalkan.
Satu masalah dengan contoh sebelumnya adalah bahwa apa pun yang memanggil
makeLoginRequest harus ingat untuk memindahkan eksekusi dari
thread utama secara eksplisit. Mari kita lihat cara mengubah Repository untuk menyelesaikan
masalah ini.
Menggunakan coroutine untuk main-safety
Kami menganggap sebuah fungsi bersifat main-safe jika tidak memblokir update UI pada
thread utama. Fungsi makeLoginRequest tidak main-safe, karena memanggil
makeLoginRequest dari thread utama akan memblokir UI. Gunakan
fungsi withContext() dari library coroutine untuk memindahkan eksekusi
coroutine ke thread lain:
class LoginRepository(...) {
...
suspend fun makeLoginRequest(
jsonBody: String
): Result<LoginResponse> {
// Move the execution of the coroutine to the I/O dispatcher
return withContext(Dispatchers.IO) {
// Blocking network request code
}
}
}
withContext(Dispatchers.IO) memindahkan eksekusi coroutine ke
thread I/O sehingga fungsi panggilan kami menjadi main-safe dan memungkinkan UI
diupdate sesuai kebutuhan.
makeLoginRequest juga ditandai dengan kata kunci suspend. Kata kunci ini
adalah cara Kotlin menerapkan fungsi yang akan dipanggil dari dalam coroutine.
Dalam contoh berikut, coroutine dibuat di LoginViewModel.
Saat makeLoginRequest mengeluarkan eksekusi dari thread utama, coroutine
dalam fungsi login kini dapat dieksekusi pada thread utama:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
// Create a new coroutine on the UI thread
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
// Make the network call and suspend execution until it finishes
val result = loginRepository.makeLoginRequest(jsonBody)
// Display result of the network request to the user
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
Perlu diingat bahwa coroutine masih diperlukan di sini, karena makeLoginRequest adalah
fungsi suspend, dan semua fungsi suspend harus dieksekusi dalam
coroutine.
Dalam beberapa hal, kode ini berbeda dengan contoh login sebelumnya:
launchtidak menggunakan parameterDispatchers.IO. JikaDispatchertidak diteruskan kelaunch, setiap coroutine yang diluncurkan dariviewModelScopeakan dijalankan di thread utama.- Kini hasil permintaan jaringan ditangani untuk menampilkan UI yang berhasil atau gagal.
Kini fungsi login berjalan seperti berikut:
- Aplikasi memanggil fungsi
login()dari lapisanViewpada thread utama. launchmembuat coroutine baru pada thread utama, dan coroutine akan memulai eksekusi.- Dalam coroutine, panggilan ke
loginRepository.makeLoginRequest()kini menangguhkan eksekusi coroutine lebih lanjut hingga blokwithContextdalammakeLoginRequest()selesai berjalan. - Setelah blok
withContextselesai, coroutine padalogin()akan melanjutkan eksekusi pada thread utama dengan hasil permintaan jaringan.
Menangani pengecualian
Untuk menangani pengecualian yang dapat ditampilkan oleh lapisan Repository, gunakan
dukungan pengecualian bawaan Kotlin.
Dalam contoh berikut, kami menggunakan blok try-catch:
class LoginViewModel(
private val loginRepository: LoginRepository
): ViewModel() {
fun login(username: String, token: String) {
viewModelScope.launch {
val jsonBody = "{ username: \"$username\", token: \"$token\"}"
val result = try {
loginRepository.makeLoginRequest(jsonBody)
} catch(e: Exception) {
Result.Error(Exception("Network request failed"))
}
when (result) {
is Result.Success<LoginResponse> -> // Happy path
else -> // Show error in UI
}
}
}
}
Dalam contoh ini, setiap pengecualian tidak terduga yang ditampilkan oleh panggilan makeLoginRequest()
ditangani sebagai error dalam UI.
Referensi coroutine lainnya
Untuk penjelasan coroutine di Android lebih lanjut, lihat Meningkatkan performa aplikasi dengan coroutine Kotlin.
Untuk referensi coroutine lainnya, lihat link berikut:
- Ringkasan coroutine (JetBrains)
- Panduan coroutine (JetBrains)
- Referensi lainnya untuk coroutine dan flow Kotlin