13 Nis 2025·6 dk okuma

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.

Ağ ve Arka Plan İşleri İçin Kotlin Coroutines vs RxJava

Üretim ortamında ağ için neden bu seçim önemli\n\nGerç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.\n\nEn ç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.\n\nKotlin Coroutines vs RxJava kararı günlük güvenilirliği etkiler:\n\n- İşi nasıl modellediğiniz (tek seferlik çağrılar vs akışlar)\n- İptalin nasıl yayıldığı\n- Hataların nasıl temsil edildiği ve UI'ya nasıl yansıtıldığı\n- Ağ, disk ve UI için thread kontrolünüz\n- Zamanlama, tekrar denemeler ve kenar durumlarının test edilebilirliği\n\nAş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.\n\n## Temel zihni modeller: suspend çağrılar, streamler ve Flow\n\nKotlin Coroutines ile RxJava arasındaki ana fark, modellediğiniz işin şeklidir.\n\nBir 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.\n\nRxJava ö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.\n\nFlow, 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.\n\nKısa bir pratik kural:\n\n- Bir istek ve bir yanıt için suspend kullanın.\n- Zaman içinde değişen değerler için Flow kullanın.\n- Uygulamanız zaten operatörlere ve karmaşık akış bileşimlerine güçlü şekilde bağımlıysa RxJava kullanın.\n\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.\n\n## Uygulamada iptal (kısa kod örnekleriyle)\n\nİ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.\n\nKotlin 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.\n\nkotlin\nclass ProfileViewModel(\n private val api: Api\n) : ViewModel() {\n\n fun loadProfile() = viewModelScope.launch {\n // If the ViewModel is cleared, this coroutine is cancelled,\n // and so is the in-flight network call (if the client supports it).\n val profile = api.getProfile() // suspend\n // update UI state here\n }\n}\n\n\nİki üretim detayı önemlidir:\n\n- 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.\n- Uzun süre takılmaması gereken istekler için withTimeout (veya withTimeoutOrNull) kullanın.\n\nRxJava 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.\n\nkotlin\nclass ProfilePresenter(private val api: ApiRx) {\n private val bag = CompositeDisposable()\n\n fun attach() {\n bag += api.getProfile()\n .subscribe(\n { profile -\u003e /* render */ },\n { error -\u003e /* show error */ }\n )\n }\n\n fun detach() {\n bag.clear() // cancels in-flight work if upstream supports cancellation\n }\n}\n\n\nPratik 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.\n\n## Anlaşılır kalan hata yönetimi\n\nKotlin 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.\n\nBeklenmeyen 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.\n\nBasit bir coroutine deseni yığın izini korur ve okunaklı kalır:\n\nkotlin\nsuspend fun loadProfile(): Profile = try {\n api.getProfile() // may throw\n} catch (e: IOException) {\n throw NetworkException(\"No connection\", e)\n}\n\n\nrunCatching ve Result gerçekten fırlatma olmadan başarı veya hata döndürmek istediğinizde kullanışlıdır:\n\nkotlin\nsuspend fun loadProfileResult(): Result\u003cProfile\u003e =\n runCatching { api.getProfile() }\n\n\nEğer hatayı da ele almıyorsanız getOrNull() ile temkinli olun. Bu, gerçek hataları sessizce “boş durum” ekranlarına dönüştürebilir.\n\nRxJava'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.\n\nHataların yutulmasını önleyen alışkanlıklar seti:\n\n- Hataları bağlamın yakınında bir kere loglayın veya raporlayın.\n- Sararken orijinal istisnayı cause olarak koruyun.\n- Her hatayı varsayılan bir değere çeviren genelde yakala-çevir (catch-all) geri dönüşlerden kaçının.\n- Kullanıcıya gösterilen hataları string değil tiplenmiş bir model olarak yapın.\n\n## Threading temelleri: Dispatchers vs Schedulers\n\nBirç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.\n\nCoroutines 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:\n\n- UI güncellemeleri için Dispatchers.Main\n- Ağ ve disk gibi bloklayan I/O için Dispatchers.IO\n- JSON ayrıştırma, sıralama, şifreleme gibi CPU işleri için Dispatchers.Default\n\nBasit bir desen: veriyi al, ana thread dışına parse et, sonra render et.\n\nkotlin\nviewModelScope.launch(Dispatchers.Main) {\n val json = withContext(Dispatchers.IO) { api.fetchProfileJson() }\n val profile = withContext(Dispatchers.Default) { parseProfile(json) }\n _uiState.value = UiState.Content(profile)\n}\n\n\nRxJava, 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.\n\nkotlin\napi.fetchProfileJson()\n .subscribeOn(Schedulers.io())\n .map { json -\u003e parseProfile(json) } // still on io unless you change it\n .observeOn(AndroidSchedulers.mainThread())\n .subscribe(\n { profile -\u003e render(profile) },\n { error -\u003e showError(error) }\n )\n\n\nSü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.\n\nEkran 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.\n\n## Ağ çağrıları için yeniden denemeler, zaman aşımı ve paralel işler\n\nSorunsuz 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.\n\n### Hızlıca başarısız olan zaman aşımı\n\nCoroutines 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.\n\nkotlin\nval user = withTimeout(5_000) {\n api.getUser() // suspend\n}\n\n\nRxJava'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.\n\n### Zarara yol açmayan yeniden denemeler\n\nYalnı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.\n\nİyi varsayılan kılavuzlar:\n\n- Ağ zaman aşımı ve geçici sunucu hatalarında yeniden dene.\n- Doğrulama hatalarında (400'ler) veya kimlik doğrulama hatalarında yeniden deneme yapmayın.\n- Yeniden denemeyi sınırlayın (genelde 2–3) ve son hatayı loglayın.\n- Sunucuyu vurmamak için geri çekilme (backoff) gecikmeleri kullanın.\n\nRxJava'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.\n\n### Karmaşık olmayan paralel çağrılar\n\nCoroutines paralel işleri doğrudan yapar: iki isteği başlat, ikisini de bekle.\n\nkotlin\ncoroutineScope {\n val profile = async { api.getProfile() }\n val feed = async { api.getFeed() }\n profile.await() to feed.await()\n}\n\n\nRxJava, 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.\n\nBü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.\n\n## Birlikte çalışma ve geçiş desenleri (karma kod tabanları)\n\nÇoğu ekip bir gecede geçiş yapmaz. Pratik bir Kotlin Coroutines vs RxJava göçü, uygulamayı modül modül taşırken stabil tutar.\n\n### Bir Rx API'sini suspend fonksiyona sarma\n\nHalihazırda bir Single\u003cT\u003e veya Completable varsa, iptal desteği ile sarın ki iptal edilen coroutine Rx aboneliğini dispose etsin.\n\nkotlin\nsuspend fun \u003cT : Any\u003e Single\u003cT\u003e.awaitCancellable(): T =\n suspendCancellableCoroutine { cont -\u003e\n val d = subscribe(\n { value -\u003e cont.resume(value) {} },\n { error -\u003e cont.resumeWithException(error) }\n )\n cont.invokeOnCancellation { d.dispose() }\n }\n\n\nBu, 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.\n\n### Coroutine kodunu Rx arayanlara açma\n\nGeç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.\n\nkotlin\nfun loadProfileRx(api: Api): Single\u003cProfile\u003e =\n Single.fromCallable {\n runBlocking { api.loadProfile() } // ensure subscribeOn(Schedulers.io())\n }\n\n\nBu sınırı küçük ve belgelenmiş tutun. Yeni kod için suspend API'yi doğrudan coroutine scope'tan çağırmayı tercih edin.\n\n### Flow nerede uyuyor, nerede uymuyor\n\nFlow 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.\n\nGeçiş stratejisi kafa karışıklığını azaltır:\n\n- Yaprak modülleri önce dönüştürün (ağ, depolama) — suspend API'ler yapın.\n- Modül sınırlarında küçük adaptörler ekleyin (Rx -> suspend, suspend -> Rx).\n- Tüketicilere de hâkim olduğunuzda Rx stream'leri Flow ile değiştirin.\n- Özellik alanı başına bir eşzamansız stil belirleyin.\n- Son çağıran da göç ettiğinde adaptörleri silin.\n\n## Gerçekten kullanacağınız test desenleri\n\nZamanlama 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.\n\n### Coroutines: runTest, TestDispatcher ve zamanı kontrol etmek\n\nCoroutine 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.\n\nkotlin\n@OptIn(ExperimentalCoroutinesApi::class)\n@Test\nfun `emits Loading then Success`() = runTest {\n val dispatcher = StandardTestDispatcher(testScheduler)\n val repo = Repo(api = fakeApi, io = dispatcher)\n\n val states = mutableListOf\u003cUiState\u003e()\n val job = launch(dispatcher) { repo.loadProfile().toList(states) }\n\n testScheduler.runCurrent() // run queued work\n assert(states.first() is UiState.Loading)\n\n testScheduler.advanceTimeBy(1_000) // trigger delay/retry windows\n testScheduler.runCurrent()\n assert(states.last() is UiState.Success)\n\n job.cancel()\n}\n\n\nİ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.\n\n### RxJava: TestScheduler, TestObserver, deterministik zaman\n\nRx testleri genelde bir TestScheduler ile zaman ve bir TestObserver ile doğrulama kombinasyonunu kullanır.\n\nkotlin\n@Test\nfun `disposes on cancel and stops emissions`() {\n val scheduler = TestScheduler()\n val observer = TestObserver\u003cUiState\u003e()\n\n val d = repo.loadProfileRx(scheduler)\n .subscribeWith(observer)\n\n scheduler.triggerActions()\n observer.assertValueAt(0) { it is UiState.Loading }\n\n d.dispose()\n scheduler.advanceTimeBy(1, TimeUnit.SECONDS)\n observer.assertValueCount(1) // no more events after dispose\n}\n\n\nHer 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.\n\nKüçük bir kontrol seti çoğu regresyonu yakalar:\n\n- Yükleniyor ve son durumlar (Success, Empty, Error)\n- İptal temizliği (job iptal edildi, disposable dispose edildi)\n- Hata eşlemesi (sunucu kodları -> kullanıcı mesajları)\n- Yeniden denemeler sonrası çift yayın olmaması\n- Zaman tabanlı mantık için sanal zaman kullanımı, gerçek uyku yok\n\n## Üretim hatalarına yol açan yaygın yanlışlar\n\nÇ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.\n\nYaygı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.\n\nBir 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.\n\nYeniden 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.\n\nThreading 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.\n\nHızlı kontrollerin çoğunu yakalaması için:\n\n- İşi bir yaşam döngüsü sahibine bağlayın ve o sahip sona erdiğinde iptal edin.\n- Temizliği bariz hale getirin: Job'ları iptal edin veya Disposables'ı tek bir yerde clear edin.\n- Yeniden denemelerde katı sınırlar koyun (sayısı, gecikme, hangi hataların uygun olduğu).\n- Kod incelemelerinde UI güncellemeleri için tek bir kural uygulayın (sadece main thread).\n- Arka plan senkronizasyonunu rastgele bir fonksiyon çağrısı değil, kısıtları olan bir sistem olarak ele alın.\n\nEğ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.\n\n## Coroutines, RxJava veya her ikisini seçmek için hızlı kontrol listesi\n\nİş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.\n\nTakımı ikna etmek için basit bir karar listesi:\n\n- Tek seferlik istek (giriş, profil alımı): suspend fonksiyon tercih edin.\n- Sürekli akış (olaylar, veritabanı güncellemeleri): Flow veya Rx Observable tercih edin.\n- UI yaşam döngüsü iptali: viewModelScope veya lifecycleScope içindeki coroutines genelde manuel disposable'lardan daha basittir.\n- İ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.\n- Karmaşık yeniden denemeler ve hata eşlemesi: ekibin okunaklı tutabileceği yaklaşımı seçin.\n\nPratik 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.\n\nTutarlılık mükemmellikten iyidir. Her yerde kullanılan iki iyi desen, tutarsız kullanılan beş “en iyi” patttern'den daha kolay desteklenir.\n\n## Örnek senaryo: giriş, profil alma ve arka plan senkronizasyonu\n\nYaygı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.\n\n### Coroutines versiyonu (sıralı + iptal edilebilir)\n\nCoroutines ile "bunu yap, sonra şunu yap" şekli doğaldır. Kullanıcı ekranı kapatırsa scope'u iptal etmek devam eden işleri durdurur.\n\nkotlin\nsuspend fun loginAndLoadProfile(): Result\u003cProfile\u003e = runCatching {\n val token = api.login(email, password) // suspend\n val profile = api.profile(\"Bearer $token\")\n syncManager.startSyncInBackground(token) // fire-and-forget\n profile\n}.recoverCatching { e -\u003e\n throw when (e) {\n is HttpException -\u003e when (e.code()) {\n 401 -\u003e AuthExpiredException()\n in 500..599 -\u003e ServerDownException()\n else -\u003e e\n }\n is IOException -\u003e NoNetworkException()\n else -\u003e e\n }\n}\n\n// UI layer\nval job = viewModelScope.launch { loginAndLoadProfile() }\noverride fun onCleared() { job.cancel() }\n\n\n### RxJava versiyonu (zincir + disposal)\n\nRxJava'da aynı akış zincir şeklindedir. İptal etmek genelde CompositeDisposable ile dispose etmektir.\n\nkotlin\nval d = api.login(email, password)\n .flatMap { token -\u003e api.profile(\"Bearer $token\").map { it to token } }\n .doOnSuccess { (_, token) -\u003e syncManager.startSyncInBackground(token) }\n .onErrorResumeNext { e: Throwable -\u003e\n Single.error(\n when (e) {\n is HttpException -\u003e if (e.code() == 401) AuthExpiredException() else e\n is IOException -\u003e NoNetworkException()\n else -\u003e e\n }\n )\n }\n .subscribe({ (profile, _) -\u003e show(profile) }, { showError(it) })\n\ncompositeDisposable.add(d)\noverride fun onCleared() { compositeDisposable.clear() }\n\n\nBurada asgari test paketi üç sonucu kapsamalıdır: başarı, eşlenen hatalar (401, 500'ler, bağlantı yok) ve iptal/disposal.\n\n## Sonraki adımlar: konvansiyonları seçin ve tutarlı tutun\n\nEkipler 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.\n\nTek 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.\n\nSonra her özelliğin ağ aksaklıklarında aynı şekilde davranmasını sağlayacak küçük ortak yardımcılar ekleyin:\n\n- Hataları (HTTP kodları, zaman aşımı, çevrimdışı) UI'nizin anlayacağı uygulama seviyesi hatalara eşleyen tek bir yer\n- 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\n- Hangi isteklerin güvenli şekilde yeniden denenebileceğini belirten yeniden deneme politikası (ör. GET vs POST)\n- Kullanıcı ekranı terk ettiğinde neyin duracağı ve neyin devam edebileceğine dair iptal kuralı\n- Hassas veriler sızdırmadan destek için loglama kuralları\n\nTest 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.\n\nEğ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

When should I use a suspend function vs a stream for networking?

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.

Does coroutine cancellation actually stop an in-flight network request?

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.

What’s the safest way to prevent leaks when the user leaves a screen?

İş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.

How should error handling differ between Coroutines and RxJava?

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.

Should I model errors as exceptions or as data?

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.

What’s a simple way to add timeouts without making code messy?

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.

How do I implement retries without causing duplicate requests or bans?

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.

What’s the main threading pitfall with Dispatchers vs Schedulers?

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.

Can I mix RxJava and Coroutines during a migration?

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.

How do I test cancellation, retries, and time-based logic reliably?

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.

Başlaması kolay
Harika bir şey yaratın

Ücretsiz planla AppMaster ile denemeler yapın.
Hazır olduğunuzda uygun aboneliği seçebilirsiniz.

Başlayın
Ağ ve Arka Plan İşleri İçin Kotlin Coroutines vs RxJava | AppMaster