Menguasai Kotlin Multiplatform (KMP) & Compose Multiplatform: membagi business logic antar platform lewat expect/actual dan shared module, serta berbagi UI dengan Compose Multiplatform — tren yang adopsinya naik dari 7% ke 18% pada 2026

Di episode 5 dan 6 kita melihat dua pendekatan cross-platform dari sisi UI. Kini ada pendekatan ketiga yang sedang naik daun: Kotlin Multiplatform (KMP). Kalau RN/Flutter berbagi seluruh aplikasi (UI + logika), KMP menawarkan strategi berbeda: bagikan logika bisnisnya, biarkan UI tiap platform native. Adopsinya naik dari 7% ke 18% di 2026 — dan bersama Compose Multiplatform (CMP), kini UI pun bisa dibagi.
Mengapa ini penting? Karena banyak tim menemukan bahwa kompleksitas sebenarnya aplikasi mobile ada di business logic: validasi, networking, storage, domain model. Membaginya sekali untuk dua platform menghemat separuh pekerjaan, sementara UI tetap native dan terasa "asli" di tiap platform.
Proyek KMP terdiri dari module shared yang berisi business logic lintas platform, plus aplikasi Android dan iOS yang mengonsumsinya:
FitkuKMP/
├── shared/
│ ├── src/commonMain/kotlin/ → logika yang dibagi
│ ├── src/androidMain/kotlin/ → implementasi khusus Android
│ └── src/iosMain/kotlin/ → implementasi khusus iOS
├── androidApp/ → aplikasi Android (Kotlin/Compose)
└── iosApp/ → aplikasi iOS (Swift/SwiftUI)commonMain berisi logika yang tidak bergantung platform — domain model, repository, use case. Untuk bagian yang bergantung platform (misal membaca tanggal sistem), KMP memakai mekanisme expect/actual.
expect mendeklarasikan "kontrak" di commonMain; actual memberikan implementasi nyata di tiap platform:
// commonMain: kontrak
expect fun platformName(): String
// androidMain: implementasi Android
actual fun platformName(): String = "Android ${Build.VERSION.SDK_INT}"
// iosMain: implementasi iOS
actual fun platformName(): String = "iOS ${UIDevice.currentDevice.systemVersion}"Dengan pola ini, kalian menulis logika bisnis sekali dan memanggilnya dari kedua platform.
Mari kita bagikan domain Workout dan kalkulasi kalori ke shared module:
package com.fitku.shared
data class Workout(
val name: String,
val durationMinutes: Int,
val intensity: Intensity,
)
enum class Intensity { LOW, MEDIUM, HIGH }
fun Workout.caloriesBurned(weightKg: Double): Double {
val met = when (intensity) {
Intensity.LOW -> 3.5
Intensity.MEDIUM -> 6.0
Intensity.HIGH -> 9.0
}
return met * weightKg * durationMinutes / 60.0
}
class WorkoutRepository(private val api: FitkuApi) {
suspend fun sync(userId: String): List<Workout> =
api.fetchWorkouts(userId)
}Kemudian konsumsi dari Android:
class WorkoutViewModel : ViewModel() {
private val repository = WorkoutRepository(FitkuApi())
fun caloriesFor(workout: Workout): Double =
workout.caloriesBurned(weightKg = 65.0)
}Dan dari iOS — karena shared dikompilasi ke framework, Swift bisa memanggilnya langsung:
import FitkuShared
let workout = Workout(name: "Lari pagi", durationMinutes: 30, intensity: .high)
let calories = workout.caloriesBurned(weightKg: 65.0)Satu implementasi logika, dua platform memakainya — ini jantung KMP.
Important
Konfigurasi KMP (Gradle kotlin.mpp, export framework lewat CocoaPods atau Swift Package) membutuhkan kesabaran. Gunakan Kotlin Multiplatform Wizard (kmp.jetbrains.com) untuk mem-bootstrap proyek — jauh lebih aman daripada setup manual. Kesalahan umum: lupa menambahkan frame export untuk iOS, dan tidak menyetel minTarget dengan benar.
Jika business logic sudah dibagi, langkah berikutnya adalah berbagi UI lewat Compose Multiplatform — Compose berjalan di Android, iOS, desktop, dan web:
@Composable
fun HomeScreen(weightKg: Double) {
val workouts = remember { sampleWorkouts() }
Column(Modifier.padding(16.dp)) {
Text("Halo, Fitku!", style = MaterialTheme.typography.headlineMedium)
LazyColumn {
items(workouts) { workout ->
WorkoutRow(
workout = workout,
calories = workout.caloriesBurned(weightKg)
)
}
}
}
}Kode UI ini berjalan di Android dan iOS dengan satu codebase. Strategi yang umum: shared UI untuk layar utama, native UI untuk detail yang butuh kedalaman platform.
| Aspek | KMP/CMP | RN/Expo | Flutter |
|---|---|---|---|
| Bahasa | Kotlin | TypeScript/JS | Dart |
| Logika bersama | Ya (shared module) | Ya (satu codebase) | Ya |
| UI | Native atau CMP | Native view (Fabric) | Custom renderer |
| Akses native | Penuh (native-first) | Via bridge/module | Via plugin |
| Kurva belajar | Butuh tahu native | Mudah dari web | Sedang |
| Posisi 2026 | Naik 7% → 18% | Dominan | Kuat di performa |
Pilihan bergantung konteks tim: kalian dari web → RN; ingin performa pixel-perfect → Flutter; ingin investasi di native + Android (dan tim sudah Kotlin) → KMP/CMP.
expect/actual dengan disiplin; jangan curang dengan reflection.kotlinx.serialization agar model JSON aman di kedua platform.Tip
Mulai kecil: tulis dulu shared module untuk satu fitur (misal kalkulasi kalori Fitku), lalu perluas ke repository dan use case. Adopsi bertahap lebih realistis daripada langsung memindahkan seluruh aplikasi.
Pada episode 7 ini, kalian telah menguasai pendekatan ketiga:
expect/actual dan shared module.Di episode 8 selanjutnya kita beralih dari "bahasa & platform" ke cara mengorganisasi kode: Architecture Patterns (MVVM/MVI) — memisahkan UI, logika, dan data lewat Repository pattern dan dependency injection. Sampai jumpa!