SideEffect — apa itu, sinkronisasi state di Jetpack Compose

Penulis: IT Sectr Diterbitkan: 2026-06-30 Waktu membaca: 9 mnt

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 — API side-effect untuk kode yang dijalankan setelah setiap rekomposisi yang berhasil.
  • Sinkronisasi — mengirimkan state Compose ke sistem eksternal yang tidak mendukung Compose.
  • Tanpa kunci — tidak seperti LaunchedEffect, SideEffect tidak dimulai ulang, tetapi dijalankan pada setiap rekomposisi.
  • Tanpa pembersihan — SideEffect tidak menyediakan onDispose, hanya ditujukan untuk sinkronisasi satu arah.
  • Sinkron — blok dijalankan secara sinkron dengan fase komposisi Compose, tanpa coroutine.

Apa itu SideEffect di Jetpack Compose

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.

kotlin
@Composable
fun MapScreen(zoomLevel: Int, markers: List<Marker>) {
    val mapView = remember { MapView(LocalContext.current) }
    
    SideEffect {
        mapView.setZoom(zoomLevel)
        mapView.updateMarkers(markers)
    }
    
    AndroidView(factory = { mapView })
}

Bagaimana SideEffect bekerja dan fase komposisi

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.

kotlin
// 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

Memperbarui fungsi callback melalui SideEffect

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.

kotlin
@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 })
}

Sinkronisasi dengan Analytics SDK

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.

kotlin
@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
}

SideEffect vs LaunchedEffect: kapan menggunakan yang mana

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.

KarakteristikSideEffectLaunchedEffect
EksekusiSetiap rekomposisiSaat kunci berubah
AsinkronitasSinkronCoroutine
KunciTidakYa (vararg)
PembersihanTidakPembatalan otomatis coroutine
Penggunaan tipikalCallback, Analytics, sinkronisasi ViewMemuat, 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 umum dengan SideEffect

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

Apakah SideEffect dijalankan pada komposisi pertama?

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).

Bisakah SideEffect menyebabkan loop tak terbatas?

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.

Apa perbedaan antara SideEffect dan snapshotFlow?

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.

Bagaimana cara men-debug SideEffect jika terlalu sering dijalankan?

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.

Bisakah SideEffect dikombinasikan dengan DisposableEffect?

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

  • SideEffect — API side-effect untuk kode sinkron yang dijalankan setelah setiap rekomposisi berhasil di Jetpack Compose.
  • Sinkronisasi — skenario utama: mengirimkan state Compose ke sistem eksternal (Google Maps, WebView, ViewPager, Analytics SDK).
  • Callback — SideEffect memastikan fungsi callback yang menutup state saat ini selalu terkini pada setiap pembaruan UI.
  • Tanpa loop — dijalankan setelah fase komposisi, jadi mengubah state di dalam SideEffect tidak menyebabkan rekomposisi tak terbatas.
  • Keterbatasan — tidak mendukung kunci, asinkronitas, dan blok pembersihan; untuk tugas-tugas ini gunakan LaunchedEffect atau DisposableEffect.
  • Performa — hindari komputasi berat di dalam SideEffect, karena dijalankan pada setiap rekomposisi (hingga 60 kali per detik).
  • Debug — kendalikan frekuensi panggilan melalui Compose Debugger dan optimalkan melalui remember untuk menyaring rekomposisi yang tidak perlu.

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.

Diskusikan proyek

Baca juga