Android'de Güvenilir HTTP İstekleri: Pratik Bir Rehber

Kerem Aydın·7 Temmuz 2026·⏱3 dk

Kısa cevap: Çoğu görev için Retrofit + OkHttp + Kotlin Coroutines kullanın; zaman aşımlarını ve interceptors'ları yapılandırın, yanıtları güvenli NetworkResult sarmalayıcısına alın, backoff ile sınırlı yeniden deneme uygulayın ve token'ları güvenli şekilde saklayın — bu, kötü bağlantı koşullarında bile öngörülebilir ve dayanıklı çalışma sağlar.

Özetle: API arayüzü için Retrofit, yapılandırma (zaman aşımları, önbellek, interceptors) için OkHttp, asenkron işlemler için Coroutines.

İçindekiler {{TOC_AUTOMATIC}}

Kütüphane Seçimi ve Temel İstemci Yapılandırması

  1. Neden Retrofit + OkHttp: Deklaratif API, uygun dönüştürücüler (Moshi/kotlinx.serialization), test edilebilirlik; OkHttp — zaman aşımları, önbellek ve interceptors üzerinde kontrol.
  2. Minimal OkHttp yapılandırması:
fun createOkHttpClient(tokenProvider: () -> String?): OkHttpClient =
  OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .retryOnConnectionFailure(true)
    .addInterceptor(AuthInterceptor(tokenProvider))
    .apply { if (BuildConfig.DEBUG) addInterceptor(HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }) }
    .build()
  1. Retrofit örneği:
fun createRetrofit(okHttp: OkHttpClient): Retrofit =
  Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .client(okHttp)
    .addConverterFactory(MoshiConverterFactory.create())
    .build()

Üretim derlemelerinde istek/yanıt gövdelerinin ayrıntılı günlüklenmesini bırakmayın — hassas veri sızıntısı riski.

Mimari: Repository'ler, Güvenli Çağrı ve Yeniden Deneme

Ağ katmanını ayrı bir modül/paket olarak düzenleyin: ApiService (Retrofit arayüzleri) → repository'ler → use-case'ler/ViewModel. Her zaman ham nesne yerine sonuç sarmalayıcısı döndürün.

Sonuç için sealed class örneği:

sealed class NetworkResult<out T> {
  data class Success<T>(val data: T): NetworkResult<T>()
  data class HttpError(val code: Int, val message: String?): NetworkResult<Nothing>()
  data class NetworkError(val throwable: Throwable): NetworkResult<Nothing>()
  object UnknownError: NetworkResult<Nothing>()
}

Güvenli çağrı:

suspend fun <T> safeApiCall(apiCall: suspend () -> Response<T>): NetworkResult<T> =
  try {
    val resp = apiCall()
    val body = resp.body()
    if (resp.isSuccessful && body != null) NetworkResult.Success(body)
    else NetworkResult.HttpError(resp.code(), resp.errorBody()?.string())
  } catch (e: IOException) { NetworkResult.NetworkError(e) }
    catch (e: Exception) { NetworkResult.UnknownError }

Exponential backoff ile yeniden deneme (sınırlı sayıda deneme):

suspend fun <T> retryWithBackoff(
  maxAttempts: Int = 3,
  initialDelayMs: Long = 500,
  factor: Double = 2.0,
  block: suspend () -> NetworkResult<T>
): NetworkResult<T> {
  var delayMs = initialDelayMs
  repeat(maxAttempts - 1) {
    when (val r = block()) {
      is NetworkResult.Success -> return r
      is NetworkResult.NetworkError -> { delay(delayMs); delayMs = (delayMs * factor).toLong() }
      else -> return r
    }
  }
  return block()
}

UI: Hataları anlaşılır mesajlara eşleme ve yeniden deneme butonu gösterme. NetworkError durumunda uygulamayı çökertmeyin.

HTTP koduna ve errorBody'e erişim sağlamak için Retrofit yöntemlerinin imzalarında Response kullanın.

Önbellekleme, Çevrimdışı Çalışma ve Güvenlik

  • Önbellekleme: OkHttp Cache + doğru Cache-Control/ETag başlıkları. Önemli veriler için — yerel veritabanı (Room) + strateji: önce yerel kopya, ardından arka planda yenileme.
  • JSON: Moshi veya kotlinx.serialization; DTO'ları domain modelinden ayırın (repository katmanında eşleme).
  • Yetkilendirme: Token'ı Interceptor aracılığıyla ekleyin, token yenilemeyi ayrı olarak organize edin (race condition'ları önlemek için).

Basit bir AuthInterceptor örneği:

class AuthInterceptor(private val tokenProvider: () -> String?): Interceptor {
  override fun intercept(chain: Interceptor.Chain): Response {
    val req = chain.request().newBuilder().apply {
      tokenProvider()?.let { addHeader("Authorization", "Bearer $it") }
    }.build()
    return chain.proceed(req)
  }
}

Kritik uygulamalar için certificate pinning'i düşünün. Token'ları güvenli depolamada saklayın (EncryptedSharedPreferences/Keystore).

Sık Yapılan Hatalar

  • Ağ işlemlerini UI thread'inde çağırmak.
  • HTTP kodlarını görmezden gelmek (sadece gövde ile çalışmak).
  • Üretimde token/parola günlüklemek.
  • Sabit kodlanmış baseUrl ve anahtarlar.
  • Tek bir interceptor içinde senkronizasyon olmadan "sihirli" token yenileme girişimleri — race condition'lara/döngülere yol açar.

İyi mimari: Uygulamanın geri kalanında minimum değişiklikle sunucu veya HTTP istemcisini değiştirebilirsiniz.

SSS

  • Zaman aşımları nasıl seçilir? — UX gereksinimlerine ve işlem türüne göre connectTimeout 5–15 sn, read/write 15–60 sn ayarlayın (dosya yüklemeleri daha uzun).
  • JSON için hangisi daha iyi — Moshi mi yoksa kotlinx.serialization mı? — Moshi olgun ve Retrofit ile uyumlu; kotlinx.serialization daha hızlı ve çoklu platform için kullanışlı.
  • Token nasıl doğru şekilde yenilenir? — Yenilemeyi ayrı bir katmanda yapın; token bekleyen istekleri senkronize edin; interceptor içinde sonsuz döngüde yenileme yapmayın.
  • Yeniden deneme ne zaman etkinleştirilir? — Kısa süreli geçici arızalar için (NetworkError, zaman aşımları). Açık HTTP hataları (4xx) için yeniden denemeyi hariç tutun. Deneme sayısını sınırlayın ve backoff kullanın.

Önerilen şablonları uygulayın: NetworkResult sarmalayıcısı, safeApiCall, sınırlı yeniden deneme, merkezi AuthInterceptor ve önbellekleme — böylece ağ katmanınız öngörülebilir, test edilebilir ve gerçek ağ koşullarına dayanıklı hale gelir.