Menguasai Kotlin Coroutines: Panduan Lengkap 2026
Pelajari Kotlin coroutines untuk pengembangan Android: suspend function, scope, dispatcher, dan pola-pola tingkat lanjut.

Kotlin coroutines telah mengubah secara mendasar cara pemrograman asinkron dilakukan di platform Android. Masa-masa callback hell dan AsyncTask yang sudah usang telah berlalu: dengan coroutines, pengembang dapat menulis kode asinkron yang tampak seperti kode sinkron, namun tetap berkinerja tinggi dan mudah dipelihara.
Coroutines bersifat ringan (ribuan dapat berjalan dalam satu thread), secara bawaan mendukung pembatalan, dan terintegrasi dengan mulus bersama Jetpack serta ekosistem Android modern.
Memahami Dasar-Dasar
Sebelum masuk ke kode, penting untuk memahami apa yang membuat coroutines begitu powerful.
Apa Itu Coroutine?
Coroutine adalah sebuah instance dari komputasi yang dapat ditangguhkan. Berbeda dengan thread, coroutine tidak memblokir: ia menangguhkan eksekusi dan membebaskan thread untuk tugas-tugas lain.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch {
delay(1000L) // Suspend without blocking
println("World!")
}
println("Hello,")
}
// Output: Hello, World!Suspend Function: Inti dari Coroutines
Kata kunci suspend menunjukkan bahwa sebuah fungsi dapat menangguhkan eksekusi coroutine tanpa memblokir thread.
suspend fun fetchUserData(userId: String): User {
return withContext(Dispatchers.IO) {
// Network call - runs on an IO thread
apiService.getUser(userId)
}
}
suspend fun fetchUserWithPosts(userId: String): UserWithPosts {
// Sequential execution
val user = fetchUserData(userId)
val posts = fetchUserPosts(userId)
return UserWithPosts(user, posts)
}Aturan utama: Sebuah suspend function hanya dapat dipanggil dari suspend function lain atau dari dalam sebuah coroutine.
Coroutine Scope
Scope menentukan siklus hidup coroutine. Hal ini sangat penting untuk mencegah kebocoran memori.
viewModelScope: Scope untuk ViewModel
class UserViewModel(
private val userRepository: UserRepository
) : ViewModel() {
private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
fun loadUser(userId: String) {
viewModelScope.launch {
_uiState.value = UserUiState.Loading
try {
val user = userRepository.getUser(userId)
_uiState.value = UserUiState.Success(user)
} catch (e: Exception) {
_uiState.value = UserUiState.Error(e.message)
}
}
}
}
sealed class UserUiState {
object Loading : UserUiState()
data class Success(val user: User) : UserUiState()
data class Error(val message: String?) : UserUiState()
}lifecycleScope: Untuk Activity dan Fragment
class UserFragment : Fragment() {
private val viewModel: UserViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { state ->
when (state) {
is UserUiState.Loading -> showLoading()
is UserUiState.Success -> showUser(state.user)
is UserUiState.Error -> showError(state.message)
}
}
}
}
}
}Jangan pernah menggunakan GlobalScope dalam aplikasi Android. Coroutine yang diluncurkan dengan GlobalScope tidak terikat pada siklus hidup apa pun dan dapat menyebabkan kebocoran memori.
Dispatcher: Mengontrol Eksekusi
Dispatcher menentukan thread mana yang akan digunakan oleh coroutine untuk berjalan.
4 Dispatcher Utama
// Main: main thread (UI)
viewModelScope.launch(Dispatchers.Main) {
textView.text = "UI update"
}
// IO: I/O operations (network, database)
viewModelScope.launch(Dispatchers.IO) {
val data = repository.fetchFromNetwork()
}
// Default: CPU-intensive computations
viewModelScope.launch(Dispatchers.Default) {
val result = heavyComputation(data)
}
// Unconfined: inherits caller context (rare use)withContext: Berpindah Dispatcher
class ImageProcessor {
suspend fun processImage(bitmap: Bitmap): Bitmap {
return withContext(Dispatchers.Default) {
// CPU-intensive processing on Default
applyFilters(bitmap)
}
}
suspend fun saveToGallery(bitmap: Bitmap) {
withContext(Dispatchers.IO) {
// Disk write on IO
saveToFile(bitmap)
}
}
}
// Usage in ViewModel
viewModelScope.launch {
val processed = imageProcessor.processImage(originalBitmap)
imageProcessor.saveToGallery(processed)
// Automatic return to Main for UI update
_uiState.value = UiState.Success(processed)
}Eksekusi Paralel dengan async/await
Untuk menjalankan tugas secara paralel dan menggabungkan hasilnya, gunakan async.
suspend fun loadDashboard(): Dashboard {
return coroutineScope {
// Parallel launch
val userDeferred = async { userRepository.getUser() }
val statsDeferred = async { statsRepository.getStats() }
val notificationsDeferred = async { notificationRepository.getNotifications() }
// Await results
Dashboard(
user = userDeferred.await(),
stats = statsDeferred.await(),
notifications = notificationsDeferred.await()
)
}
}Dengan async, ketiga pemanggilan dijalankan secara paralel. Jika setiap pemanggilan membutuhkan waktu 1 detik, total waktu yang diperlukan adalah sekitar 1 detik, bukan 3 detik jika dijalankan secara berurutan.
Penanganan Error
Klasik try/catch
viewModelScope.launch {
try {
val user = userRepository.getUser(userId)
_uiState.value = UiState.Success(user)
} catch (e: HttpException) {
_uiState.value = UiState.Error("Server error: ${e.code()}")
} catch (e: IOException) {
_uiState.value = UiState.Error("Network error")
} catch (e: Exception) {
_uiState.value = UiState.Error("Unexpected error")
}
}CoroutineExceptionHandler
class UserViewModel : ViewModel() {
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
_uiState.value = UiState.Error(throwable.message)
Timber.e(throwable, "Error in coroutine")
}
fun loadUser(userId: String) {
viewModelScope.launch(exceptionHandler) {
val user = userRepository.getUser(userId)
_uiState.value = UiState.Success(user)
}
}
}Pola Result Wrapper
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
}
suspend fun <T> safeApiCall(apiCall: suspend () -> T): Result<T> {
return try {
Result.Success(apiCall())
} catch (e: Exception) {
Result.Error(e)
}
}
// Usage
class UserRepository(private val api: UserApi) {
suspend fun getUser(id: String): Result<User> = safeApiCall {
api.getUser(id)
}
}
// In ViewModel
viewModelScope.launch {
when (val result = userRepository.getUser(userId)) {
is Result.Success -> _uiState.value = UiState.Success(result.data)
is Result.Error -> _uiState.value = UiState.Error(result.exception.message)
}
}Siap menguasai wawancara Android Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Pembatalan: Membersihkan Sumber Daya dengan Benar
Coroutine mendukung pembatalan kooperatif. Hal ini sangat penting untuk menghindari kebocoran sumber daya.
Pembatalan Otomatis dengan Scope
class SearchViewModel : ViewModel() {
private var searchJob: Job? = null
fun search(query: String) {
// Cancel previous search
searchJob?.cancel()
searchJob = viewModelScope.launch {
delay(300) // Debounce
val results = searchRepository.search(query)
_searchResults.value = results
}
}
}Memeriksa Pembatalan
suspend fun processLargeList(items: List<Item>) {
items.forEach { item ->
// Check if coroutine is cancelled
ensureActive()
processItem(item)
}
}
suspend fun downloadFiles(urls: List<String>) = coroutineScope {
urls.map { url ->
async {
try {
downloadFile(url)
} catch (e: CancellationException) {
cleanupPartialDownload(url)
throw e // Re-throw to propagate cancellation
}
}
}.awaitAll()
}Jangan pernah menelan CancellationException: jika menangkap Exception, pastikan untuk melempar kembali CancellationException agar pembatalan dapat menyebar dengan benar.
Flow: Pemrograman Reaktif
Flow adalah padanan RxJava Observable dalam dunia coroutines, tetapi lebih sederhana dan terintegrasi secara bawaan.
Membuat dan Mengumpulkan Flow
fun getUsers(): Flow<List<User>> = flow {
while (true) {
val users = userApi.getUsers()
emit(users)
delay(5000) // Poll every 5 seconds
}
}
// Flow from Room
@Dao
interface UserDao {
@Query("SELECT * FROM users")
fun getAllUsers(): Flow<List<User>>
}
// Collecting in ViewModel
viewModelScope.launch {
userDao.getAllUsers()
.catch { e -> _uiState.value = UiState.Error(e.message) }
.collect { users ->
_uiState.value = UiState.Success(users)
}
}StateFlow vs SharedFlow
class EventViewModel : ViewModel() {
// StateFlow: keeps last value, ideal for UI state
private val _uiState = MutableStateFlow(UiState.Initial)
val uiState: StateFlow<UiState> = _uiState.asStateFlow()
// SharedFlow: for one-shot events (navigation, snackbar)
private val _events = MutableSharedFlow<UiEvent>()
val events: SharedFlow<UiEvent> = _events.asSharedFlow()
fun onButtonClick() {
viewModelScope.launch {
_events.emit(UiEvent.NavigateToDetail)
}
}
}
sealed class UiEvent {
object NavigateToDetail : UiEvent()
data class ShowSnackbar(val message: String) : UiEvent()
}Operator Flow yang Penting
userRepository.getUsers()
.map { users -> users.filter { it.isActive } }
.distinctUntilChanged()
.debounce(300)
.flatMapLatest { users ->
fetchUserDetails(users)
}
.catch { e ->
emit(emptyList())
}
.onEach { users ->
analytics.logUserCount(users.size)
}
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
initialValue = emptyList()
)Pola Tingkat Lanjut
Retry dengan Exponential Backoff
suspend fun <T> retryWithBackoff(
times: Int = 3,
initialDelay: Long = 100,
maxDelay: Long = 1000,
factor: Double = 2.0,
block: suspend () -> T
): T {
var currentDelay = initialDelay
repeat(times - 1) { attempt ->
try {
return block()
} catch (e: Exception) {
Timber.w("Attempt ${attempt + 1} failed, retrying in ${currentDelay}ms")
}
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
return block()
}
// Usage
val user = retryWithBackoff {
userApi.getUser(userId)
}Timeout
suspend fun fetchWithTimeout() {
try {
val result = withTimeout(5000L) {
api.fetchData()
}
processResult(result)
} catch (e: TimeoutCancellationException) {
showError("Request took too long")
}
}
// Or with a default value
val result = withTimeoutOrNull(5000L) {
api.fetchData()
} ?: defaultValueKesimpulan
Kotlin coroutines telah menjadi bagian yang tidak terpisahkan dari pengembangan Android modern. Coroutines menawarkan pendekatan yang elegan dan berkinerja tinggi terhadap pemrograman asinkron, serta terintegrasi dengan sempurna dalam ekosistem Jetpack.
Daftar Periksa
- Gunakan
viewModelScopedanlifecycleScopeuntuk menghindari kebocoran memori - Pilih Dispatcher yang tepat (Main, IO, Default)
- Tangani error dengan try/catch atau Result wrapper
- Implementasikan pembatalan kooperatif
- Gunakan Flow untuk aliran data reaktif
- Utamakan StateFlow untuk state UI, SharedFlow untuk event
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Penguasaan coroutines akan memberikan keunggulan yang signifikan dalam proyek Android maupun wawancara teknis. Latihan secara berkala dan eksplorasi pola-pola tingkat lanjut akan semakin memperdalam keahlian di bidang ini.
Tag
Bagikan
Artikel terkait

Kotlin 2.3 untuk Android: Name-Based Destructuring, KMP dan Pertanyaan Wawancara 2026
Pertanyaan wawancara Kotlin 2.3 untuk developer Android di tahun 2026. Name-based destructuring, KMP, context parameters, Flow, dan coroutines dengan contoh kode.

Jetpack Compose: Animasi Lanjutan Langkah demi Langkah
Panduan lengkap animasi lanjutan di Compose: transisi, AnimatedVisibility, Animatable, gestur, dan performa untuk antarmuka Android yang halus.

20 Pertanyaan Wawancara Jetpack Compose Teratas di Tahun 2026
20 pertanyaan wawancara Jetpack Compose yang paling sering ditanyakan: recomposition, state management, navigation, performa, dan pola arsitektur dengan contoh kode lengkap.