Kembali ke Blog
The 4 Day Sprint: Menavigasi Refactor KMP yang High-Stakes
29 Apr 2026 Muhammad Tantowi Jauhari

The 4 Day Sprint: Menavigasi Refactor KMP yang High-Stakes


Dalam dunia startup, ritme kerja sering kali diibaratkan seperti balapan Formula 1. Segalanya bergerak sangat cepat, dan keputusan harus diambil dalam sekejap mata.

Baru-baru ini, saya berada di salah satu skenario “seize the moment” tersebut. Aplikasi Android native saya sudah live dan stabil, ketika seorang stakeholder utama di perusahaan tempat saya bekerja datang dengan permintaan yang sudah bisa ditebak: klien kita butuh dukungan iOS, dan mereka butuh secepatnya. Bukannya panik, saya justru melihat ini sebagai peluang emas untuk mentransisikan proyek ke Kotlin Multiplatform (KMP).

Jembatan antara logo Android dan Apple yang merepresentasikan KMP

Kebetulan saya sudah mendalami KMP selama beberapa bulan terakhir, jadi momen ini terasa seperti waktu yang tepat untuk menguji kemampuan KMP di medan tempur yang sesungguhnya.

Scoping dan Penyelarasan

Sebelum satu baris kode pun diubah, saya menyadari bahwa pekerjaan terpenting justru terjadi di luar IDE. Dalam lingkungan yang bergerak cepat, kita harus menyelaraskan visi dengan para stakeholder saat itu juga.

Saya meluangkan waktu untuk mendefinisikan dengan tepat apa saja yang perlu dipindahkan dan mengelola ekspektasi mengenai siklus pengembangannya. Kejelasan di awal ini sangat krusial untuk mencegah “scope creep” yang biasanya menjadi pembunuh utama sprint pendek.

Memahami Alur (Data Flow)

Setelah cakupan pekerjaan ditetapkan, saya memetakan alur data mulai dari API dan database hingga ke lapisan UI. Saya harus mengidentifikasi setiap dependency yang spesifik untuk platform tertentu. Contohnya seperti SharedPreferences, OkHttp, atau pemanggilan Context Android. Bagian-bagian inilah yang nantinya perlu diabstraksi menggunakan mekanisme expect/actual milik KMP.

Fokusnya bukan untuk memindahkan seluruh UI, melainkan mengisolasi logika bisnis utama agar bisa hidup di dalam sebuah shared module.

Sprint 4 Hari

Untuk memberi gambaran yang lebih jelas tentang transisi ini, berikut adalah beberapa pola teknis utama yang saya terapkan selama sprint 4 hari tersebut.

Perlu dicatat bahwa contoh-contoh ini berfokus pada arsitektur, logika bisnis, dan kompatibilitas lintas platform. Kode ini memberikan gambaran umum tentang bagaimana KMP diimplementasikan dan tidak mencerminkan isi internal proyek aslinya.

Hari 1: Manajemen Dependency dengan Version Catalogs

Pindah ke KMP adalah momen yang tepat untuk menyentralisasi manajemen dependency. Saya menggunakan libs.versions.toml untuk memastikan target Android dan iOS tetap sinkron dengan versi library yang sama.

[versions]
kotlin = "2.0.0"
ktor = "2.3.11"
koin = "3.5.6"

[libraries]
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
koin-core = { module = "io.insert-koin:koin-core", version.ref = "koin" }

Hari 2: Stack Jaringan Multiplatform (Ktor)

Inti dari migrasi data layer adalah beralih dari Retrofit ke Ktor. Di commonMain, saya menyiapkan HttpClient bersama yang secara otomatis memilih engine yang tepat berdasarkan platform yang dijalankan.

// commonMain/src/kotlin/network/NetworkClient.kt
fun createHttpClient(engine: HttpClientEngine) = HttpClient(engine) {
    install(ContentNegotiation) {
        json(Json {
            ignoreUnknownKeys = true
            prettyPrint = true
            isLenient = true
        })
    }
    install(Logging) {
        level = LogLevel.ALL
    }
}

Hari 3: Abstraksi Spesifik Platform (Expect/Actual)

Setiap kali saya menemukan logika yang membutuhkan API platform tertentu (seperti membuat UUID atau mengakses penyimpanan lokal), saya menggunakan pola expect/actual agar logika di commonMain tetap bersih.

// commonMain/src/kotlin/util/Platform.kt
expect class Platform() {
    val name: String
    fun logSystemInfo()
}

// androidMain/src/kotlin/util/Platform.kt
actual class Platform {
    actual val name: String = "Android ${android.os.Build.VERSION.SDK_INT}"
    actual fun logSystemInfo() {
        Log.d("Platform", "Running on $name")
    }
}

// iosMain/src/kotlin/util/Platform.kt
actual class Platform {
    actual val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
    actual fun logSystemInfo() {
        println("Running on $name")
    }
}

Hari 4: Mengekspos Logika ke Swift

Salah satu bagian paling memuaskan adalah melihat Repository yang ada di shared module dikonsumsi oleh SwiftUI. Dengan menggunakan build .xcframework, kode Kotlin tersebut menjadi library yang terasa “native” bagi sisi iOS.

// SwiftUI View Component
import Shared

class ProfileViewModel: ObservableObject {
    @Published var userData: User? = nil
    private let repository = UserRepository() // Shared Kotlin Repository

    func loadUser() async {
        do {
            let result = try await repository.fetchUserProfile()
            DispatchQueue.main.async {
                self.userData = result
            }
        } catch {
            print("Error loading user: \(error)")
        }
    }
}

Dan selesai. Aplikasi Android native kita kini sudah bisa berjalan di iOS juga.

KMP implementation details

The X Factor: AI-Assisted Planning

“Faktor X” sebenarnya yang membuat timeline 4 hari ini berhasil adalah bagaimana saya mengintegrasikan AI ke dalam alur kerja. Alih-alih hanya menyuruh AI menulis kode, saya menggunakannya dalam “Plan Mode” untuk mengiterasi strategi refactor sebelum eksekusi dimulai. Dengan mendefinisikan spesifikasi teknis lebih awal, saya menghemat berjam-jam potensi masalah.

Saya menggunakan AI untuk membantu memetakan batas-batas modul dan kemudian mengandalkan model yang lebih hemat biaya untuk menangani pekerjaan boilerplate yang repetitif setelah fondasi arsitekturnya solid. Contohnya untuk hal yang perlu pemikiran mendalam seperti strategi refactoring saya akan menggunakan Claude Opus 4.7, sedangkan untuk hal yang repetitif seperti membuat kode boilerplate saya akan menggunakan AI yang lebih hemat biaya seperti Claude Haiku 4.5.

Mengingat model harga langganan AI yang baru saja berubah (contohnya GitHub Copilot, lihat di sini dan di sini), ini bisa menjadi cara paling efisien biaya untuk mengurangi penggunaan token tanpa mengorbankan kualitas output.

Refleksi Perjalanan

Setelah merefleksikan proses ini, saya menyadari bahwa refactoring yang sukses bukan sekadar soal adu cepat mengejar output, melainkan tentang kematangan rencana dari task tersebut dan handling edge cases dari jauh-jauh hari sebelum mulai inisiasi proyek.

Membangun aplikasi mungkin terasa makin mudah dengan bantuan AI dalam penulisan kode, namun ada satu hal yang belum bisa disentuh teknologi: ambient context atau konteks manusiawi yang melatarbelakangi sebuah sistem. Inilah alasan mengapa perencanaan strategis harus tetap datang dari kita.

Di tulisan berikutnya, saya akan mengulas lebih dalam mengapa software engineering is not dead dan bagaimana peran vital manusia dengan mindset overthinker tetap bertahan di tengah kecerdasan AI yang terus berevolusi.

Jika kalian ingin mencoba ini, prioritaskan library yang “KMP-First” seperti Koin untuk dependency injection atau Ktor untuk networking.

Yang terpenting, ingatlah untuk menangani Coroutines dengan hati-hati. Dispatcher Android tidak selalu berperilaku sama di iOS, jadi menggunakan wrapper yang bersih untuk async/await Swift sangat penting untuk hasil akhir yang profesional.

Selain itu, jangan takut untuk melakukan modularisasi lebih awal; memisahkan logika data, domain, dan fitur akan menghindarkan kalian dari “shared module” yang berantakan di kemudian hari.

— Muhammad Tantowi Jauhari - LinkedIn - Whatsapp