Episode ini menangani pekerjaan asinkron di Compose: LaunchedEffect, SideEffect, dan DisposableEffect, integrasi coroutines dengan UI, pola loading state untuk panggilan jaringan, serta efek yang sadar lifecycle.

Composable adalah fungsi murni: diberikan input, menghasilkan UI. Tapi aplikasi nyata perlu melakukan hal di luar komposisi — memanggil jaringan, mendengarkan listener, menulis ke file. Pekerjaan semacam ini disebut side effect, dan Compose menyediakan API khusus untuk menjalankannya dengan aman.
Melakukannya secara sembarangan — misalnya memanggil coroutine langsung di dalam composable — justru menimbulkan bug karena komposisi bisa dijalankan ulang dan dibatalkan kapan saja.
Episode 10 ini meliputi keluarga efek, integrasi coroutine, pola loading state, dan efek yang sadar lifecycle.
LaunchedEffect menjalankan coroutine saat composable masuk komposisi dan membatalkannya saat keluar. Ini tempat yang tepat untuk pemuatan data awal:
@Composable
fun ArtikelScreen(viewModel: ArtikelViewModel) {
val state by viewModel.uiState.collectAsState()
LaunchedEffect(Unit) {
viewModel.muatArtikel()
}
when (val s = state) {
is UiState.Memuat -> LoadingIndikator()
is UiState.Sukses -> KontenArtikel(s.artikel)
is UiState.Gagal -> PesanError(s.pesan)
}
}LaunchedEffect(Unit) berjalan sekali saat komposisi dimulai. Kuncinya di parameter: jika key berubah, efek dibatalkan lalu dijalankan ulang. Pemakaian when pada sealed class ini adalah pola state UI yang akan dikupas di episode 20. Untuk memastikan dependency lifecycle terpasang, cek pohon dependency dengan ./gradlew :app:dependencies.
Ketika key LaunchedEffect berubah, efek lama dibatalkan dan yang baru dijalankan. Pola ini berguna untuk menanggapi perubahan parameter:
LaunchedEffect(artikelId) {
muatKomentar(artikelId)
}Saat artikelId berubah, coroutine lama dibatalkan dan pemuatan baru dimulai — mencegah data dari artikel lama menimpa yang baru.
SideEffect dijalankan setelah komposisi berhasil — tempat yang tepat untuk memberitahukan kode di luar Compose:
SideEffect {
onSetStatusBar(warnaStatusBar)
}SideEffect { ... } dipanggil pada setiap komposisi yang berhasil. Berbeda dengan LaunchedEffect, kode di sini sinkron dan tidak boleh suspend — berguna untuk sinkronisasi dengan view system.
DisposableEffect menyediakan blok onDispose untuk membersihkan resource saat composable keluar dari komposisi:
DisposableEffect(Unit) {
val listener = object : LocationListener {
override fun onLocationChanged(p: Location) {
posisiBaru(p)
}
}
lokasiManager.tambahListener(listener)
onDispose {
lokasiManager.hapusListener(listener)
}
}onDispose menjamin listener dibuang ketika composable tidak lagi dipakai — mencegah kebocoran resource dan callback ke UI yang sudah hilang.
Untuk meluncurkan coroutine dari event seperti onClick, gunakan rememberCoroutineScope. Scope ini otomatis dibatalkan saat composable meninggalkan komposisi:
val scope = rememberCoroutineScope()
Button(onClick = {
scope.launch {
simpanKeServer()
}
}) { Text("Simpan") }rememberCoroutineScope() membungkus LaunchedEffect untuk penggunaan ad-hoc: coroutine berjalan di UI, dibatalkan otomatis saat composable hilang. Ingat pola ini sudah dipakai untuk Snackbar di episode 7.
Untuk meng-collect Flow hanya saat UI terlihat, gunakan collectAsStateWithLifecycle dari library lifecycle-runtime-compose:
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.9.0")val lokasi by viewModel.lokasiFlow.collectAsStateWithLifecycle()
Text("Posisi: $lokasi")collectAsStateWithLifecycle berhenti mengumpulkan saat UI tidak terlihat dan melanjutkan saat terlihat kembali — menghindari pekerjaan sia-sia di background dan menjadi rekomendasi resmi Android.
Gabungkan efek dan state menjadi pola loading yang bersih: UiState membawa satu dari tiga kondisi, dan UI merender sesuai kondisi. Pola ini juga menjadi dasar MVVM dan MVI di episode 20.
sealed interface UiState<out T> {
data object Memuat : UiState<Nothing>
data class Sukses<T>(val data: T) : UiState<T>
data class Gagal(val pesan: String) : UiState<Nothing>
}UiState mengekspresikan kondisi UI tanpa boolean yang saling bertabrakan. Dengan LaunchedEffect untuk memicu pemuatan dan when untuk rendering, alur data tetap mudah dilacak.
Episode 10 menangani pekerjaan asinkron: LaunchedEffect untuk pemuatan berbasis key, SideEffect untuk sinkronisasi luar, DisposableEffect dengan onDispose untuk cleanup, rememberCoroutineScope untuk event ad-hoc, dan collectAsStateWithLifecycle untuk meng-collect Flow secara sadar lifecycle.
Inti yang harus dibawa pulang:
Di episode 11 selanjutnya kita akan membahas data persistence dan offline — integrasi Room dan DataStore, state restoration dengan rememberSaveable dan SavedStateHandle, pola offline-first dengan repository, serta UI yang digerakkan data.