Android'de Güvenilir HTTP İstekleri: Pratik Bir Rehber
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ı
- 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.
- 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()
- 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
Ö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.