Ağ ve Arka Plan İşleri İçin Kotlin Coroutines vs RxJava
Kotlin Coroutines ve RxJava: gerçek Android uygulamalarında ağ ve arka plan işleri için iptal, hata yönetimi ve test desenlerini karşılaştırın.

Üretim ortamında ağ için neden bu seçim önemli
Gerçek bir Android uygulamasında ağ ve arka plan işleri tek bir API çağrısından daha fazlasıdır. Giriş ve token yenileme, ekranların yükleme ortasındayken döndürülmesi, kullanıcı bir ekranı terk ettikten sonra senkronizasyon, fotoğraf yüklemeleri ve pil tüketimini artırmayacak periyodik işler gibi durumları içerir.
En çok zarar veren hatalar genellikle sözdizimi hataları değildir. Bunlar eşzamansız iş UI'dan daha uzun süre yaşadığında (sızıntılar), iptal edildiğinde UI dururken gerçek isteğin devam etmesi (boşa trafik ve takılı döndürücüler), tekrar denemelerin istekleri çoğaltması (rate limit, yasaklar) ya da farklı katmanların hataları farklı şekilde işlemesi yüzünden kullanıcıya ne gösterileceğinin tahmin edilememesi gibi durumlarda ortaya çıkar.
Kotlin Coroutines vs RxJava kararı günlük güvenilirliği etkiler:
- İşi nasıl modellediğiniz (tek seferlik çağrılar vs akışlar)
- İptalin nasıl yayıldığı
- Hataların nasıl temsil edildiği ve UI'ya nasıl yansıtıldığı
- Ağ, disk ve UI için thread kontrolünüz
- Zamanlama, tekrar denemeler ve kenar durumlarının test edilebilirliği
Aşağıdaki desenler, yük altında veya yavaş ağlarda kırılmaya eğilimli olanları ele alır: iptal, hata yönetimi, tekrar denemeler ve zaman aşımı ile regresyonları önleyen test alışkanlıkları. Örnekler kısa ve pratiktir.
Temel zihni modeller: suspend çağrılar, streamler ve Flow
Kotlin Coroutines ile RxJava arasındaki ana fark, modellediğiniz işin şeklidir.
Bir suspend fonksiyon tek seferlik bir işlemi temsil eder. Bir değer döndürür veya bir hata fırlatır. Bu, profil alma, ayar güncelleme, fotoğraf yükleme gibi çoğu ağ çağrısına uyar. Çağıran kod yukarıdan aşağı okunur; bu, loglama, önbellekleme ve dallanma ekledikten sonra bile okunabilir kalmasını sağlar.
RxJava önce tek bir değerle mi yoksa zaman içinde birden çok değerle mi uğraştığınızı sorar. Single tek seferlik bir sonuçtur (başarılı veya hata). Observable (veya Flowable) birden çok değer yayabilen, sonra tamamlanan veya hata veren bir akıştır. Bu, gerçekten olay benzeri özelliklere uyar: metin değişim olayları, websocket mesajları veya polling gibi.
Flow, coroutine dostu bir akışı temsil etmenin yoludur. Bunu coroutines'ın “akış versiyonu” olarak düşünebilirsiniz; yapılandırılmış iptal ve suspend API'lerle doğrudan uyum sağlar.
Kısa bir pratik kural:
- Bir istek ve bir yanıt için
suspendkullanın. - Zaman içinde değişen değerler için
Flowkullanın. - Uygulamanız zaten operatörlere ve karmaşık akış bileşimlerine güçlü şekilde bağımlıysa RxJava kullanın.
Özellikler büyüdükçe, okunabilirlik genellikle bir seferlik çağrıya akış modelini zorladığınızda veya devam eden olayları tek bir dönüş değeri gibi işlemeye çalıştığınızda bozulur. Önce soyutlamayı gerçeğe göre eşleştirin, sonra buna göre konvansiyonlar geliştirin.
Uygulamada iptal (kısa kod örnekleriyle)
İptal, eşzamansız kodun ya güvenli hissettiği ya da rastgele çöküşler ve boşa giden çağrılar haline geldiği yerdir. Amaç basit: kullanıcı bir ekranı terk ettiğinde o ekran için başlatılan tüm işler durmalıdır.
Kotlin Coroutines ile iptal modelin içine gömülüdür. Bir Job işi temsil eder ve yapılandırılmış concurrency ile genelde job'ları etrafa geçirmezsiniz. İşleri bir scope içinde (örneğin ViewModel scope) başlatırsınız. O scope iptal edildiğinde içindeki her şey de iptal edilir.
class ProfileViewModel(
private val api: Api
) : ViewModel() {
fun loadProfile() = viewModelScope.launch {
// If the ViewModel is cleared, this coroutine is cancelled,
// and so is the in-flight network call (if the client supports it).
val profile = api.getProfile() // suspend
// update UI state here
}
}
İki üretim detayı önemlidir:
- Suspend ağ çağrılarını iptal edilebilir bir istemci üzerinden çağırın. Aksi halde coroutine durur ama HTTP çağrısı çalışmaya devam edebilir.
- Uzun süre takılmaması gereken istekler için
withTimeout(veyawithTimeoutOrNull) kullanın.
RxJava açık disposal kullanır. Her abonelik için bir Disposable tutarsınız veya bunları bir CompositeDisposable içinde toplayabilirsiniz. Ekran kapandığında dispose edersiniz ve zincirin durması gerekir.
class ProfilePresenter(private val api: ApiRx) {
private val bag = CompositeDisposable()
fun attach() {
bag += api.getProfile()
.subscribe(
{ profile -\u003e /* render */ },
{ error -\u003e /* show error */ }
)
}
fun detach() {
bag.clear() // cancels in-flight work if upstream supports cancellation
}
}
Pratik bir ekran-çıkış kuralı: iptalin nerede gerçekleştiğini (scope iptali veya dispose()) işaret edemiyorsanız, işin çalışmaya devam edeceğini varsayın ve yayınlamadan önce düzeltin.
Anlaşılır kalan hata yönetimi
Kotlin Coroutines ile RxJava arasındaki büyük farklardan biri hataların nasıl yol aldığıdır. Coroutines hataları normal kod gibi gösterir: bir suspend çağrı fırlatır ve çağıran ne yapacağına karar verir. Rx, hataları akış içinde iletir; bu güçlüdür ama dikkatli olunmazsa sorunları gizlemek kolaydır.
Beklenmeyen hatalar için (zaman aşımı, 500'ler, ayrıştırma hataları) istisnaları kullanın. UI'nın belirli bir yanıta ihtiyacı olduğunda (yanlış şifre, “e-posta zaten kullanılıyor”) hatayı veri olarak modelleyin; böylece bu domain modelinin bir parçası olur.
Basit bir coroutine deseni yığın izini korur ve okunaklı kalır:
suspend fun loadProfile(): Profile = try {
api.getProfile() // may throw
} catch (e: IOException) {
throw NetworkException("No connection", e)
}
runCatching ve Result gerçekten fırlatma olmadan başarı veya hata döndürmek istediğinizde kullanışlıdır:
suspend fun loadProfileResult(): Result\u003cProfile\u003e =
runCatching { api.getProfile() }
Eğer hatayı da ele almıyorsanız getOrNull() ile temkinli olun. Bu, gerçek hataları sessizce “boş durum” ekranlarına dönüştürebilir.
RxJava'da hata yolunu açık tutun. onErrorReturn yalnızca güvenli geri dönüşler için kullanın. Önbelleğe geçmek gibi kaynak değiştirme gerektiğinde onErrorResumeNext tercih edin. Yeniden denemeler için retryWhen ile kuralları dar tutun ki örneğin “yanlış şifre” durumunda yeniden denemesin.
Hataların yutulmasını önleyen alışkanlıklar seti:
- Hataları bağlamın yakınında bir kere loglayın veya raporlayın.
- Sararken orijinal istisnayı
causeolarak koruyun. - Her hatayı varsayılan bir değere çeviren genelde yakala-çevir (catch-all) geri dönüşlerden kaçının.
- Kullanıcıya gösterilen hataları string değil tiplenmiş bir model olarak yapın.
Threading temelleri: Dispatchers vs Schedulers
Birçok eşzamansız hata threading'e dayanır: ana iş parçacığında ağır işler yapmak veya arka plandan UI'ya dokunmak. Kotlin Coroutines vs RxJava arasındaki temel fark, thread geçişlerini nasıl ifade ettiğinizdir.
Coroutines ile genelde UI işi için ana thread'de başlar, ardından pahalı kısımları arka plandaki dispatcher'a geçirirsiniz. Yaygın seçimler:
- UI güncellemeleri için
Dispatchers.Main - Ağ ve disk gibi bloklayan I/O için
Dispatchers.IO - JSON ayrıştırma, sıralama, şifreleme gibi CPU işleri için
Dispatchers.Default
Basit bir desen: veriyi al, ana thread dışına parse et, sonra render et.
viewModelScope.launch(Dispatchers.Main) {
val json = withContext(Dispatchers.IO) { api.fetchProfileJson() }
val profile = withContext(Dispatchers.Default) { parseProfile(json) }
_uiState.value = UiState.Content(profile)
}
RxJava, işi "nerede yapılacağı" ile subscribeOn ve sonuçların "nerede gözlemleneceği" ile observeOn aracılığıyla ifade eder. Yaygın bir sürpriz, observeOn'un upstream işi etkilemesini beklemektir. Etkilemez. subscribeOn kaynak ve üstündeki operatörler için thread'i ayarlar; her observeOn o noktadan itibaren thread'i değiştirir.
api.fetchProfileJson()
.subscribeOn(Schedulers.io())
.map { json -\u003e parseProfile(json) } // still on io unless you change it
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ profile -\u003e render(profile) },
{ error -\u003e showError(error) }
)
Sürprizleri önleyen bir kural: UI işini tek bir yerde tutun. Coroutines'da UI durumunu Dispatchers.Main üzerinde atayın veya toplayın. RxJava'da render'dan hemen önce tek bir observeOn(main) koyun ve gerçekten gerekmedikçe ek observeOn çağrılarından kaçının.
Ekran takılıyor ise önce ayrıştırma ve map işlemlerini ana thread dışına taşıyın. Bu tek değişiklik birçok gerçek dünya sorununu çözer.
Ağ çağrıları için yeniden denemeler, zaman aşımı ve paralel işler
Sorunsuz yol genellikle problem değildir. Sorunlar takılan çağrılar, işleri daha kötü hale getiren yeniden denemeler veya "paralel" görünen ama gerçekte paralel olmayan işler ile gelir. Bu desenler çoğunlukla bir ekibin Kotlin Coroutines vs RxJava tercihini belirler.
Hızlıca başarısız olan zaman aşımı
Coroutines ile herhangi bir suspend çağrının etrafına sert bir süre limiti koyabilirsiniz. Zaman aşımını çağrı noktasına yakın tutun ki doğru UI mesajını gösterebilin.
val user = withTimeout(5_000) {
api.getUser() // suspend
}
RxJava'da zaman aşımını akışa ekliyorsunuz. Bu, zaman aşımı davranışının paylaşılan bir pipeline'ın parçası olması gerektiğinde faydalıdır.
Zarara yol açmayan yeniden denemeler
Yalnızca yeniden denemenin güvenli olduğu durumlarda yeniden deneyin. Basit kural: yan etki yaratmayan istekler (GET gibi) için daha rahat yeniden deneme; yan etkisi olan isteklerde dikkat. Deneme sayısını sınırlayın ve gecikme ya da jitter ekleyin.
İyi varsayılan kılavuzlar:
- Ağ zaman aşımı ve geçici sunucu hatalarında yeniden dene.
- Doğrulama hatalarında (400'ler) veya kimlik doğrulama hatalarında yeniden deneme yapmayın.
- Yeniden denemeyi sınırlayın (genelde 2–3) ve son hatayı loglayın.
- Sunucuyu vurmamak için geri çekilme (backoff) gecikmeleri kullanın.
RxJava'da retryWhen yalnızca bu hatalar için bu gecikme ile yeniden dene gibi ifadeyi yazmanıza izin verir. Coroutines'da Flow için retry ve retryWhen var; plain suspend fonksiyonlarda küçük bir döngü ile delay kullanmak yaygındır.
Karmaşık olmayan paralel çağrılar
Coroutines paralel işleri doğrudan yapar: iki isteği başlat, ikisini de bekle.
coroutineScope {
val profile = async { api.getProfile() }
val feed = async { api.getFeed() }
profile.await() to feed.await()
}
RxJava, birden çok kaynağı birleştirmek zincirin tamamının amacıysa öne çıkar. zip genelde “ikisini bekle” aracı, merge ise gelen sonuçları gelir gelmez almak için kullanışlıdır.
Büyük veya hızlı stream'lerde backpressure hâlâ önemlidir. RxJava'nın Flowable'ı olgun backpressure araçlarına sahiptir. Coroutines Flow birçok durumu iyi idare eder, ancak olaylar UI veya veritabanı yazımlarından daha hızlı ise buffer veya drop politikalarına ihtiyacınız olabilir.
Birlikte çalışma ve geçiş desenleri (karma kod tabanları)
Çoğu ekip bir gecede geçiş yapmaz. Pratik bir Kotlin Coroutines vs RxJava göçü, uygulamayı modül modül taşırken stabil tutar.
Bir Rx API'sini suspend fonksiyona sarma
Halihazırda bir Single\u003cT\u003e veya Completable varsa, iptal desteği ile sarın ki iptal edilen coroutine Rx aboneliğini dispose etsin.
suspend fun \u003cT : Any\u003e Single\u003cT\u003e.awaitCancellable(): T =
suspendCancellableCoroutine { cont -\u003e
val d = subscribe(
{ value -\u003e cont.resume(value) {} },
{ error -\u003e cont.resumeWithException(error) }
)
cont.invokeOnCancellation { d.dispose() }
}
Bu, yaygın bir hata modunu önler: kullanıcı ekranı terk eder, coroutine iptal olur ama ağ çağrısı çalışmaya devam eder ve daha sonra paylaşılan durumu günceller.
Coroutine kodunu Rx arayanlara açma
Geçiş sırasında bazı katmanlar hala Rx tipleri bekleyecektir. Suspend işi Single.fromCallable ile sarın ve yalnızca arka plan thread'inde bloklayın.
fun loadProfileRx(api: Api): Single\u003cProfile\u003e =
Single.fromCallable {
runBlocking { api.loadProfile() } // ensure subscribeOn(Schedulers.io())
}
Bu sınırı küçük ve belgelenmiş tutun. Yeni kod için suspend API'yi doğrudan coroutine scope'tan çağırmayı tercih edin.
Flow nerede uyuyor, nerede uymuyor
Flow birçok Observable kullanımını değiştirebilir: UI durumu, veritabanı güncellemeleri ve paging-benzeri stream'ler. Hot stream'ler, subject'lar, ileri düzey backpressure ayarı veya ekipte hâlihazırda bilinen çok sayıda özel operatör varsa Flow daha az doğrudan olabilir.
Geçiş stratejisi kafa karışıklığını azaltır:
- Yaprak modülleri önce dönüştürün (ağ, depolama) — suspend API'ler yapın.
- Modül sınırlarında küçük adaptörler ekleyin (Rx -> suspend, suspend -> Rx).
- Tüketicilere de hâkim olduğunuzda Rx stream'leri Flow ile değiştirin.
- Özellik alanı başına bir eşzamansız stil belirleyin.
- Son çağıran da göç ettiğinde adaptörleri silin.
Gerçekten kullanacağınız test desenleri
Zamanlama ve iptal sorunları eşzamansız hataların saklandığı yerlerdir. İyi eşzamansız testler zamanı deterministik yapar ve sonuçları kolayca doğrulanabilir kılar. Kotlin Coroutines vs RxJava burada farklı hissettirir, ama her ikisi de iyi test edilebilir.
Coroutines: runTest, TestDispatcher ve zamanı kontrol etmek
Coroutine kodu için runTest ile test dispatcher kullanın ki test gerçek thread'lere veya gecikmelere bağlı olmasın. Sanal zaman, zaman aşımı, yeniden deneme ve debounce pencerelerini uyku olmadan tetiklemenizi sağlar.
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `emits Loading then Success`() = runTest {
val dispatcher = StandardTestDispatcher(testScheduler)
val repo = Repo(api = fakeApi, io = dispatcher)
val states = mutableListOf\u003cUiState\u003e()
val job = launch(dispatcher) { repo.loadProfile().toList(states) }
testScheduler.runCurrent() // run queued work
assert(states.first() is UiState.Loading)
testScheduler.advanceTimeBy(1_000) // trigger delay/retry windows
testScheduler.runCurrent()
assert(states.last() is UiState.Success)
job.cancel()
}
İptali test etmek için toplayan Jobu (veya ebeveyn scope'u) iptal edin ve fake API'nizin durduğunu veya daha fazla durum yayılmadığını doğrulayın.
RxJava: TestScheduler, TestObserver, deterministik zaman
Rx testleri genelde bir TestScheduler ile zaman ve bir TestObserver ile doğrulama kombinasyonunu kullanır.
@Test
fun `disposes on cancel and stops emissions`() {
val scheduler = TestScheduler()
val observer = TestObserver\u003cUiState\u003e()
val d = repo.loadProfileRx(scheduler)
.subscribeWith(observer)
scheduler.triggerActions()
observer.assertValueAt(0) { it is UiState.Loading }
d.dispose()
scheduler.advanceTimeBy(1, TimeUnit.SECONDS)
observer.assertValueCount(1) // no more events after dispose
}
Her iki stile de hatalı yolları test ederken, exception tipinden çok eşlemeye odaklanın. Bir 401, zaman aşımı veya bozuk bir yanıt sonrası beklenen UI durumunu doğrulayın.
Küçük bir kontrol seti çoğu regresyonu yakalar:
- Yükleniyor ve son durumlar (Success, Empty, Error)
- İptal temizliği (job iptal edildi, disposable dispose edildi)
- Hata eşlemesi (sunucu kodları -> kullanıcı mesajları)
- Yeniden denemeler sonrası çift yayın olmaması
- Zaman tabanlı mantık için sanal zaman kullanımı, gerçek uyku yok
Üretim hatalarına yol açan yaygın yanlışlar
Çoğu üretim sorunu Kotlin Coroutines vs RxJava seçiminden değil, birkaç alışkanlıktan kaynaklanır; bunlar işi düşündüğünüzden daha uzun çalıştırır, iki kez çalıştırır veya UI'ya yanlış zamanda dokunur.
Yaygın bir sızıntı, işi yanlış scope'ta başlatmaktır. Bir ağ çağrısını ekranın ömründen daha uzun yaşayan bir scope'ta başlatırsanız (veya kendi scope'unuzu oluşturup asla iptal etmezseniz), istek kullanıcı ekranı terk ettikten sonra bitip yine de durumu güncelleyebilir. Coroutines'da bu genelde varsayılan olarak uzun ömürlü scope kullanma şeklinde; RxJava'da ise kaçırılmış bir dispose olarak görünür.
Bir diğer klasik hata “ateşle ve unut”tur. Global scope'lar ve unutulmuş Disposable'lar işlerin birikene kadar sorun çıkarmayabilir. Her geri geldiğinizde yenilenen bir sohbet ekranı birkaç gezinmeden sonra birden fazla yenileme işi çalıştırabilir, her biri bellek tutar ve ağ için yarışır.
Yeniden denemeler de kolayca yanlış yapılır. Sınırsız yeniden deneme veya gecikmesiz yeniden deneme backend'i spamlayabilir ve pili tüketebilir. Özellikle hata kalıcıyken (çıkıştan sonra 401 gibi) tehlikelidir. Yeniden denemeyi şartlı yapın, backoff ekleyin ve kalıcı hatalarda durun.
Threading hataları yeniden üretmesi zor çöküşlere neden olur. JSON'u ana thread'de ayrıştırmak veya dispatcher/scheduler'ı nerede koyduğunuza bağlı olarak arka plandan UI'ya güncelleme yapmak bu tür hatalara örnektir.
Hızlı kontrollerin çoğunu yakalaması için:
- İşi bir yaşam döngüsü sahibine bağlayın ve o sahip sona erdiğinde iptal edin.
- Temizliği bariz hale getirin: Job'ları iptal edin veya Disposables'ı tek bir yerde clear edin.
- Yeniden denemelerde katı sınırlar koyun (sayısı, gecikme, hangi hataların uygun olduğu).
- Kod incelemelerinde UI güncellemeleri için tek bir kural uygulayın (sadece main thread).
- Arka plan senkronizasyonunu rastgele bir fonksiyon çağrısı değil, kısıtları olan bir sistem olarak ele alın.
Eğer (örneğin AppMaster tarafından) üretilmiş Kotlin kodundan Android uygulamaları yayımlıyorsanız, aynı tuzaklar geçerlidir. Scope, iptal, yeniden deneme sınırları ve thread kuralları için net konvansiyonlara hâlâ ihtiyacınız var.
Coroutines, RxJava veya her ikisini seçmek için hızlı kontrol listesi
İşin şekliyle başlayın. Çoğu ağ çağrısı tek seferliktir, ama uygulamada bağlantı, kimlik doğrulama durumu veya canlı güncellemeler gibi sürekli sinyaller de vardır. Yanlış soyutlamayı erken seçmek genelde iptal karmaşası ve okunması zor hata yolları olarak geri döner.
Takımı ikna etmek için basit bir karar listesi:
- Tek seferlik istek (giriş, profil alımı):
suspendfonksiyon tercih edin. - Sürekli akış (olaylar, veritabanı güncellemeleri):
Flowveya RxObservabletercih edin. - UI yaşam döngüsü iptali:
viewModelScopeveyalifecycleScopeiçindeki coroutines genelde manuel disposable'lardan daha basittir. - İleri düzey akış operatörlerine ve backpressure'a yoğun bağımlılık: özellikle eski kod tabanlarında RxJava hâlâ daha iyi olabilir.
- Karmaşık yeniden denemeler ve hata eşlemesi: ekibin okunaklı tutabileceği yaklaşımı seçin.
Pratik bir kural: bir ekran bir istek yapıp bir sonuç render ediyorsa coroutines kodu normal bir fonksiyon çağrısına yakın tutar. Birden çok olayı boru hattı halinde işliyorsanız (yazma, debounce, önceki istekleri iptal etme, filtreleri birleştirme) RxJava veya Flow daha doğal gelebilir.
Tutarlılık mükemmellikten iyidir. Her yerde kullanılan iki iyi desen, tutarsız kullanılan beş “en iyi” patttern'den daha kolay desteklenir.
Örnek senaryo: giriş, profil alma ve arka plan senkronizasyonu
Yaygın üretim akışı: kullanıcı Giriş'e dokunur, auth endpoint'ine çağrı yapılır, sonra ana ekran için profil alınır ve en sonunda arka planda bir senkronizasyon başlatılır. Kotlin Coroutines vs RxJava burada günlük bakımda farklı hissettirebilir.
Coroutines versiyonu (sıralı + iptal edilebilir)
Coroutines ile "bunu yap, sonra şunu yap" şekli doğaldır. Kullanıcı ekranı kapatırsa scope'u iptal etmek devam eden işleri durdurur.
suspend fun loginAndLoadProfile(): Result\u003cProfile\u003e = runCatching {
val token = api.login(email, password) // suspend
val profile = api.profile("Bearer $token")
syncManager.startSyncInBackground(token) // fire-and-forget
profile
}.recoverCatching { e -\u003e
throw when (e) {
is HttpException -\u003e when (e.code()) {
401 -\u003e AuthExpiredException()
in 500..599 -\u003e ServerDownException()
else -\u003e e
}
is IOException -\u003e NoNetworkException()
else -\u003e e
}
}
// UI layer
val job = viewModelScope.launch { loginAndLoadProfile() }
override fun onCleared() { job.cancel() }
RxJava versiyonu (zincir + disposal)
RxJava'da aynı akış zincir şeklindedir. İptal etmek genelde CompositeDisposable ile dispose etmektir.
val d = api.login(email, password)
.flatMap { token -\u003e api.profile("Bearer $token").map { it to token } }
.doOnSuccess { (_, token) -\u003e syncManager.startSyncInBackground(token) }
.onErrorResumeNext { e: Throwable -\u003e
Single.error(
when (e) {
is HttpException -\u003e if (e.code() == 401) AuthExpiredException() else e
is IOException -\u003e NoNetworkException()
else -\u003e e
}
)
}
.subscribe({ (profile, _) -\u003e show(profile) }, { showError(it) })
compositeDisposable.add(d)
override fun onCleared() { compositeDisposable.clear() }
Burada asgari test paketi üç sonucu kapsamalıdır: başarı, eşlenen hatalar (401, 500'ler, bağlantı yok) ve iptal/disposal.
Sonraki adımlar: konvansiyonları seçin ve tutarlı tutun
Ekipler genelde problem yaşar çünkü desenler özellikler arasında farklıdır, Kotlin Coroutines vs RxJava seçimi tek başına sebep değildir. Kısa bir karar notu (bir sayfa bile yeterli) incelemelerde zaman kazandırır ve davranışı öngörülebilir kılar.
Tek seferlik işler (bir ağ çağrısı ve bir defalık dönüş) ile stream'leri (zaman içinde güncellenen veriler) netçe ayırın. Her biri için varsayılanı kararlaştırın ve istisnaların ne zaman izinli olduğunu tanımlayın.
Sonra her özelliğin ağ aksaklıklarında aynı şekilde davranmasını sağlayacak küçük ortak yardımcılar ekleyin:
- Hataları (HTTP kodları, zaman aşımı, çevrimdışı) UI'nizin anlayacağı uygulama seviyesi hatalara eşleyen tek bir yer
- Network çağrıları için varsayılan zaman aşımı değerleri ve uzun işlemler için geçerli şekilde üstesinden gelecek bir yol
- Hangi isteklerin güvenli şekilde yeniden denenebileceğini belirten yeniden deneme politikası (ör. GET vs POST)
- Kullanıcı ekranı terk ettiğinde neyin duracağı ve neyin devam edebileceğine dair iptal kuralı
- Hassas veriler sızdırmadan destek için loglama kuralları
Test konvansiyonları da en az mimari kadar önemlidir. Testlerin gerçek zamana veya gerçek thread'lere bağlı olmaması konusunda anlaşın. Coroutines için genelde test dispatcher ve yapılandırılmış scope'lar; RxJava için test scheduler ve açık disposal tercih edilir. Her iki durumda da hedef hızlı, deterministik testlerdir, uyku yok.
Eğer daha hızlı ilerlemek istiyorsanız, AppMaster (appmaster.io) arka uç API'lerini ve Kotlin tabanlı mobil uygulamaları elle yazmadan üretmenizi sağlayan bir seçenek sunar. Üretilmiş kod olsa bile, üretim düzeyinde ağ davranışını öngörülebilir kılan aynı konvansiyonlara — iptal, hata, yeniden deneme ve test kuralları — ihtiyaç vardır.
SSS
Varsayılan olarak tek dönüş yapan bir istek için suspend kullanın; örneğin giriş veya profil alma gibi. Değerler zaman içinde değişiyorsa (websocket ile gelen mesajlar, bağlantı durumu, veritabanı güncellemeleri) Flow (veya Rx streamleri) kullanın.
Evet — ama yalnızca HTTP istemciniz iptali destekliyorsa. Coroutine kapsamı iptal edildiğinde coroutine durur; fakat alttaki HTTP isteğinin de iptal edilebilir olması gerekir, aksi halde istek arka planda çalışmaya devam edebilir.
İşi bir yaşam döngüsü kapsamına bağlayın, örneğin viewModelScope, böylece ekran mantığı sona erdiğinde iş de iptal olur. İş gerçekten uygulama geneli ise uzun ömürlü scope kullanın; aksi halde global veya unutulmuş scope'lardan kaçının.
Coroutines tarafında hatalar genellikle try/catch ile fırlatılır ve UI durumuna eşleştirme yapabileceğiniz yere yakın tutulmalıdır. RxJava tarafında hatalar akış içinde taşınır; bu yüzden hata yolunu açık tutun ve hataları sessizce varsayılan değerlere çevirecek operatörlerden kaçının.
Beklenmeyen hatalar (zaman aşımı, 500 serisi, ayrıştırma hatası) için istisnaları kullanın. UI’nin özel bir yanıt beklediği durumlarda (yanlış şifre, “e-posta zaten kullanılıyor”) tiplenmiş hata verisi kullanın, böylece metin eşleştirmeye bağlı kalmazsınız.
Doğrudan çağrı noktasına yakın bir yerde zaman aşımı uygulayın ki doğru UI mesajını gösterebilesiniz. Coroutines için withTimeout, RxJava için timeout operatörü temiz çözümlerdir.
Yalnızca güvenli olduğunda yeniden deneyin. Genelde idempotent istekler (GET gibi) için daha serbest yeniden deneme uygundur; yan etkisi olan isteklerde (ör. sipariş oluşturma) yeniden denemekten kaçının. Denemeyi sınırlayın (2–3), hataları sınıflandırın ve gecikme/jitter ekleyin.
Coroutines Dispatchers kullanır; tipik olarak UI için Main ile başlar, ardından pahalı işleri IO veya Default'a geçirirsiniz. RxJava subscribeOn ile üst akışın nerede çalıştığını, observeOn ile sonuçların nerede tüketileceğini belirler; render etmeden hemen önce tek bir observeOn(main) kullanmak sürprizleri önler.
Evet. Karma kod tabanlarında sınırı küçük tutun ve iptali destekleyecek şekilde sarın. Rx'i suspend içine sarmak için cancellable bir adapter kullanın (iptal edildiğinde Rx aboneliğini dispose etsin) ve ters yönde yalnızca sınırlı, belgelenmiş bir köprü kullanın.
Sanal zamanı kullanın, böylece testler gerçek zaman veya gecikmelere bağlı olmaz. Coroutines için runTest ve test dispatcher; RxJava için TestScheduler kullanın ve dispose() sonrası yayın olmadığını doğrulayın.


