Menguasai Kotlin Coroutines: Panduan Lengkap 2026

Pelajari Kotlin coroutines untuk pengembangan Android: suspend function, scope, dispatcher, dan pola-pola tingkat lanjut.

Panduan lengkap Kotlin Coroutines untuk Android

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.

Mengapa coroutines?

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.

BasicCoroutine.ktkotlin
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.

SuspendFunction.ktkotlin
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

UserViewModel.ktkotlin
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

UserFragment.ktkotlin
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)
                    }
                }
            }
        }
    }
}
Waspadai kebocoran

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

Dispatchers.ktkotlin
// 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

ImageProcessor.ktkotlin
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.

ParallelExecution.ktkotlin
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()
        )
    }
}
Performa

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

ErrorHandling.ktkotlin
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

ExceptionHandler.ktkotlin
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

ResultPattern.ktkotlin
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

SearchViewModel.ktkotlin
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

CancellationCheck.ktkotlin
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

FlowBasics.ktkotlin
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

StateFlowVsSharedFlow.ktkotlin
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

FlowOperators.ktkotlin
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

RetryPattern.ktkotlin
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

TimeoutPattern.ktkotlin
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()
} ?: defaultValue

Kesimpulan

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 viewModelScope dan lifecycleScope untuk 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

#kotlin
#coroutines
#android
#async
#concurrency

Bagikan

Artikel terkait