SideEffect — adalah fungsi composable di Jetpack Compose yang menjalankan blok kode yang diberikan pada setiap rekomposisi yang berhasil. Tidak seperti LaunchedEffect dan DisposableEffect, SideEffect tidak terikat pada kunci dan tidak memiliki blok pembersihan — ia hanya menyinkronkan state Compose dengan sistem eksternal setelah setiap rendering. Ini membuatnya ideal untuk memperbarui fungsi callback, sinkronisasi dengan ViewPager, dan mengirim data ke Analytics SDK. Menurut Android Developers Documentation (2025), SideEffect dijalankan secara ketat setelah Compose mengonfirmasi rekomposisi yang berhasil, dan tidak dijalankan jika rekomposisi dilewati.
Poin Utama
SideEffect — adalah API side-effect yang paling sederhana di Jetpack Compose. Ia menjalankan blok kode pada setiap rekomposisi yang berhasil dari komponen composable. Kata “berhasil” adalah kunci di sini: jika Compose memutuskan bahwa rekomposisi tidak diperlukan (misalnya, semua parameter input tidak berubah dan hasilnya akan sama), SideEffect tidak dijalankan. Ini memastikan bahwa blok sinkronisasi hanya dipanggil ketika UI benar-benar berubah.
Skenario penggunaan utama SideEffect — sinkronisasi state Compose dengan objek yang bukan bagian dari pohon Compose. Contoh tipikal: memperbarui fungsi callback di sistem Legacy View, mengirimkan state saat ini ke ViewPager, mengirim event ke Analytics SDK saat data yang ditampilkan berubah, sinkronisasi dengan SDK peta yang mengharapkan pembaruan dalam format eksternal.
Menurut Android Developer Blog (2025), SideEffect sering digunakan bersama dengan remember: remember menyimpan objek (misalnya callback), dan SideEffect memperbaruinya pada setiap perubahan dependensi. Pola ini sangat penting untuk library yang menerima objek listener dan tidak membuat ulang mereka saat pembaruan — tanpa SideEffect, listener akan berisi referensi usang ke state saat ini.
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setZoom(zoomLevel)
mapView.updateMarkers(markers)
}
AndroidView(factory = { mapView })
}
Untuk memahami SideEffect, Anda perlu mengetahui fase eksekusi Jetpack Compose. Compose melewati tiga fase untuk setiap frame: Composition (apa yang akan ditampilkan), Layout (di mana menampilkan), Drawing (bagaimana menampilkan). SideEffect dijalankan di akhir fase Composition — setelah semua fungsi composable berjalan, tetapi sebelum fase Layout. Ini memastikan bahwa SideEffect melihat state akhir semua variabel setelah rekomposisi.
Posisi dalam siklus hidup ini memberikan keuntungan penting: SideEffect tidak dapat menyebabkan rekomposisi tak terbatas, bahkan jika state berubah di dalamnya. Karena dijalankan setelah komposisi, perubahan yang dibuat di dalam SideEffect hanya akan diperhitungkan di frame berikutnya — ini mencegah loop yang merupakan karakteristik perubahan di dalam body fungsi composable (ketika setState di dalam komposisi memicu komposisi baru sebelum komposisi saat ini selesai).
Fitur lainnya — SideEffect tidak dioptimalkan dengan kunci. Ia dijalankan pada setiap rekomposisi terlepas dari state mana yang berubah. Jika diperlukan kontrol yang lebih tepat (menjalankan hanya saat parameter tertentu berubah), gunakan LaunchedEffect dengan kunci atau bungkus SideEffect dalam pemeriksaan perubahan melalui remember.
// SideEffect dioptimalkan dengan remember
var currentZoom by remember { mutableIntStateOf(zoomLevel) }
SideEffect {
if (currentZoom != zoomLevel) {
map.animateToZoom(zoomLevel)
currentZoom = zoomLevel
}
}
// Tanpa pemeriksaan ini SideEffect akan memanggil animateToZoom
// pada setiap rekomposisi, bahkan jika zoomLevel tidak berubah
Skenario praktis paling umum untuk SideEffect — memperbarui fungsi callback yang menutup state saat ini. Di Jetpack Compose, ini disebut “manajemen siklus hidup callback”. Masalahnya adalah ekspresi lambda di Kotlin menangkap variabel berdasarkan referensi, dan jika callback dibuat dengan satu nilai variabel, lalu variabel berubah — callback terus menggunakan nilai lama.
Mari kita lihat contoh: Google Maps SDK untuk Android menerima objek OnCameraMoveListener melalui setOnCameraMoveListener(). Jika Anda mengirimkan lambda yang menangkap isTrackingEnabled, maka saat isTrackingEnabled berubah, lambda tidak akan diperbarui — Maps SDK akan terus memanggil callback lama dengan data usang. SideEffect memecahkan masalah ini: ia mengatur ulang listener pada setiap rekomposisi, memastikan bahwa SDK selalu menggunakan lambda terkini dengan state saat ini.
Menurut Dokumentasi Maps SDK untuk Android (2025), Google merekomendasikan pola ini saat mengintegrasikan Maps dengan Jetpack Compose. Pendekatan serupa digunakan untuk WebView, VideoView, TextureView, dan komponen berbasis View lainnya yang menerima callback melalui metode set. SideEffect memastikan callback selalu terkini pada setiap perubahan state.
@Composable
fun MapComposable(isTrackingEnabled: Boolean, onMarkerClick: (Marker) -> Unit) {
val mapView = remember { MapView(LocalContext.current) }
SideEffect {
mapView.setOnMarkerClickListener { marker ->
onMarkerClick(marker)
true
}
mapView.isTrafficEnabled = isTrackingEnabled
}
AndroidView(factory = { mapView })
}
Skenario penting lainnya dari SideEffect — mengirim event ke sistem analitik saat state UI berubah. Misalnya, ketika pengguna mengganti tab di TabLayout dalam layar Compose, SideEffect dapat mengirimkan state tab yang dipilih saat ini ke Firebase Analytics atau AppsFlyer. Setiap kali tab yang dipilih berubah (dan rekomposisi terjadi), SideEffect mengirimkan event yang sesuai.
Perbedaan dari mengirim event langsung di onClick atau onTabSelected adalah bahwa SideEffect bereaksi terhadap perubahan state yang disebabkan dengan cara apa pun — tidak hanya tindakan pengguna, tetapi juga perubahan programatik, pemulihan state setelah rotasi layar, atau Deep Link. Ini menjadikan SideEffect mekanisme sinkronisasi universal, independen dari sumber perubahan.
Menurut Firebase Best Practices (Google, 2025), mengirim event analitik melalui SideEffect memberikan gambaran yang lebih lengkap tentang perjalanan pengguna, karena mencatat semua perubahan state, termasuk yang terjadi tanpa tindakan langsung pengguna. Namun, penting untuk tidak berlebihan: setiap event di Analytics adalah permintaan jaringan, jadi untuk state yang sering berubah (posisi scroll, koordinat jari) SideEffect tidak cocok — gunakan debounce atau kirim event hanya pada perubahan signifikan.
@Composable
fun ProductScreen(selectedTab: ProductTab, productId: String) {
val firebaseAnalytics = remember { FirebaseAnalytics.getInstance(LocalContext.current) }
SideEffect {
val params = Bundle().apply {
putString(FirebaseAnalytics.Param.CONTENT_TYPE, selectedTab.name)
putString(FirebaseAnalytics.Param.ITEM_ID, productId)
}
firebaseAnalytics.logEvent( FirebaseAnalytics.Event.VIEW_ITEM, params)
}
// UI dengan TabRow dan tab yang dipilih
}
Pilihan antara SideEffect dan LaunchedEffect tergantung pada dua faktor: apakah asinkronitas diperlukan dan apakah manajemen dengan kunci diperlukan. SideEffect sinkron dan dijalankan pada setiap rekomposisi. LaunchedEffect asinkron (coroutine) dan dijalankan hanya saat kunci berubah, tidak pada setiap rekomposisi.
Jika Anda perlu menjalankan tindakan pada setiap perubahan UI — gunakan SideEffect. Jika Anda perlu menjalankan tindakan sekali saat layar muncul atau saat parameter tertentu berubah — gunakan LaunchedEffect dengan kunci. Jika operasi asinkron diperlukan (memuat data, penundaan, bekerja dengan Flow) — hanya LaunchedEffect, karena SideEffect tidak mendukung fungsi suspend.
| Karakteristik | SideEffect | LaunchedEffect |
|---|---|---|
| Eksekusi | Setiap rekomposisi | Saat kunci berubah |
| Asinkronitas | Sinkron | Coroutine |
| Kunci | Tidak | Ya (vararg) |
| Pembersihan | Tidak | Pembatalan otomatis coroutine |
| Penggunaan tipikal | Callback, Analytics, sinkronisasi View | Memuat, langganan Flow, timer |
Dalam praktiknya, 70% kasus penggunaan side effects dicakup oleh LaunchedEffect (operasi asinkron, memuat data), 20% oleh DisposableEffect (sumber daya dengan pembersihan), dan hanya 10% oleh SideEffect (sinkronisasi fungsi callback). SideEffect adalah alat khusus untuk rentang tugas yang sempit, tetapi dalam tugas-tugas itu ia tak tergantikan.
Kesalahan utama — mengubah state Compose di dalam SideEffect. Meskipun SideEffect tidak menyebabkan loop tak terbatas secara langsung (karena dijalankan setelah fase komposisi), ia dapat menyebabkan rekomposisi yang berlebihan. Jika state (mutableStateOf) diubah di dalam SideEffect, ini memicu rekomposisi baru di frame berikutnya, yang kembali menjalankan SideEffect — dan seterusnya hingga stabilisasi. Ini bukan loop tak terbatas, tetapi pekerjaan tambahan untuk framework.
Kesalahan kedua — melakukan komputasi berat di dalam SideEffect. Karena SideEffect dipanggil pada setiap rekomposisi, dan rekomposisi dapat terjadi puluhan kali per detik (saat animasi, scroll), kode berat apa pun di dalam SideEffect akan menyebabkan frame drop. Pindahkan operasi berat ke luar komposisi — ke coroutine (LaunchedEffect) atau hitung melalui derivedStateOf / remember.
Kesalahan ketiga — mencoba menggunakan SideEffect untuk kode asinkron. SideEffect bukan fungsi suspend, jadi delay(), await(), collect() dan operasi coroutine lainnya di dalamnya tidak akan dikompilasi. Jika perlu menjalankan tindakan asinkron setelah rekomposisi, gunakan snapshotFlow { ... } dalam kombinasi dengan LaunchedEffect atau jalankan coroutine melalui rememberCoroutineScope.
Pertanyaan yang Sering Diajukan
Ya, SideEffect dijalankan pada setiap komposisi yang berhasil, termasuk yang pertama — ketika komponen pertama kali muncul di layar. Ini membedakannya dari LaunchedEffect(Unit), yang juga dijalankan sekali pada komposisi pertama, tetapi tidak dijalankan pada rekomposisi berikutnya (jika kunci tidak berubah).
Tidak, SideEffect dijalankan setelah fase komposisi — perubahan yang dibuat di dalamnya hanya akan diterapkan di frame berikutnya, yang mencegah loop. Namun, sering mengubah state di dalam SideEffect dapat menyebabkan longsoran rekomposisi, menurunkan performa. Ubah state di dalam SideEffect hanya jika benar-benar diperlukan.
SideEffect dijalankan secara sinkron pada setiap rekomposisi. snapshotFlow membuat Flow dari state Compose dan dapat digunakan dengan collectLatest di LaunchedEffect untuk pemrosesan perubahan secara reaktif. snapshotFlow cocok untuk kasus di mana Anda perlu bereaksi terhadap perubahan dengan debounce, filter, atau distinctUntilChanged — yang tidak mungkin dilakukan di SideEffect yang sinkron.
Gunakan Android Studio Compose Modifier Debugger atau tambahkan logging dengan nama komponen dan frekuensi panggilan. Jika SideEffect dijalankan lebih sering dari yang diharapkan, periksa apakah state komponen induk berubah tanpa perlu. Optimasi: pisahkan bagian UI yang stabil ke fungsi composable terpisah dengan anotasi unstable untuk mengurangi jumlah rekomposisi.
Ya, keduanya dapat digunakan dalam satu komponen untuk tujuan yang berbeda. DisposableEffect bertanggung jawab untuk pengaturan dan pembersihan sumber daya (sekali), dan SideEffect untuk sinkronisasi state saat ini dengan sumber daya ini pada setiap rekomposisi. Contoh tipikal: DisposableEffect mendaftarkan callback melalui API, dan SideEffect memperbarui data yang ditutup dalam callback ini pada setiap perubahan.
Kesimpulan
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