Kotlin Coroutines Rehberi: Android için 2026 Kılavuzu

Android geliştirmede Kotlin coroutines konusunda uzmanlaşın: suspend fonksiyonlar, scope yapıları, dispatcher'lar ve ileri düzey kalıplar.

Android için Kotlin Coroutines rehberi

Kotlin coroutines, Android platformunda asenkron programlamanın temelini yeniden tanımladı. Callback karmaşası ve kullanımdan kaldırılan AsyncTask artık geride kaldı: coroutines sayesinde geliştiriciler, performanslı ve bakımı kolay olan asenkron kodu senkron bir yapıda yazabiliyor.

Neden coroutines?

Coroutines hafiftir (tek bir iş parçacığında binlercesi çalışabilir), doğal olarak iptal edilebilir ve Jetpack ile modern Android ekosistemiyle sorunsuz entegre olur.

Temelleri Anlamak

Koda geçmeden önce, coroutines'i bu kadar güçlü kılan yapıyı anlamak gerekir.

Coroutine Nedir?

Coroutine, askıya alınabilir bir hesaplama örneğidir. İş parçacıklarından farklı olarak coroutines bloklamaz: yürütmeyi askıya alır ve iş parçacığını diğer görevler için serbest bırakır.

BasicCoroutine.ktkotlin
import kotlinx.coroutines.*

fun main() = runBlocking {
    launch {
        delay(1000L) // Suspend without blocking
        println("World!")
    }
    println("Hello,")
}
// Output: Hello, World!

Suspend Fonksiyonlar: Coroutines'in Kalbi

suspend anahtar kelimesi, bir fonksiyonun iş parçacığını bloklamadan coroutine yürütmesini askıya alabileceğini belirtir.

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)
}

Altın kural: Bir suspend fonksiyon yalnızca başka bir suspend fonksiyondan veya bir coroutine kapsamından çağrılabilir.

Coroutine Scope Yapıları

Scope, coroutines'in yaşam döngüsünü belirler. Bellek sızıntılarını önlemek için kritik öneme sahiptir.

viewModelScope: ViewModel'ler için Kapsam

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: Activity ve Fragment'lar için

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)
                    }
                }
            }
        }
    }
}
Sızıntılara dikkat

Bir Android uygulamasında asla GlobalScope kullanılmamalıdır. GlobalScope ile başlatılan coroutines'ler herhangi bir yaşam döngüsüne bağlı değildir ve bellek sızıntılarına neden olabilir.

Dispatchers: Yürütme Kontrolü

Dispatcher'lar, coroutine'in hangi iş parçacığında çalışacağını belirler.

4 Temel Dispatcher

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: Dispatcher Değiştirme

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)
}

async/await ile Paralel Yürütme

Görevleri paralel olarak yürütmek ve sonuçları birleştirmek için async kullanılır.

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()
        )
    }
}
Performans

async ile 3 çağrının tümü paralel olarak yürütülür. Her çağrı 1 saniye sürerse, toplam süre sıralı yürütmedeki 3 saniye yerine yaklaşık 1 saniye olur.

Hata Yönetimi

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)
        }
    }
}

Result Wrapper Kalıbı

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)
    }
}

Android mülakatlarında başarılı olmaya hazır mısın?

İnteraktif simülatörler, flashcards ve teknik testlerle pratik yap.

İptal: Kaynakları Doğru Temizleme

Coroutines, iş birlikçi iptal mekanizmasını destekler. Kaynak sızıntılarından kaçınmak için bu durum oldukça önemlidir.

Scope ile Otomatik İptal

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
        }
    }
}

İptal Kontrolü

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()
}

CancellationException asla yutulmamalıdır: Exception yakalanırken CancellationException yeniden fırlatılmalıdır, böylece iptal mekanizması doğru şekilde yayılır.

Flow: Reaktif Programlama

Flow, RxJava Observable yapısının coroutines karşılığıdır ancak daha sade ve entegre bir çözüm sunar.

Flow Oluşturma ve Toplama

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 ve SharedFlow Karşılaştırması

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()
}

Temel Flow Operatörleri

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()
    )

İleri Düzey Kalıplar

Üstel Geri Çekilme ile Yeniden Deneme

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)
}

Zaman Aşımı

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

Sonuc

Kotlin coroutines, modern Android geliştirmenin vazgeçilmez bir parçası haline geldi. Jetpack ekosistemiyle tam uyumlu olan coroutines, asenkron programlamaya zarif ve yüksek performanslı bir yaklaşım sunmaktadır.

Kontrol Listesi

  • Sızıntılardan kaçınmak için viewModelScope ve lifecycleScope kullanılmalıdır
  • Doğru Dispatcher seçilmelidir (Main, IO, Default)
  • Hatalar try/catch veya Result wrapper ile ele alınmalıdır
  • İş birlikçi iptal mekanizması uygulanmalıdır
  • Reaktif veri akışları için Flow kullanılmalıdır
  • UI durumu için StateFlow, olaylar için SharedFlow tercih edilmelidir

Pratik yapmaya başla!

Mülakat simülatörleri ve teknik testlerle bilgini test et.

Coroutines konusunda uzmanlaşmak, Android projelerinde ve teknik mülakatlarda önemli bir avantaj sağlayacaktır. Düzenli pratik yapmak ve ileri düzey kalıpları keşfetmek, bu alandaki yetkinliği daha da artıracaktır.

Etiketler

#kotlin
#coroutines
#android
#async
#concurrency

Paylaş

İlgili makaleler