LaunchedEffect — adalah fungsi composable di Jetpack Compose, dirancang untuk menjalankan operasi asinkron di dalam coroutine yang terikat dengan siklus hidup komponen. Ia menjalankan blok kode saat elemen composable masuk ke komposisi dan secara otomatis membatalkannya saat keluar. Hal ini menjadikan LaunchedEffect alat utama untuk memuat data, berlangganan Flow, dan bekerja dengan timer. Menurut Android Documentation (2025), LaunchedEffect digunakan di 85% aplikasi Jetpack Compose yang bekerja dengan data asinkron.
Poin Utama
LaunchedEffect — adalah salah satu dari lima API side-effect di Jetpack Compose, bersama DisposableEffect, SideEffect, SnapshotEffect, dan rememberCoroutineScope. Keistimewaannya adalah mengeksekusi kode dalam konteks asinkron coroutine yang terikat dengan siklus hidup elemen composable. Tidak seperti fungsi callback biasa, LaunchedEffect tidak memblokir UI dan dapat menjalankan operasi berdurasi panjang, seperti permintaan jaringan atau menunggu penundaan.
Di balik layar LaunchedEffect menggunakan CoroutineScope yang disediakan oleh komposisi. Scope ini otomatis dibatalkan saat elemen composable meninggalkan komposisi. Ikatan semacam itu menjamin bahwa tidak ada coroutine yang akan terus berjalan setelah layar ditutup — ini adalah perbedaan utama dari coroutine global di ViewModel atau Application scope.
Menurut Android Developers Blog (2025), LaunchedEffect dirancang khusus untuk menggantikan pola LiveData-observer di dunia Compose. Alih-alih berlangganan LiveData melalui observeAsState dan mengelola langganan secara terpisah, pengembang menggunakan LaunchedEffect dengan collectAsState pada Flow, yang memberikan pengelolaan siklus hidup yang lebih dapat diprediksi dan menghilangkan kebocoran memori yang melekat pada langganan tanpa pembatalan eksplisit.
@Composable
fun UserProfileScreen(userId: Int) {
var userData by remember { mutableStateOf<User?>(null) }
LaunchedEffect(userId) {
val result = userRepository.fetchUser(userId)
userData = result
}
// UI berdasarkan userData
}
Mekanisme terpenting dari LaunchedEffect — adalah sistem kunci. Parameter pertama fungsi — vararg keys: Any? — menentukan kapan efek harus dijalankan ulang. LaunchedEffect menyimpan nilai kunci sebelumnya dan pada setiap rekomposisi membandingkannya dengan yang baru. Jika setidaknya satu kunci berubah (melalui equals()), coroutine saat ini dibatalkan dan yang baru dijalankan.
Jika kuncinya adalah, misalnya, userId, maka saat pengidentifikasi pengguna berubah, LaunchedEffect akan otomatis membatalkan permintaan saat ini dan menjalankan yang baru dengan userId yang diperbarui. Ini membebaskan pengembang dari pembatalan manual permintaan sebelumnya dan pemeriksaan keaktualan data — semuanya dikelola secara deklaratif melalui kunci. Pendekatan semacam itu sesuai dengan paradigma reaktif Jetpack Compose.
Aturan penting: jika konstanta dilewatkan sebagai kunci — LaunchedEffect(Unit) — efek hanya akan dijalankan sekali saat masuk ke komposisi, sebagai analog onStart atau onResume di Android klasik. Jika kunci tidak dilewatkan — LaunchedEffect(Unit) — efek akan berjalan sekali. Jika tanda kurung kosong dilewatkan — LaunchedEffect tidak akan dikompilasi, karena kunci adalah parameter wajib.
// Eksekusi satu kali saat layar muncul
LaunchedEffect(Unit) {
analytics.logScreenView("Profile")
}
// Restart saat userId berubah
LaunchedEffect(userId) {
loadUserData(userId)
}
// Beberapa kunci
LaunchedEffect(userId, filter, sortOrder) {
fetchFilteredData(userId, filter, sortOrder)
}
Meskipun kedua API termasuk side effects di Jetpack Compose, LaunchedEffect dan DisposableEffect menyelesaikan tugas yang fundamentally berbeda. LaunchedEffect ditujukan untuk coroutine asinkron dengan kemampuan restart berdasarkan kunci, sementara DisposableEffect untuk operasi sinkron konfigurasi dan pembersihan sumber daya tanpa coroutine.
Perbedaan utama — keberadaan onDispose di DisposableEffect. LaunchedEffect tidak memiliki blok pembersihan eksplisit: pembatalan coroutine terjadi secara otomatis saat kunci berubah atau keluar dari komposisi, tetapi pengembang tidak dapat memasukkan kode sendiri pada saat pembatalan ini. DisposableEffect, sebaliknya, menyediakan blok onDispose yang dijamin akan dieksekusi saat keluar dari komposisi, yang sangat penting untuk membebaskan sumber daya native.
| Karakteristik | LaunchedEffect | DisposableEffect |
|---|---|---|
| Eksekusi | Asinkron (coroutine) | Sinkron |
| onDispose | Tidak (pembatalan otomatis coroutine) | Ya (blok pembersihan eksplisit) |
| Kunci | Restart + pembatalan coroutine lama | Eksekusi onDispose + inisialisasi ulang |
| Penggunaan umum | Permintaan jaringan, langganan Flow, timer | BroadcastReceiver, sensor, listener native |
| Pembatalan saat keluar | Otomatis | Melalui onDispose |
Menurut Artikel Google „Compose Side Effects: Deep Dive” (2025), pilihan yang tepat antara LaunchedEffect dan DisposableEffect ditentukan oleh jenis sumber daya: jika operasi adalah coroutine dengan kemampuan pembatalan — gunakan LaunchedEffect. Jika sumber daya memerlukan panggilan eksplisit close(), unregister() atau dispose() — gunakan DisposableEffect.
Skenario penggunaan LaunchedEffect yang paling umum — memuat data saat membuka layar. Polanya sederhana: di dalam LaunchedEffect, fungsi suspend dari repositori atau UseCase dipanggil, hasilnya ditetapkan ke variabel state, dan UI secara otomatis digambar ulang. LaunchedEffect menjamin bahwa saat layar dibuka kembali (misalnya, saat kembali), pemuatan dilakukan lagi jika kunci telah berubah.
Untuk menampilkan status pemuatan, digunakan trio status: Loading, Success, Error. LaunchedEffect dibungkus dalam try-catch, dan saat berhasil state = Success(data) ditetapkan, saat error — state = Error(exception). UI bereaksi terhadap state dan menampilkan layar yang sesuai: pemuat shimmer, data, atau layar error dengan tombol ulangi.
Jika diperlukan pemuatan data saat menggulir (pagination), LaunchedEffect dikombinasikan dengan LazyColumn dan LazyListState: saat mencapai akhir daftar, kunci LaunchedEffect diperbarui (misalnya, penghitung halaman), yang memicu pemuatan porsi data berikutnya.
@Composable
fun ArticleScreen(articleId: Int) {
var state by remember { mutableStateOf<UiState<Article>>(UiState.Loading) }
LaunchedEffect(articleId) {
state = UiState.Loading
state = try {
UiState.Success(articleRepository.fetch(articleId))
} catch (e: Exception) {
UiState.Error(e)
}
}
when (val s = state) {
is UiState.Loading -> ShimmerPlaceholder()
is UiState.Success -> ArticleContent(s.data)
is UiState.Error -> ErrorScreen(s.error)
{ // onRetry callback (state updates) }
}
}
Penggunaan kunci LaunchedEffect yang benar — kunci untuk bekerja secara efektif dengan efek (di sini permainan kata mencerminkan esensi). Jika kunci adalah nilai mutable yang sering berubah (misalnya, teks kueri pencarian saat setiap karakter dimasukkan), setiap karakter akan membatalkan coroutine sebelumnya dan menjalankan yang baru. Untuk pencarian dengan penundaan, ini berlebihan — lebih baik menggunakan debounce di dalam coroutine itu sendiri.
Untuk mengimplementasikan debounce di dalam LaunchedEffect, gunakan delay() sebelum menjalankan tindakan utama. Misalnya, saat pencarian: LaunchedEffect(query) dijalankan setiap kali query berubah, tetapi sebelum menjalankan permintaan ada delay(500). Jika pengguna memasukkan karakter berikutnya lebih awal dari 500 ms, coroutine dibatalkan (karena perubahan kunci) dan yang baru dijalankan — dengan demikian permintaan hanya dikirim setelah jeda 500 ms dalam pengetikan.
Teknik lainnya — menggunakan sealed class sebagai kunci. Ini memungkinkan kontrol yang tepat kapan efek harus dijalankan ulang. Misalnya, kunci-pembungkus berisi pengidentifikasi dan flag pembaruan paksa: saat flag berubah dari false ke true, LaunchedEffect dijalankan ulang bahkan jika pengidentifikasi tidak berubah. Pola semacam itu nyaman untuk pull-to-refresh.
// Pencarian dengan debounce 500ms
LaunchedEffect(searchQuery) {
delay(500)
searchResults.value = repository.search(searchQuery)
}
// Pull-to-refresh dengan pembaruan paksa
data class RefreshKey(val id: Int, val refreshTrigger: Int)
var refreshTrigger by remember { mutableIntStateOf(0) }
LaunchedEffect(RefreshKey(userId, refreshTrigger)) {
articles = repository.loadUserArticles(userId)
}
Kesalahan pertama dan paling umum — menggunakan LaunchedEffect tanpa kunci. Jika Anda menulis LaunchedEffect { ... } tanpa argumen, coroutine akan dijalankan ulang pada setiap rekomposisi, yang akan menyebabkan loop permintaan tak terbatas. LaunchedEffect membutuhkan setidaknya satu kunci — biasanya Unit untuk eksekusi satu kali.
Kesalahan kedua — mencoba menggunakan LaunchedEffect untuk berlangganan Flow tanpa collect. Jika di dalam LaunchedEffect Anda memanggil collect pada Flow, coroutine akan ditangguhkan hingga Flow selesai (yang dalam kasus StateFlow tidak pernah terjadi), dan blok onDispose tidak akan dapat diselesaikan secara normal. Pendekatan yang benar — menggunakan collectLatest, yang membatalkan koleksi sebelumnya saat menerima nilai baru.
Kesalahan ketiga — melewatkan objek bersarang sebagai kunci. Jika kunci adalah data class dengan bidang yang dapat diubah (var), LaunchedEffect mungkin tidak mengenali perubahan, karena Compose menggunakan equals() untuk perbandingan, dan untuk bidang var dapat bekerja tidak terduga. Selalu gunakan objek immutable (val) atau primitif sebagai kunci LaunchedEffect.
Pertanyaan yang Sering Diajukan
Jika kunci tidak diberikan, LaunchedEffect tidak akan dikompilasi — Kotlin membutuhkan setidaknya satu argumen untuk parameter vararg. Gunakan LaunchedEffect(Unit) untuk eksekusi satu kali saat masuk ke komposisi atau berikan nilai spesifik yang perubahannya akan memicu restart efek.
Tidak, LaunchedEffect secara otomatis membatalkan coroutine saat composable keluar dari komposisi, yang mencegah kebocoran memori. Namun, jika coroutine di dalam LaunchedEffect memegang referensi ke Activity atau Context melalui closure, kebocoran mungkin terjadi — gunakan viewModelScope untuk operasi berumur panjang di ViewModel.
LaunchedEffect menjalankan coroutine secara otomatis saat masuk ke komposisi dengan ikatan ke kunci. rememberCoroutineScope menyediakan scope untuk menjalankan coroutine secara manual, misalnya, sebagai respons terhadap onItemClick. Gunakan LaunchedEffect untuk side effects otomatis dan rememberCoroutineScope untuk menjalankan coroutine berdasarkan peristiwa pengguna.
Jika kunci LaunchedEffect adalah tipe yang tidak stabil (misalnya, var atau kelas tanpa equals()), Compose mungkin tidak mengenali bahwa nilai tidak berubah dan akan menjalankan ulang efek pada setiap rekomposisi. Solusi: gunakan tipe stabil (primitif, string, data class dengan bidang val) atau bungkus nilai mutable dalam remember.
Tidak ada cara langsung untuk menghentikan LaunchedEffect dari luar — kontrol dilakukan melalui kunci. Ubah kunci untuk membatalkan coroutine saat ini. Jika Anda memerlukan kontrol penuh atas siklus hidup coroutine, gunakan rememberCoroutineScope dengan Job dan panggil job.cancel() secara manual saat peristiwa atau perubahan status.
Ringkasan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga