Kotlin Coroutines vs RxJava untuk Jaringan dan Pekerjaan Latar Belakang
Kotlin Coroutines vs RxJava: bandingkan pembatalan, penanganan kesalahan, dan pola pengujian untuk jaringan dan pekerjaan latar belakang dalam aplikasi Android nyata.

Mengapa pilihan ini penting untuk jaringan produksi
Jaringan dan pekerjaan latar belakang di aplikasi Android nyata lebih dari sekadar satu panggilan API. Ini mencakup login dan refresh token, layar yang bisa diputar saat pemanggilan berjalan, sinkronisasi setelah pengguna meninggalkan layar, unggahan foto, dan pekerjaan periodik yang tidak boleh menguras baterai.
Bug yang paling merusak biasanya bukan masalah sintaks. Mereka muncul ketika pekerjaan async hidup lebih lama dari UI (memory leak), ketika pembatalan menghentikan UI tapi bukan permintaan aktual (trafik terbuang dan spinner macet), ketika retry menggandakan permintaan (batas laju, banned), atau ketika lapisan berbeda menangani kesalahan dengan cara berbeda sehingga tidak ada yang bisa memprediksi apa yang dilihat pengguna.
Keputusan antara Kotlin Coroutines vs RxJava memengaruhi keandalan sehari-hari:
- Bagaimana Anda memodelkan pekerjaan (panggilan satu-kali vs stream)
- Bagaimana pembatalan menyebar
- Bagaimana kesalahan direpresentasikan dan ditampilkan ke UI
- Bagaimana Anda mengontrol thread untuk jaringan, disk, dan UI
- Seberapa mudah menguji timing, retry, dan kasus tepi
Pola di bawah fokus pada hal yang cenderung rusak ketika beban tinggi atau jaringan lambat: pembatalan, penanganan kesalahan, retry dan timeout, serta kebiasaan pengujian yang mencegah regresi. Contoh dibuat singkat dan praktis.
Model mental inti: panggilan suspend, stream, dan Flow
Perbedaan utama antara Kotlin Coroutines vs RxJava adalah bentuk pekerjaan yang Anda modelkan.
Fungsi suspend merepresentasikan operasi satu-kali. Ia mengembalikan satu nilai atau melempar satu kegagalan. Itu cocok untuk sebagian besar panggilan jaringan: ambil profil, perbarui pengaturan, unggah foto. Kode pemanggil dibaca dari atas ke bawah, yang tetap mudah dipindai bahkan setelah Anda menambahkan logging, caching, dan cabang.
RxJava mulai dengan menanyakan apakah Anda berurusan dengan satu nilai atau banyak nilai sepanjang waktu. Single adalah hasil satu-kali (sukses atau error). Observable (atau Flowable) adalah stream yang bisa mengeluarkan banyak nilai, lalu selesai, atau gagal. Ini cocok untuk fitur yang benar-benar berbasis event: perubahan teks, pesan websocket, atau polling.
Flow adalah cara yang ramah coroutine untuk merepresentasikan stream. Anggap itu sebagai versi “stream” dari coroutines, dengan pembatalan terstruktur dan kecocokan langsung dengan API yang suspend.
Aturan praktis singkat:
- Gunakan
suspenduntuk satu permintaan dan satu respons. - Gunakan
Flowuntuk nilai yang berubah seiring waktu. - Gunakan RxJava ketika aplikasi Anda sudah sangat bergantung pada operator dan komposisi stream yang kompleks.
Seiring fitur berkembang, keterbacaan biasanya rusak lebih dulu ketika Anda memaksakan model stream pada panggilan satu-kali, atau mencoba memperlakukan event yang berlangsung sebagai nilai tunggal. Cocokkan abstraksi pada realitas dulu, lalu bangun konvensi di sekitarnya.
Pembatalan dalam praktik (dengan contoh kode singkat)
Pembatalan adalah tempat kode async terasa aman, atau berubah menjadi crash acak dan panggilan yang terbuang. Tujuannya sederhana: ketika pengguna meninggalkan layar, pekerjaan yang dimulai untuk layar itu harus berhenti.
Dengan Kotlin Coroutines, pembatalan dibangun ke dalam model. Job merepresentasikan pekerjaan, dan dengan structured concurrency Anda biasanya tidak meneruskan job. Anda memulai pekerjaan di dalam scope (seperti ViewModel scope). Ketika scope itu dibatalkan, semua yang ada di dalamnya juga dibatalkan.
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
}
}
Dua detail produksi yang penting:
- Panggil jaringan yang suspend melalui klien yang mendukung pembatalan. Kalau tidak, coroutine berhenti tetapi panggilan HTTP mungkin tetap berjalan.
- Gunakan
withTimeout(atauwithTimeoutOrNull) untuk permintaan yang tidak boleh menggantung.
RxJava menggunakan disposal eksplisit. Anda menyimpan Disposable untuk setiap subscription, atau mengumpulkannya dalam CompositeDisposable. Ketika layar pergi, Anda dispose, dan rantai itu harus berhenti.
class ProfilePresenter(private val api: ApiRx) {
private val bag = CompositeDisposable()
fun attach() {
bag += api.getProfile()
.subscribe(
{ profile -> /* render */ },
{ error -> /* show error */ }
)
}
fun detach() {
bag.clear() // cancels in-flight work if upstream supports cancellation
}
}
Aturan praktis saat layar keluar: jika Anda tidak dapat menunjuk di mana pembatalan terjadi (pembatalan scope atau dispose()), anggap pekerjaan akan terus berjalan dan perbaiki sebelum dikirim.
Penanganan kesalahan yang tetap bisa dimengerti
Perbedaan besar dalam Kotlin Coroutines vs RxJava adalah bagaimana kesalahan melintas. Coroutines membuat kegagalan terlihat seperti kode normal: panggilan suspend melempar, dan pemanggil memutuskan apa yang harus dilakukan. Rx mendorong kegagalan melalui stream, yang kuat, tapi mudah menyembunyikan masalah jika Anda tidak hati-hati.
Gunakan exception untuk kegagalan tak terduga (timeout, 500s, bug parsing). Modelkan error sebagai data ketika UI membutuhkan respons spesifik (kata sandi salah, “email sudah digunakan”) dan Anda ingin itu menjadi bagian dari domain model.
Pola coroutine sederhana menjaga stack trace dan tetap terbaca:
suspend fun loadProfile(): Profile = try {
api.getProfile() // may throw
} catch (e: IOException) {
throw NetworkException("No connection", e)
}
runCatching dan Result berguna ketika Anda benar-benar ingin mengembalikan sukses atau gagal tanpa melempar:
suspend fun loadProfileResult(): Result<Profile> =
runCatching { api.getProfile() }
Berhati-hatilah dengan getOrNull() jika Anda tidak juga menangani kegagalan. Itu bisa dengan tenang mengubah bug nyata menjadi layar “kosong”.
Di RxJava, jaga jalur error tetap eksplisit. Gunakan onErrorReturn hanya untuk fallback yang aman. Lebih suka onErrorResumeNext ketika Anda perlu mengganti sumber (misalnya, ke data cache). Untuk retry, buat aturan sempit dengan retryWhen sehingga Anda tidak me-retry pada “kata sandi salah.”
Serangkaian kebiasaan yang mencegah error tersapu:
- Log atau laporkan error sekali, di dekat tempat Anda punya konteks.
- Pertahankan pengecualian asli sebagai
causesaat membungkus. - Hindari fallback catch-all yang mengubah setiap kesalahan menjadi nilai default.
- Buat error yang terlihat pengguna sebagai model bertipe, bukan string.
Dasar threading: Dispatchers vs Schedulers
Banyak bug async disebabkan oleh threading: melakukan pekerjaan berat di main thread, atau menyentuh UI dari thread latar. Kotlin Coroutines vs RxJava terutama berbeda dalam cara Anda mengekspresikan perpindahan thread.
Dengan coroutines, Anda sering memulai di thread utama untuk kerja UI, lalu pindah ke dispatcher latar untuk bagian yang mahal. Pilihan umum:
Dispatchers.Mainuntuk update UIDispatchers.IOuntuk I/O yang blocking seperti jaringan dan diskDispatchers.Defaultuntuk pekerjaan CPU seperti parsing JSON, sorting, enkripsi
Pola sederhana: ambil data, parse di luar main, lalu render.
viewModelScope.launch(Dispatchers.Main) {
val json = withContext(Dispatchers.IO) { api.fetchProfileJson() }
val profile = withContext(Dispatchers.Default) { parseProfile(json) }
_uiState.value = UiState.Content(profile)
}
RxJava mengekspresikan “di mana pekerjaan berlangsung” dengan subscribeOn dan “di mana hasil diobservasi” dengan observeOn. Kejutan umum adalah mengharapkan observeOn memengaruhi kerja upstream. Itu tidak. subscribeOn menetapkan thread untuk sumber dan operator di atasnya, dan setiap observeOn mengganti thread dari titik itu ke depan.
api.fetchProfileJson()
.subscribeOn(Schedulers.io())
.map { json -> parseProfile(json) } // still on io unless you change it
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ profile -> render(profile) },
{ error -> showError(error) }
)
Aturan yang menghindari kejutan: jaga pekerjaan UI di satu tempat. Di coroutines, tetapkan atau kumpulkan state UI di Dispatchers.Main. Di RxJava, taruh satu observeOn(main) terakhir tepat sebelum rendering, dan jangan taburkan observeOn tambahan kecuali memang perlu.
Jika sebuah layar tersendat, pindahkan parsing dan mapping keluar dari main terlebih dulu. Perubahan tunggal itu memperbaiki banyak masalah dunia nyata.
Retry, timeout, dan pekerjaan paralel untuk panggilan jaringan
Jalur yang lancar jarang menjadi masalah. Masalah datang dari panggilan yang menggantung, retry yang memperparah, atau pekerjaan “paralel” yang sebenarnya tidak paralel. Pola-pola ini sering menentukan apakah tim lebih memilih Kotlin Coroutines vs RxJava.
Timeout yang gagal cepat
Dengan coroutines, Anda bisa memberi batas keras di sekitar panggilan suspend mana pun. Simpan timeout dekat lokasi pemanggilan sehingga Anda bisa menampilkan pesan UI yang tepat.
val user = withTimeout(5_000) {
api.getUser() // suspend
}
Di RxJava, Anda menempelkan operator timeout ke stream. Itu berguna ketika perilaku timeout harus menjadi bagian dari pipeline bersama.
Retry tanpa menyebabkan kerusakan
Retry hanya ketika retry aman. Aturan sederhana: retry permintaan idempoten (seperti GET) lebih bebas daripada permintaan yang membuat efek samping (seperti “create order”). Bahkan begitu, batasi jumlah dan tambahkan delay atau jitter.
Penjaga default yang baik:
- Retry pada timeout jaringan dan error server sementara.
- Jangan retry pada error validasi (400s) atau kegagalan auth.
- Batasi retry (sering 2-3) dan log kegagalan akhir.
- Gunakan backoff delay agar Anda tidak membanjiri server.
Di RxJava, retryWhen memungkinkan mengekspresikan “retry hanya untuk error ini, dengan delay ini.” Di coroutines, Flow punya retry dan retryWhen, sementara fungsi suspend biasa sering menggunakan loop kecil dengan delay.
Panggilan paralel tanpa kode kusut
Coroutines membuat pekerjaan paralel menjadi langsung: mulai dua permintaan, tunggu keduanya.
coroutineScope {
val profile = async { api.getProfile() }
val feed = async { api.getFeed() }
profile.await() to feed.await()
}
RxJava menonjol ketika menggabungkan banyak sumber adalah inti dari rantai. zip biasanya untuk “tunggu keduanya”, dan merge berguna ketika Anda ingin hasil segera setelah tiba.
Untuk stream besar atau cepat, backpressure tetap penting. Flowable di RxJava punya alat backpressure matang. Coroutines Flow menangani banyak kasus dengan baik, tetapi Anda mungkin masih memerlukan kebijakan buffering atau dropping jika event bisa melampaui kemampuan UI atau penulisan database Anda.
Interop dan pola migrasi (kode campuran)
Kebanyakan tim tidak berpindah sekaligus. Migrasi Kotlin Coroutines vs RxJava yang praktis menjaga aplikasi tetap stabil sambil memindahkan modul demi modul.
Bungkus API Rx menjadi fungsi suspend
Jika Anda punya Single<T> atau Completable yang ada, bungkus dengan dukungan pembatalan sehingga coroutine yang dibatalkan juga membuang langganan Rx.
suspend fun <T : Any> Single<T>.awaitCancellable(): T =
suspendCancellableCoroutine { cont ->
val d = subscribe(
{ value -> cont.resume(value) {} },
{ error -> cont.resumeWithException(error) }
)
cont.invokeOnCancellation { d.dispose() }
}
Ini menghindari mode kegagalan umum: pengguna meninggalkan layar, coroutine dibatalkan, tapi panggilan jaringan tetap berjalan dan memperbarui state bersama nanti.
Ekspos kode coroutine ke pemanggil Rx
Selama migrasi, beberapa lapisan masih mengharapkan tipe Rx. Bungkus pekerjaan suspend dalam Single.fromCallable dan blok hanya di thread latar.
fun loadProfileRx(api: Api): Single<Profile> =
Single.fromCallable {
runBlocking { api.loadProfile() } // ensure subscribeOn(Schedulers.io())
}
Jaga batas ini kecil dan terdokumentasi. Untuk kode baru, lebih suka memanggil API suspend langsung dari coroutine scope.
Di mana Flow cocok, dan di mana tidak
Flow bisa menggantikan banyak kasus Observable: state UI, pembaruan database, dan stream seperti paging. Flow bisa kurang langsung jika Anda sangat mengandalkan hot streams, subjects, penalaan backpressure tingkat lanjut, atau sekumpulan besar operator kustom yang tim Anda sudah kuasai.
Strategi migrasi yang mengurangi kebingungan:
- Konversi modul daun terlebih dulu (jaringan, penyimpanan) ke API suspend.
- Tambahkan adapter kecil di batas modul (Rx ke suspend, suspend ke Rx).
- Ganti Rx stream dengan Flow hanya ketika Anda juga mengendalikan konsumennya.
- Gunakan satu gaya async per area fitur.
- Hapus adapter segera setelah pemanggil terakhir bermigrasi.
Pola pengujian yang benar-benar akan Anda gunakan
Masalah timing dan pembatalan adalah tempat bug async bersembunyi. Tes async yang bagus membuat waktu deterministik dan hasil mudah di-assert. Ini area lain di mana Kotlin Coroutines vs RxJava terasa berbeda, meskipun keduanya bisa diuji dengan baik.
Coroutines: runTest, TestDispatcher, dan mengontrol waktu
Untuk kode coroutine, lebih suka runTest dengan test dispatcher sehingga tes Anda tidak bergantung pada thread nyata atau delay nyata. Waktu virtual memungkinkan Anda memicu timeout, retry, dan jendela debounce tanpa tidur.
@OptIn(ExperimentalCoroutinesApi::class)
@Test
fun `emits Loading then Success`() = runTest {
val dispatcher = StandardTestDispatcher(testScheduler)
val repo = Repo(api = fakeApi, io = dispatcher)
val states = mutableListOf<UiState>()
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()
}
Untuk menguji pembatalan, batalkan Job yang mengumpulkan (atau scope induk) dan pastikan fake API berhenti atau tidak ada lagi state yang disalurkan.
RxJava: TestScheduler, TestObserver, waktu deterministik
Tes Rx biasanya menggabungkan TestScheduler untuk waktu dan TestObserver untuk asersi.
@Test
fun `disposes on cancel and stops emissions`() {
val scheduler = TestScheduler()
val observer = TestObserver<UiState>()
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
}
Saat menguji jalur error di kedua gaya, fokuslah pada pemetaan, bukan tipe pengecualian. Assert state UI yang Anda harapkan setelah 401, timeout, atau respon yang salah.
Set kecil pemeriksaan yang menutupi sebagian besar regresi:
- State Loading dan final (Success, Empty, Error)
- Pembersihan pembatalan (job dibatalkan, disposable dibuang)
- Pemetaan error (kode server ke pesan pengguna)
- Tidak ada emisi duplikat setelah retry
- Logika berbasis waktu menggunakan waktu virtual, bukan delay nyata
Kesalahan umum yang menyebabkan bug produksi
Sebagian besar isu produksi bukan disebabkan oleh memilih Kotlin Coroutines vs RxJava. Mereka berasal dari beberapa kebiasaan yang membuat pekerjaan berjalan lebih lama dari yang Anda pikirkan, berjalan dua kali, atau menyentuh UI di waktu yang salah.
Salah satu kebocoran umum adalah memulai pekerjaan di scope yang salah. Jika Anda memulai panggilan jaringan dari scope yang lebih lama umurnya daripada layar (atau Anda membuat scope sendiri dan tidak pernah membatalkannya), permintaan bisa selesai setelah pengguna pergi dan masih mencoba memperbarui state. Di coroutines, ini sering terlihat seperti menggunakan scope jangka panjang secara default. Di RxJava, biasanya disposable yang terlewat.
Klasik lain adalah “fire and forget.” Global scopes dan Disposable yang terlupakan terasa baik sampai pekerjaan menumpuk. Layar chat yang menyegarkan setiap resume mudah berakhir dengan beberapa job refresh berjalan setelah beberapa navigasi, masing-masing menahan memori dan bersaing untuk jaringan.
Retry juga mudah salah. Retry tak terbatas, atau retry tanpa delay, bisa membanjiri backend Anda dan menguras baterai. Ini sangat berbahaya ketika kegagalan permanen, seperti 401 setelah logout. Buat retry bersyarat, tambahkan backoff, dan hentikan ketika error tidak dapat dipulihkan.
Kesalahan threading menyebabkan crash yang sulit direproduksi. Anda mungkin mem-parse JSON di main thread atau memperbarui UI dari thread latar tergantung di mana Anda menaruh dispatcher atau scheduler.
Pemeriksaan cepat yang menangkap sebagian besar isu ini:
- Ikat pekerjaan ke lifecycle owner dan batalkan saat owner itu berakhir.
- Buat pembersihan jelas: batalkan Jobs atau bersihkan Disposables di satu tempat.
- Beri batas ketat pada retry (jumlah, delay, dan error yang memenuhi syarat).
- Tegakkan satu aturan untuk update UI (hanya main thread) dalam code review.
- Perlakukan sinkronisasi latar sebagai sistem dengan batasan, bukan panggilan fungsi acak.
Jika Anda mengirim aplikasi Android dari kode Kotlin yang digenerasi (misalnya, dari AppMaster), jebakan yang sama masih berlaku. Anda tetap memerlukan konvensi yang jelas untuk scope, pembatalan, batas retry, dan aturan thread.
Daftar cek cepat untuk memilih Coroutines, RxJava, atau keduanya
Mulailah dari bentuk pekerjaan. Sebagian besar panggilan jaringan adalah satu-kali, tetapi aplikasi juga punya sinyal terus-menerus seperti konektivitas, state auth, atau update live. Memilih abstraksi yang salah sejak awal biasanya terlihat nanti sebagai pembatalan yang berantakan dan jalur error yang susah dibaca.
Cara sederhana untuk memutuskan (dan menjelaskan ke tim Anda):
- Permintaan satu-kali (login, ambil profil): lebih suka fungsi
suspend. - Stream berkelanjutan (event, pembaruan database): lebih suka
Flowatau RxObservable. - Pembatalan lifecycle UI: coroutines di
viewModelScopeataulifecycleScopesering lebih sederhana daripada disposables manual. - Ketergantungan besar pada operator stream lanjutan dan backpressure: RxJava mungkin masih lebih cocok, terutama di codebase lama.
- Retry kompleks dan pemetaan error: pilih pendekatan yang tim Anda bisa jaga keterbacaannya.
Aturan praktis: jika satu layar membuat satu permintaan dan merender satu hasil, coroutines menjaga kode tetap dekat dengan panggilan fungsi normal. Jika Anda membangun pipeline dari banyak event (ketikan, debounce, batalkan permintaan sebelumnya, gabungkan filter), RxJava atau Flow sering terasa lebih alami.
Konsistensi mengalahkan kesempurnaan. Dua pola bagus yang digunakan di mana-mana lebih mudah dipelihara daripada lima pola “terbaik” yang digunakan tidak konsisten.
Contoh skenario: login, ambil profil, dan sinkronisasi latar
Alur produksi umum: pengguna mengetuk Login, Anda memanggil endpoint auth, lalu ambil profil untuk layar beranda, dan akhirnya memulai sinkronisasi latar. Di sinilah Kotlin Coroutines vs RxJava terasa berbeda dalam pemeliharaan sehari-hari.
Versi Coroutines (sekuensial + dapat dibatalkan)
Dengan coroutines, bentuk “lakukan ini, lalu itu” itu natural. Jika pengguna menutup layar, pembatalan scope menghentikan pekerjaan yang sedang berlangsung.
suspend fun loginAndLoadProfile(): Result<Profile> = runCatching {
val token = api.login(email, password) // suspend
val profile = api.profile("Bearer $token")
syncManager.startSyncInBackground(token) // fire-and-forget
profile
}.recoverCatching { e ->
throw when (e) {
is HttpException -> when (e.code()) {
401 -> AuthExpiredException()
in 500..599 -> ServerDownException()
else -> e
}
is IOException -> NoNetworkException()
else -> e
}
}
// UI layer
val job = viewModelScope.launch { loginAndLoadProfile() }
override fun onCleared() { job.cancel() }
Versi RxJava (rantai + disposal)
Di RxJava, alur yang sama adalah sebuah rantai. Pembatalan berarti dispose, biasanya dengan CompositeDisposable.
val d = api.login(email, password)
.flatMap { token -> api.profile("Bearer $token").map { it to token } }
.doOnSuccess { (_, token) -> syncManager.startSyncInBackground(token) }
.onErrorResumeNext { e: Throwable ->
Single.error(
when (e) {
is HttpException -> if (e.code() == 401) AuthExpiredException() else e
is IOException -> NoNetworkException()
else -> e
}
)
}
.subscribe({ (profile, _) -> show(profile) }, { showError(it) })
compositeDisposable.add(d)
override fun onCleared() { compositeDisposable.clear() }
Suite pengujian minimal di sini harus mencakup tiga hasil: sukses, kegagalan yang dipetakan (401, 500s, tidak ada jaringan), dan pembatalan/dispose.
Langkah selanjutnya: pilih konvensi dan pertahankan konsistensi
Tim biasanya bermasalah karena pola berbeda antar fitur, bukan karena Kotlin Coroutines vs RxJava salah. Catatan keputusan singkat (bahkan satu halaman) menghemat waktu di review dan membuat perilaku dapat diprediksi.
Mulai dengan pembagian jelas: pekerjaan satu-kali (panggilan jaringan tunggal yang mengembalikan sekali) vs stream (pembaruan seiring waktu, seperti event websocket, lokasi, atau perubahan database). Putuskan default untuk masing-masing, dan definisikan kapan pengecualian boleh dilakukan.
Lalu tambahkan satu set helper bersama sehingga setiap fitur berperilaku sama ketika jaringan bermasalah:
- Satu tempat untuk memetakan error (kode HTTP, timeout, offline) ke kegagalan tingkat aplikasi yang dipahami UI
- Nilai timeout default untuk panggilan jaringan, dengan cara jelas untuk menimpa untuk operasi panjang
- Kebijakan retry yang menyatakan apa yang aman untuk di-retry (misalnya, GET vs POST)
- Aturan pembatalan: apa yang berhenti saat pengguna meninggalkan layar, dan apa yang boleh terus
- Aturan logging yang membantu dukungan tanpa membocorkan data sensitif
Konvensi pengujian sama pentingnya. Sepakati pendekatan standar agar tes tidak bergantung pada waktu nyata atau thread nyata. Untuk coroutines, itu biasanya berarti test dispatcher dan scope terstruktur. Untuk RxJava, biasanya berarti test scheduler dan disposal eksplisit. Bagaimanapun, tujuannya tes cepat, deterministik, tanpa sleep.
Jika Anda ingin bergerak lebih cepat secara keseluruhan, AppMaster (appmaster.io) adalah salah satu opsi untuk menghasilkan API backend dan aplikasi mobile berbasis Kotlin tanpa menulis semuanya dari awal. Bahkan dengan kode yang digenerasi, konvensi produksi yang sama seputar pembatalan, error, retry, dan pengujianlah yang membuat perilaku jaringan dapat diprediksi.
FAQ
Default ke suspend untuk satu permintaan yang hanya mengembalikan sekali, seperti login atau mengambil profil. Gunakan Flow (atau aliran Rx) ketika nilai berubah seiring waktu, seperti pesan websocket, konektivitas, atau pembaruan database.
Ya, tetapi hanya jika klien HTTP Anda mendukung pembatalan. Coroutines menghentikan coroutine saat scope dibatalkan, tetapi panggilan HTTP di bawahnya juga harus mendukung pembatalan agar permintaan tidak terus berjalan di latar belakang.
Ikat pekerjaan ke lifecycle scope, seperti viewModelScope, sehingga dibatalkan saat logika layar selesai. Hindari meluncurkan di scope jangka panjang atau global kecuali pekerjaan tersebut memang untuk keseluruhan aplikasi.
Di coroutines, kegagalan umumnya dilempar dan Anda menanganinya dengan try/catch dekat tempat Anda bisa memetakannya ke state UI. Di RxJava, error mengalir melalui stream, jadi jaga jalur error tetap eksplisit dan hindari operator yang diam-diam mengubah kegagalan menjadi nilai default.
Gunakan pengecualian untuk kegagalan tak terduga seperti timeout, 500, atau masalah parsing. Gunakan data error bertipe ketika UI membutuhkan respons spesifik seperti “kata sandi salah” atau “email sudah digunakan”, sehingga Anda tidak bergantung pada pencocokan string.
Terapkan timeout di tempat Anda bisa menampilkan pesan UI yang tepat, misalnya dekat lokasi pemanggilan. Di coroutines, withTimeout sederhana untuk panggilan suspend; di RxJava, operator timeout membuat timeout menjadi bagian dari rantai.
Retry hanya ketika aman, biasanya untuk permintaan idempoten seperti GET, dan batasi jumlahnya kecil seperti 2–3 kali. Jangan retry pada error validasi atau kegagalan autentikasi, dan tambahkan delay sehingga Anda tidak membanjiri server atau menguras baterai.
Coroutines menggunakan Dispatchers dan biasanya dimulai di Main untuk UI, lalu beralih ke IO atau Default untuk pekerjaan berat. RxJava menggunakan subscribeOn untuk menentukan di mana upstream berjalan dan observeOn untuk tempat Anda mengonsumsi hasil; letakkan satu observeOn(main) terakhir sebelum rendering untuk menghindari kejutan.
Bisa. Jaga batas itu kecil dan terdokumentasi. Bungkus Rx menjadi suspend dengan adapter yang mendukung pembatalan sehingga pembatalan coroutine juga membuang (dispose) langganan Rx, dan hanya ekspos pekerjaan suspend ke pemanggil Rx dalam batas yang kecil dan terdokumentasi.
Gunakan waktu virtual agar pengujian tidak menunggu atau bergantung pada thread nyata. Untuk coroutines, runTest dengan test dispatcher memungkinkan mengontrol delay dan pembatalan; untuk RxJava, gunakan TestScheduler dan pastikan tidak ada emisi setelah dispose(). Asser state UI yang diharapkan setelah 401, timeout, atau respon yang cacat.


