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
:
viewModelScope
adalahCoroutineScope
yang telah ditetapkan dan disertakan dengan ekstensi KTXViewModel
. Perlu diingat bahwa semua coroutine harus dijalankan dalam cakupan.CoroutineScope
mengelola satu atau beberapa coroutine terkait.launch
adalah fungsi yang membuat coroutine dan mengirimkan eksekusi isi fungsinya ke dispatcher yang sesuai.Dispatchers.IO
menunjukkan bahwa coroutine ini harus dieksekusi pada thread yang telah disiapkan untuk operasi I/O.
Fungsi login
dieksekusi sebagai berikut:
- Aplikasi memanggil fungsi
login
dari lapisanView
pada thread utama. launch
membuat coroutine baru, dan permintaan jaringan dibuat secara terpisah pada thread yang telah disiapkan untuk operasi I/O.- Saat coroutine berjalan, fungsi
login
akan 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:
launch
tidak menggunakan parameterDispatchers.IO
. JikaDispatcher
tidak diteruskan kelaunch
, setiap coroutine yang diluncurkan dariviewModelScope
akan 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 lapisanView
pada thread utama. launch
membuat coroutine baru pada thread utama, dan coroutine akan memulai eksekusi.- Dalam coroutine, panggilan ke
loginRepository.makeLoginRequest()
kini menangguhkan eksekusi coroutine lebih lanjut hingga blokwithContext
dalammakeLoginRequest()
selesai berjalan. - Setelah blok
withContext
selesai, 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