Hana — VyuApp Support
Online
20/20 pesan tersisa
🌸 Selamat datang di VyuApp! Saya Hana, ada yang bisa saya bantu hari ini?
Semua artikel

Engineering

Kotlin Multiplatform Shared Business Logic Tutorial: Panduan Lengkap 2026

Pelajari cara berbagi business logic antar platform menggunakan Kotlin Multiplatform (KMP). Tutorial lengkap mencakup arsitektur expect/actual, Ktor untuk networking, SQLDelight untuk database, dan Ko

1 Juli 2026 12 min read#KOTLIN MULTIPLATFORM#KMP#CROSS-PLATFORM#MOBILE DEVELOPMENT
Kotlin Multiplatform Shared Business Logic Tutorial: Panduan Lengkap 2026

Pelajari cara berbagi business logic antar platform menggunakan Kotlin Multiplatform (KMP). Tutorial lengkap mencakup arsitektur expect/actual, Ktor untuk networking, SQLDelight untuk database, dan Koin untuk dependency injection.

KOTLIN MULTIPLATFORM KMP CROSS-PLATFORM MOBILE DEVELOPMENT BUSINESS LOGIC
Layar komputer menampilkan kode pemrograman Kotlin untuk pengembangan lintas platform

Apa Itu Kotlin Multiplatform?

Kotlin Multiplatform (KMP) adalah teknologi dari JetBrains yang memungkinkan developer menulis logika bisnis sekali dan menjalankannya di berbagai platform—Android, iOS, web, dan desktop. Berbeda dengan Flutter atau React Native yang mengharuskan menggunakan UI framework tertentu, solusi ini fokus pada berbagi business logic sambil tetap mempertahankan native UI di masing-masing platform.

Pada tahun 2026, teknologi tersebut telah mencapai status production-ready dengan adopsi oleh perusahaan besar seperti Netflix, Airbnb, dan VMware. Google secara resmi mendukung KMP untuk Android, menjadikannya salah satu pendekatan cross-platform paling menjanjikan saat ini. Menurut survei JetBrains 2025, lebih dari 60% developer Kotlin sudah menggunakan atau berencana menggunakan KMP dalam proyek mereka.

Mengapa KMP Berbeda?

Pendekatan ini memecahkan dilema utama dalam cross-platform development: trade-off antara code reuse dan pengalaman pengguna native. Dengan berbagi hanya non-UI code, developer bisa mencapai code reuse hingga 70-80% tanpa mengorbankan native look and feel di setiap platform.

Ekosistem KMP juga semakin matang dengan dukungan library populer seperti Ktor untuk networking, SQLDelight untuk penyimpanan lokal, dan Koin untuk dependency injection. Semua komponen tersebut bekerja secara native di shared module tanpa memerlukan bridge atau wrapper tambahan.

Bagi tim yang ingin mempelajari pendekatan pengembangan modern lainnya, baca juga panduan kami tentang Clean Architecture untuk Android yang membahas pemisahan layer secara mendalam.

Arsitektur KMP: Shared Module Structure

Arsitektur KMP menggunakan konsep source sets untuk memisahkan kode shared dan platform-specific. Struktur standar project KMP terdiri dari beberapa source set utama yang masing-masing memiliki peran spesifik dalam ekosistem aplikasi.

  • commonMain — Business logic, data models, dan use cases yang shared ke semua platform target
  • androidMain — Implementasi platform-specific untuk Android menggunakan Android SDK
  • iosMain — Implementasi platform-specific untuk iOS menggunakan Objective-C interop
  • desktopMain — Implementasi untuk desktop menggunakan Java/JVM (opsional)
  • wasmJsMain — Implementasi untuk WebAssembly target (eksperimental)
Workspace pengembang dengan layar monitor menampilkan kode Kotlin Multiplatform

Struktur Gradle Build

Setiap source set dikonfigurasi melalui file build.gradle.kts. Berikut contoh konfigurasi dasar untuk project KMP:

// build.gradle.kts (shared module)
kotlin {
    androidTarget {
        compilations.all {
            kotlinOptions {
                jvmTarget = "17"
            }
        }
    }
    
    listOf(
        iosX64(),
        iosArm64(),
        iosSimulatorArm64()
    ).forEach { iosTarget ->
        iosTarget.binaries.framework {
            baseName = "Shared"
            isStatic = true
        }
    }
    
    sourceSets {
        commonMain.dependencies {
            implementation("io.ktor:ktor-client-core:3.0.3")
            implementation("io.ktor:ktor-client-content-negotiation:3.0.3")
            implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
            implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
            implementation("app.cash.sqldelight:runtime:2.0.2")
            implementation("io.insert-koin:koin-core:4.0.0")
        }
    }
}

Perhatikan bagaimana dependencies didefinisikan di commonMain. Library seperti Ktor dan SQLDelight akan secara otomatis menggunakan implementasi platform-specific saat di-compile untuk target masing-masing.

Expect/Actual Pattern: Jantung KMP

Pola expect/actual adalah mekanisme utama dalam KMP untuk mengimplementasikan kode platform-specific. Di commonMain, Anda mendeklarasikan expect class atau function, kemudian menyediakan implementasi actual di masing-masing platform.

Mekanisme ini memungkinkan shared module mengakses fungsionalitas platform tanpa mengetahui detail implementasinya. Compiler Kotlin akan memverifikasi bahwa setiap deklarasi expect memiliki pasangan actual di setiap target platform yang dikonfigurasi.

// commonMain - Deklarasi expect
expect class PlatformSpecificHandler() {
    fun getPlatformName(): String
    fun performPlatformAction()
}

// androidMain - Implementasi actual untuk Android
actual class PlatformSpecificHandler {
    actual fun getPlatformName(): String = 
        "Android ${android.os.Build.VERSION.SDK_INT}"
    actual fun performPlatformAction() {
        // Android-specific: misalnya akses ke WorkManager
    }
}

// iosMain - Implementasi actual untuk iOS
actual class PlatformSpecificHandler {
    actual fun getPlatformName(): String = 
        "iOS ${UIDevice.currentDevice.systemVersion}"
    actual fun performPlatformAction() {
        // iOS-specific: misalnya akses ke Core Data
    }
}
Tips Penggunaan Expect/Actual

Gunakan expect/actual hanya untuk fungsionalitas yang benar-benar berbeda antar platform. Untuk logika yang bisa di-share sepenuhnya, cukup tulis di commonMain tanpa expect/actual. Pola ini paling cocok untuk akses ke hardware, platform API, dan library native.

Sharing Business Logic dengan Clean Architecture

Pola terbaik untuk proyek KMP adalah menggunakan Clean Architecture dengan pembagian layer yang jelas. Pendekatan ini memastikan business logic tetap terisolasi dari detail implementasi platform, sehingga mudah di-test dan di-maintain.

// Advertisement

Data Layer (Shared)

Data layer bertanggung jawab atas akses data dari berbagai sumber—API remote, database lokal, atau cache. Semua kode di layer ini hidup di commonMain dan menggunakan library cross-platform seperti Ktor serta SQLDelight.

// commonMain - Data Model dengan kotlinx.serialization
@Serializable
data class User(
    val id: String,
    val name: String,
    val email: String,
    val createdAt: Long = Clock.System.now().epochSeconds
)

// commonMain - Repository Interface
interface UserRepository {
    suspend fun getUsers(): Result<List<User>>
    suspend fun getUserById(id: String): Result<User>
    suspend fun saveUser(user: User): Result<Unit>
    suspend fun deleteUser(id: String): Result<Unit>
}

Domain Layer (Shared)

Domain layer berisi use cases yang merepresentasikan operasi bisnis. Layer ini tidak bergantung pada framework apapun dan bisa di-test secara independen di commonTest.

// commonMain - Use Case
class GetUsersUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(): Result<List<User>> {
        return repository.getUsers()
    }
}

class GetUserDetailUseCase(
    private val repository: UserRepository
) {
    suspend operator fun invoke(userId: String): Result<User> {
        if (userId.isBlank()) {
            return Result.failure(
                IllegalArgumentException("User ID tidak boleh kosong")
            )
        }
        return repository.getUserById(userId)
    }
}
Visualisasi alur data lintas platform dalam arsitektur Clean Architecture

Presentation Layer (Platform-Specific)

Layer presentasi menggunakan framework UI native masing-masing platform. Di Android, Anda bisa menggunakan Jetpack Compose; di iOS, SwiftUI atau UIKit. ViewModel atau Presenter di layer ini memanggil use cases dari domain layer.

// androidMain - ViewModel dengan use case dari shared module
class UserViewModel(
    private val getUsersUseCase: GetUsersUseCase
) : ViewModel() {
    
    private val _uiState = MutableStateFlow<UserUiState>(UserUiState.Loading)
    val uiState: StateFlow<UserUiState> = _uiState.asStateFlow()
    
    fun loadUsers() {
        viewModelScope.launch {
            _uiState.value = UserUiState.Loading
            getUsersUseCase()
                .onSuccess { users ->
                    _uiState.value = UserUiState.Success(users)
                }
                .onFailure { error ->
                    _uiState.value = UserUiState.Error(error.message)
                }
        }
    }
}

Untuk pembahasan lebih lanjut tentang arsitektur pengembangan mobile, lihat juga artikel kami tentang perbandingan pendekatan cross-platform development 2026.

Ekosistem Library KMP: Ktor, SQLDelight, dan Koin

Keunggulan utama Kotlin Multiplatform terletak pada ekosistem library yang matang dan saling melengkapi. Tiga library utama—Ktor untuk networking, SQLDelight untuk penyimpanan lokal, dan Koin untuk dependency injection—membentuk fondasi yang solid untuk membangun aplikasi cross-platform production-ready.

Networking dengan Ktor Client

Ktor Client adalah HTTP client cross-platform yang memungkinkan network requests dari shared code. Library ini mendukung fitur modern seperti content negotiation, logging, dan retry mechanism secara native di semua platform.

// commonMain - Network Client Setup
val httpClient = HttpClient {
    install(ContentNegotiation) {
        json(Json {
            ignoreUnknownKeys = true
            isLenient = true
            encodeDefaults = true
        })
    }
    install(Logging) {
        level = LogLevel.BODY
        logger = object : Logger {
            override fun log(message: String) {
                println("Ktor: $message")
            }
        }
    }
    install(HttpRequestRetry) {
        maxRetries = 3
        retryOnServerErrors(delayMillis = 1000L)
        exponentialDelay()
    }
}

// commonMain - API Service Implementation
class ApiService(private val client: HttpClient) {
    
    suspend fun fetchUsers(): List<User> {
        return client.get("https://api.example.com/users") {
            header("Authorization", "Bearer ${getAuthToken()}")
        }.body()
    }
    
    suspend fun createUser(user: User): User {
        return client.post("https://api.example.com/users") {
            contentType(ContentType.Application.Json)
            setBody(user)
        }.body()
    }
    
    suspend fun deleteUser(id: String): Boolean {
        val response = client.delete("https://api.example.com/users/$id")
        return response.status.isSuccess()
    }
}

Konfigurasi di atas berlaku untuk semua platform. Ktor secara otomatis menggunakan engine yang sesuai—OkHttp untuk Android, Darwin untuk iOS, dan CIO untuk desktop atau server-side.

Local Storage dengan SQLDelight

SQLDelight memungkinkan penyimpanan data lokal dengan type-safe SQL queries yang shared ke semua platform. Library ini meng-generate Kotlin code dari file SQL, sehingga Anda mendapatkan compile-time safety untuk semua query database.

-- commonMain - schema.sq
CREATE TABLE User (
    id TEXT NOT NULL PRIMARY KEY,
    name TEXT NOT NULL,
    email TEXT NOT NULL,
    created_at INTEGER NOT NULL DEFAULT 0
);

getAllUsers:
SELECT * FROM User ORDER BY name ASC;

getUserById:
SELECT * FROM User WHERE id = ?;

insertUser:
INSERT OR REPLACE INTO User (id, name, email, created_at) 
VALUES (?, ?, ?, ?);

deleteUser:
DELETE FROM User WHERE id = ?;

searchUsers:
SELECT * FROM User WHERE name LIKE '%' || ? || '%';

Setelah file SQL di-define, SQLDelight meng-generate class AppDatabase dengan method type-safe untuk setiap query. Berikut implementasi repository yang menggabungkan data dari API dan database lokal:

// commonMain - Repository dengan offline-first strategy
class UserRepositoryImpl(
    private val apiService: ApiService,
    private val database: AppDatabase
) : UserRepository {
    
    override suspend fun getUsers(): Result<List<User>> {
        return try {
            val remoteUsers = apiService.fetchUsers()
            database.transaction {
                remoteUsers.forEach { user ->
                    database.appDatabaseQueries.insertUser(
                        user.id, user.name, user.email, user.createdAt
                    )
                }
            }
            Result.success(remoteUsers)
        } catch (e: Exception) {
            // Fallback ke data lokal jika network gagal
            val localUsers = database.appDatabaseQueries
                .getAllUsers()
                .executeAsList()
                .map { User(it.id, it.name, it.email, it.created_at) }
            if (localUsers.isNotEmpty()) {
                Result.success(localUsers)
            } else {
                Result.failure(e)
            }
        }
    }
    
    override suspend fun getUserById(id: String): Result<User> {
        val localUser = database.appDatabaseQueries
            .getUserById(id)
            .executeAsOneOrNull()
        
        return if (localUser != null) {
            Result.success(User(localUser.id, localUser.name, localUser.email, localUser.created_at))
        } else {
            try {
                val remoteUser = apiService.fetchUsers().first { it.id == id }
                database.appDatabaseQueries.insertUser(
                    remoteUser.id, remoteUser.name, remoteUser.email, remoteUser.createdAt
                )
                Result.success(remoteUser)
            } catch (e: Exception) {
                Result.failure(e)
            }
        }
    }
    
    override suspend fun saveUser(user: User): Result<Unit> {
        return try {
            apiService.createUser(user)
            database.appDatabaseQueries.insertUser(
                user.id, user.name, user.email, user.createdAt
            )
            Result.success(Unit)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    override suspend fun deleteUser(id: String): Result<Unit> {
        return try {
            apiService.deleteUser(id)
            database.appDatabaseQueries.deleteUser(id)
            Result.success(Unit)
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}
Robot otomatis yang merepresentasikan proses sinkronisasi data lintas platform

// Advertisement

Dependency Injection dengan Koin

Koin adalah dependency injection framework yang mendukung KMP secara native. Library ini menggunakan pendekatan DSL (Domain-Specific Language) yang ringan dan tidak memerlukan code generation—sangat cocok untuk shared module di KMP.

// commonMain - Module Definition
val sharedModule = module {
    // Networking
    single { 
        HttpClient {
            install(ContentNegotiation) {
                json(Json { ignoreUnknownKeys = true })
            }
        } 
    }
    single { ApiService(get()) }
    
    // Database
    single { createAppDatabase(get()) }
    
    // Repository
    single<UserRepository> { UserRepositoryImpl(get(), get()) }
    
    // Use Cases
    factory { GetUsersUseCase(get()) }
    factory { GetUserDetailUseCase(get()) }
}

// commonMain - Koin Initialization
fun initKoin(): KoinApplication {
    return startKoin {
        modules(sharedModule)
    }
}

Setiap platform kemudian menginisialisasi Koin dengan cara yang sesuai:

// androidMain - Android Application
class MyApplication : Application() {
    override fun onCreate() {
        super.onCreate()
        initKoin()
        // Android-specific modules bisa ditambahkan di sini
    }
}

// iosMain - iOS App Entry Point
fun initKoinIOS() {
    initKoin()
    // iOS-specific modules bisa ditambahkan di sini
}

Keunggulan Koin dibanding Hilt atau Dagger adalah tidak adanya proses annotation processing, sehingga build time tetap cepat meskipun project KMP memiliki banyak target platform. Untuk perbandingan lebih detail tentang dependency injection, baca artikel kami tentang dependency injection di Android modern.

Tiga library di atas—Ktor, SQLDelight, dan Koin—sudah terbukti di production oleh perusahaan seperti Netflix dan Airbnb. Kombinasi ketiganya membentuk stack yang lengkap untuk hampir semua kebutuhan pengembangan aplikasi cross-platform.

Best Practices untuk KMP di Production

Menerapkan KMP di production memerlukan perhatian khusus terhadap beberapa aspek teknis. Berikut rekomendasi berdasarkan pengalaman perusahaan yang sudah mengadopsi teknologi ini secara luas:

Matriks Rekomendasi

Area Metrik / Target Tools yang Direkomendasikan
Code Sharing Ratio Targetkan 70-85% kode di commonMain Gradle source set analysis
Unit Test Coverage Minimal 80% untuk shared module Kotlin Test, kotlinx-coroutines-test
Build Time < 3 menit untuk full build Gradle build cache, configuration cache
APK Size Impact Tambahan < 2 MB dari shared module R8/ProGuard, KMP binary stripping
Memory Overhead < 5 MB untuk shared runtime New Native memory model (default sejak Kotlin 2.0)
Startup Time Tambahan < 100ms dari shared init Lazy Koin initialization

Strategi Migrasi Bertahap

Jangan langsung memigrasikan seluruh codebase ke KMP. Gunakan pendekatan bertahap yang dimulai dari layer data:

  1. Fase 1 (Minggu 1-2): Setup shared module dengan data models dan repository interface. Buat struktur Gradle yang benar sebelum menulis kode apapun.
  2. Fase 2 (Minggu 3-4): Implementasikan networking layer dengan Ktor dan local storage dengan SQLDelight. Pastikan kedua layer bisa di-test secara independen.
  3. Fase 3 (Minggu 5-6): Pindahkan use cases ke domain layer di shared module. Refactor ViewModel untuk memanggil use cases dari shared.
  4. Fase 4 (Minggu 7-8): Setup CI/CD pipeline untuk build multi-platform. Tambahkan unit tests dan integration tests di commonTest.

Error Handling yang Konsisten

Gunakan Result type atau sealed class untuk error handling yang konsisten di semua platform. Hindari melempar exception dari shared module—gunakan pattern matching di layer presentasi untuk menangani berbagai jenis error.

// commonMain - Sealed class untuk error handling
sealed class AppError(val message: String) {
    data class Network(val code: Int, val detail: String) : 
        AppError("Network error $code: $detail")
    data class Database(val detail: String) : 
        AppError("Database error: $detail")
    data class Validation(val field: String, val rule: String) : 
        AppError("Validation error on $field: $rule")
    data class Unknown(val throwable: Throwable) : 
        AppError("Unknown error: ${throwable.message}")
}

// Extension function untuk mapping
fun Exception.toAppError(): AppError = when (this) {
    is ResponseException -> AppError.Network(
        response.status.value, message ?: "Unknown"
    )
    is SQLException -> AppError.Database(message ?: "DB Error")
    is IllegalArgumentException -> AppError.Validation("input", message ?: "")
    else -> AppError.Unknown(this)
}

Kesimpulan dan Langkah Selanjutnya

Kotlin Multiplatform pada tahun 2026 telah menjadi solusi yang matang dan production-ready untuk pengembangan lintas platform. Dengan berbagi business logic melalui shared module, tim developer bisa menghemat waktu development hingga 40-60% sambil tetap mempertahankan native UI experience di setiap platform.

Kombinasi Ktor untuk networking, SQLDelight untuk local storage, Koin untuk dependency injection, dan kotlinx.serialization untuk data serialization membuat ekosistem KMP sangat lengkap. Semua komponen tersebut memiliki dokumentasi yang baik dan komunitas aktif yang terus berkembang.

Untuk memulai, gunakan template resmi dari JetBrains atau ikuti tutorial resmi di dokumentasi Kotlin Multiplatform. Mulai dari sharing data layer dan domain layer, lalu evaluasi apakah Compose Multiplatform sesuai untuk kebutuhan UI Anda.

Mulai Sekarang

Untuk memulai dengan KMP, kunjungi Kotlin Multiplatform Documentation atau gunakan template project dari KMP App Template.

Pertanyaan yang Sering Diajukan (FAQ)

Q: Apakah Kotlin Multiplatform cocok untuk project baru di 2026?
Ya, KMP sudah production-ready dan didukung secara resmi oleh JetBrains dan Google. Perusahaan seperti Netflix, Airbnb, dan Cash App sudah menggunakannya di production. Untuk project baru, KMP sangat cocok jika target Anda adalah Android dan iOS dengan UI native.
Q: Berapa persen kode yang bisa di-share antar platform menggunakan KMP?
Tergantung arsitektur aplikasi, tim biasanya berhasil meng-share 70-85% kode bisnis. Sisanya (15-30%) adalah kode platform-specific untuk UI, akses hardware, dan integrasi native. Dengan Clean Architecture, rasio sharing bisa lebih tinggi lagi.
Q: Apakah KMP lebih lambat dibanding native development?
Tidak secara signifikan. Kode di shared module di-compile ke native binary untuk iOS dan JVM bytecode untuk Android. Benchmark menunjukkan overhead runtime kurang dari 5% dibanding kode native murni. Build time juga kompetitif dengan Gradle build cache.
Q: Bagaimana cara testing business logic di shared module?
Gunakan source set commonTest dengan framework seperti kotlin.test. Unit tests di commonTest akan dijalankan di semua platform target. Untuk integration testing, gunakan library seperti Turbine untuk testing Flow dan coroutine-based logic.
Q: Apakah KMP bisa digunakan untuk backend/server-side juga?
Ya! KMP mendukung JVM dan Native target, sehingga Anda bisa menjalankan kode shared di server. Ktor (server framework) juga mendukung KMP. Ini memungkinkan Anda berbagi data models dan validation logic antara client dan server dalam satu shared module.

Sumber Referensi

// Advertisement

V

VyuApp Studio

Bespoke web engineering — Garut, ID