Compose Multiplatform vs Flutter: Rendering, Performa, dan Ukuran Bundle
Compose Multiplatform dan Flutter dapat menghasilkan UI mobile yang sama-sama mulus, tetapi jalur yang mereka tempuh sampai pixel muncul di layar tidak sama. Perbedaan ini lebih penting daripada sekadar membandingkan “Kotlin versus Dart”.
Compose Multiplatform memperluas model pemrograman Compose ke beberapa platform. Flutter membawa UI stack yang lebih mandiri bersama engine dan Dart runtime miliknya. Keduanya menambahkan komponen di luar kode aplikasi, tetapi biayanya muncul di tempat berbeda: rendering, startup, memory, ukuran binary, dan integrasi platform.
Ada satu detail yang sering rancu: Skia bukan Flutter. Skia adalah graphics library serbaguna yang digunakan banyak proyek. Flutter memang lama sangat bergantung pada Skia, tetapi Flutter saat ini menggunakan Impeller sebagai default rendering engine di iOS dan Android. Compose Multiplatform menggunakan implementasi Compose yang sesuai platform di Android dan jalur rendering berbasis Skia melalui Skiko pada target seperti iOS dan desktop.
Rendering stack keduanya berbeda
Gambaran sederhana pada mobile terlihat seperti ini:
Compose Multiplatform
Android:
Compose UI -> Android Compose rendering -> Android graphics stack -> GPU
iOS:
Compose UI -> Skiko / Skia -> Metal-backed graphics path -> GPUFlutter lebih seragam:
Flutter widgets
|
v
Flutter framework
|
v
Flutter Engine
|
v
Impeller
|
+---- Metal on iOS
|
+---- Vulkan on Android
|
v
GPUKarena itu, ringkasan “keduanya memakai Skia” sudah tidak cukup untuk menjelaskan arsitektur modern keduanya. Renderer mobile default Flutter adalah Impeller. Skia masih dapat muncul di bagian lain graphics stack Flutter dan backend yang didukung, tetapi bukan lagi renderer mobile default yang menentukan perilaku rendering Flutter saat ini.
Compose Multiplatform mengambil jalur yang tidak sepenuhnya seragam. Pada Android, Compose Multiplatform pada dasarnya adalah Jetpack Compose sehingga dapat menggunakan Compose stack Android yang sudah ada, bukan membawa implementasi rendering identik ke setiap target. Pada iOS, Compose menggambar UI melalui jalur canvas miliknya yang ditopang Skiko/Skia.
Perbedaan platform ini memengaruhi ekspektasi performa sekaligus overhead binary.
Frame time lebih berguna daripada angka FPS
UI 60 Hz memiliki waktu sekitar 16,7 ms untuk setiap frame. Pada 120 Hz, budget itu turun menjadi sekitar 8,3 ms.
60 Hz -> 1000 / 60 = 16.67 ms per frame
120 Hz -> 1000 / 120 = 8.33 ms per frameFramework tidak otomatis menjadi “lebih cepat” hanya karena benchmark menampilkan 120 FPS. Pertanyaan yang lebih berguna adalah apakah composition, layout, drawing, rasterization, dan GPU submission secara konsisten selesai di dalam frame budget ketika menjalankan workload aplikasi yang sebenarnya.
Flutter memiliki keuntungan dari sisi predictability: Google mengendalikan framework, engine, dan Impeller sebagai satu stack. Impeller melakukan precompile terhadap sekumpulan shader yang lebih kecil pada saat build, khususnya untuk mengurangi stall akibat kompilasi shader saat runtime yang dapat memicu animation jank.
Compose Multiplatform juga sudah jauh berkembang dibanding implementasi iOS awalnya. Saat Compose Multiplatform 1.8 membuat dukungan iOS stable, JetBrains melaporkan startup yang sebanding dengan aplikasi native dan scrolling yang sebanding dengan SwiftUI, termasuk pada perangkat high-refresh-rate. Pada Compose Multiplatform 1.11, concurrent rendering di iOS diaktifkan secara default sehingga pekerjaan rendering dipindahkan ke render thread khusus.
Hasil tersebut membuat anggapan lama bahwa Compose Multiplatform di iOS selalu lambat semakin sulit dipertahankan. Namun, itu juga bukan berarti setiap aplikasi CMP pasti menyamai setiap aplikasi Flutter atau native. Arsitektur aplikasi, luas recomposition, image decoding, perilaku list, overdraw, effects, dan custom drawing dapat lebih dominan daripada perbedaan framework.
Pekerjaan berat di CPU adalah masalah terpisah
Benchmark rendering juga tidak banyak menjelaskan performa business logic.
Bayangkan aplikasi yang mem-parsing dokumen besar, melakukan encryption, resize gambar, atau menjalankan query database lokal. Pekerjaan tersebut dapat menjadi bottleneck sebelum renderer menjadi masalah.
frame
|
+-- state update
+-- composition / widget build
+-- layout
+-- drawing
+-- raster / GPU work
|
+-- unrelated CPU work --------> tetap dapat menghambat progressCompose Multiplatform menggunakan Kotlin dan, tergantung target, runtime serta compilation model Kotlin yang sesuai. Aplikasi Flutter release mengompilasi Dart secara ahead-of-time menjadi native machine code pada mobile, sementara Flutter Engine menyediakan runtime services dan integrasi graphics.
Untuk perbandingan nyata, saya lebih memilih profiling workload yang benar-benar akan dijalankan daripada menarik kesimpulan dari aplikasi counter kosong. Scrolling feed, UI dengan map, text editor, camera screen, dan database client menekan bagian sistem yang berbeda.
Ukuran bundle memperlihatkan biaya portability
Cross-platform UI tetap memiliki biaya pada binary.
Di Android, Compose Multiplatform memiliki keuntungan struktural: target Android-nya adalah Jetpack Compose. Ia tidak perlu mereplikasi rendering stack iOS atau desktop yang sama persis di dalam aplikasi Android.
Di iOS, Compose Multiplatform perlu membawa lebih banyak bagian rendering miliknya. Benchmark Compose Multiplatform 1.8 dari JetBrains melaporkan sekitar 9 MB tambahan ukuran aplikasi dibanding aplikasi SwiftUI dengan UI logic dan assets yang setara. Angka tersebut berguna sebagai benchmark framework, bukan pajak tetap untuk setiap aplikasi.
Flutter juga membawa framework overhead. Aplikasi release mengemas Flutter Engine bersama kode Dart yang sudah di-AOT compile dan assets aplikasi. Ukuran download akhirnya juga dipengaruhi architecture slicing, pemrosesan oleh app store, native plugin, font, gambar, symbol, dan input build lainnya.
Karena itu, tabel sederhana seperti “CMP = X MB, Flutter = Y MB” biasanya menyesatkan kecuali kedua aplikasi dibangun dari feature set yang sama, untuk target yang sama, dengan assets dan release settings yang sama.
Model yang lebih masuk akal adalah:
final application size
=
application code
+ framework/runtime code
+ renderer/engine code
+ native dependencies
+ resources and fonts
+ assets
+ architecture/build overheadKontribusi relatif setiap bagian berubah ketika aplikasi membesar. Selisih framework 9 MB terasa besar pada utility kecil, tetapi jauh kurang penting pada aplikasi media berukuran 200 MB.
Desktop mengubah perhitungannya lagi
Pada desktop, perbedaan packaging semakin terlihat.
Aplikasi Compose Multiplatform desktop umumnya membawa komponen JVM/runtime bersama native graphics libraries. Aplikasi Flutter desktop membawa Flutter engine dan artifact native milik aplikasi. Keduanya tidak tepat dinilai hanya dari ukuran satu executable karena paket distribusi juga memuat supporting libraries dan resources.
Metrik yang relevan adalah artifact yang benar-benar di-download dan di-install pengguna:
macOS -> .app / signed distribution package
Windows -> packaged application / installer
Linux -> distribution packagePada software desktop, ketersediaan runtime, format packaging, symbol stripping, bundled fonts, native libraries, dan kompresi installer dapat mengubah ukuran secara signifikan. Perbandingan berdasarkan executable hello-world saja mudah menghasilkan kesimpulan yang keliru.
Integrasi platform dapat lebih penting daripada renderer
Ada biaya lain yang tidak terlihat di chart frame time: perpindahan ke native platform UI.
Kedua framework dapat menanamkan atau berinteraksi dengan native view, tetapi screen yang didominasi map native, camera preview, video, web view, atau control khusus platform tidak lagi hanya mengukur renderer framework.
Compose Multiplatform memiliki trade-off yang menarik untuk tim Kotlin. Shared Kotlin code dapat mencakup domain logic, networking, persistence, dan UI sementara native API tetap dapat diakses melalui Kotlin Multiplatform interop. Kode Android juga tetap dekat dengan ekosistem Jetpack Compose.
Flutter menawarkan UI runtime yang lebih seragam antartarget. Konsistensi ini berguna ketika produk menginginkan rendering dan perilaku yang hampir sama di Android dan iOS, tetapi konsekuensinya Flutter engine menjadi bagian sentral dari arsitektur aplikasi.
Tidak ada model yang menghapus platform boundary. Keduanya hanya menempatkan boundary tersebut di lokasi yang berbeda.
Yang saya benchmark sebelum memilih
Untuk proyek nyata, saya akan membuat screen representatif yang sama di kedua framework lalu mengukur:
release artifact size
cold-start time
warm-start time
p50 / p95 / p99 frame time
janky frame count
peak and steady-state memory
CPU during sustained scrolling
battery impact during a realistic sessionPengujian sebaiknya memakai release atau profile build pada perangkat fisik yang sama. Debug build berguna saat development, tetapi buruk sebagai dasar perbandingan runtime performance.
Saya juga akan menguji satu screen yang mewakili kondisi terburuk aplikasi, bukan hanya home screen. Untuk aplikasi note, misalnya, workload tersebut dapat berupa dokumen panjang dengan selection dan IME input. Pada social client, kandidatnya adalah feed yang penuh media. Pada dashboard, beberapa chart yang terus diperbarui dapat menjadi kasus yang lebih representatif.
Data seperti itu jauh lebih berguna daripada memilih hanya berdasarkan nama renderer.
Perbedaan praktisnya
Compose Multiplatform dan Flutter sama-sama mampu menghasilkan UI production yang mulus, tetapi trade-off engineering keduanya berbeda.
Compose Multiplatform sangat menarik ketika Kotlin, Jetpack Compose, dan shared application logic sudah menjadi bagian arsitektur. Jalur Android-nya menyatu secara natural dengan ekosistem Android, sedangkan iOS dan desktop lebih bergantung pada multiplatform rendering stack.
Flutter lebih vertically integrated. Flutter framework, engine, Dart runtime, dan Impeller memberi Google kontrol lebih rapat atas perjalanan sebuah frame dari widget code sampai GPU. Hasilnya dapat berupa perilaku rendering yang lebih konsisten di target mobile, dengan konsekuensi runtime dan engine Flutter ikut menjadi bagian aplikasi.
Ukuran bundle mengikuti pola yang sama: tidak ada satu angka universal yang benar untuk kedua framework. Pengukuran yang berguna adalah release artifact dari aplikasi yang benar-benar akan dibuat. Arsitektur renderer menjelaskan asal sebagian overhead; profiling menunjukkan apakah overhead itu benar-benar penting.