Kotlin Multiplatform for Android Developers: Sharing Code Without Losing Native Quality

Move business logic, networking, and data layers to shared Kotlin while keeping native Android and iOS UIs.

Introduction

Kotlin Multiplatform enables code sharing between Android and iOS while maintaining native UI quality. Share networking, data models, and business logic. Keep platform-specific UIs for best user experience. This guide covers practical KMP adoption.

What to Share and What to Keep Native

Share: data models, networking, database logic, business rules, analytics. Keep native: UI, platform integrations (notifications, biometrics), complex animations. Aim for 40-60% code sharing.

Setting Up KMP Project

Configure shared module with Kotlin Multiplatform plugin. Set up Android and iOS targets. Use expect/actual for platform-specific implementations. Configure Gradle for shared test sources.

Sharing Data Layer

Use SQLDelight for shared database code. Use Ktor or shared Retrofit interfaces for networking. Serialize models with Kotlin Serialization. Share repository implementations.

iOS Integration Patterns

Expose shared code as Swift framework. Use coroutines with Kotlin/Native. Handle threading with Dispatchers. Convert Kotlin collections to Swift arrays.

Frequently Asked Questions

Does KMP increase build complexity?

Initially yes, but the complexity is manageable with proper setup. Benefits outweigh complexity for teams maintaining both Android and iOS apps.

Can I migrate incrementally?

Yes. Start with data models, then networking, then business logic. Each layer can be migrated independently. Keep Android app fully functional throughout migration.

SYSONLINE
SDK36 · MIN 24
KOTLIN2.0
UTC07:38:15
UP00:01
MOD · ARTICLE · KOTLINS/N · AX-KOTLINPUBLISHED
KotlinFeb 20, 202612 MIN

Kotlin Multiplatform for Android Developers: Sharing Code Without Losing Native Quality

Move business logic, networking, and data layers to a shared Kotlin module while keeping native Android and iOS UIs. KMP patterns for teams shipping Android.

By Rocky Elsalaymeh · Founder & Principal Consultant, Strategia-X

Why KMP and Why Now

Kotlin Multiplatform has moved from experimental to stable, and Google officially recommends it for sharing business logic across Android and iOS. If you already have a Kotlin-first Android codebase, KMP lets you extract domain logic, data models, and networking into a shared module that both platforms consume. The critical distinction from other cross-platform solutions: KMP does not replace your UI layer. Your Android app stays Jetpack Compose, your iOS app stays SwiftUI. You share the parts that should be identical -- validation rules, API contracts, caching logic, analytics events -- and keep the parts that should be native. For Android developers, the learning curve is almost flat. You already write Kotlin. KMP just gives you new targets. The getting started guide walks through setup in under an hour, and Compose Multiplatform extends shared UI to desktop and web if needed.

Project Structure: The expect/actual Pattern

A KMP project has three source sets: commonMain (shared code), androidMain (Android-specific implementations), and iosMain (iOS-specific implementations). The expect/actual mechanism lets you declare an interface in common code and provide platform implementations. The shared module produces an Android library (.aar) and an iOS framework (.framework). Your Android app depends on the shared module like any other Gradle dependency. The iOS app embeds the framework via CocoaPods or SPM.
kotlin
// shared/src/commonMain/kotlin/Platform.kt
expect class PlatformContext

expect fun getPlatformName(): String

// shared/src/androidMain/kotlin/Platform.android.kt
actual typealias PlatformContext = android.content.Context

actual fun getPlatformName(): String = "Android"

// shared/src/iosMain/kotlin/Platform.ios.kt
import platform.UIKit.UIDevice

actual class PlatformContext

actual fun getPlatformName(): String =
    UIDevice.currentDevice.systemName()

Sharing Networking with Ktor

Ktor is the de facto HTTP client for KMP. Define your API client in commonMain and configure platform-specific engines in each target. The serialization layer uses kotlinx.serialization, which works identically on both platforms. Your Android app was probably using Retrofit. The migration path is straightforward: extract your API response models to the shared module, replace Retrofit interfaces with Ktor client calls, and keep your repository layer in common code.
kotlin
// shared/src/commonMain/kotlin/api/TaskApi.kt
class TaskApi(private val client: HttpClient) {

    suspend fun getTasks(): List<TaskDto> =
        client.get("https://api.example.com/tasks")
            .body()

    suspend fun createTask(request: CreateTaskRequest): TaskDto =
        client.post("https://api.example.com/tasks") {
            contentType(ContentType.Application.Json)
            setBody(request)
        }.body()
}

// shared/src/commonMain/kotlin/api/HttpClientFactory.kt
expect fun createPlatformHttpClient(): HttpClient

// shared/src/androidMain/kotlin/api/HttpClientFactory.android.kt
actual fun createPlatformHttpClient(): HttpClient =
    HttpClient(OkHttp) {
        install(ContentNegotiation) {
            json(Json { ignoreUnknownKeys = true })
        }
        install(Logging) {
            level = LogLevel.HEADERS
        }
    }

Shared Data Layer with SQLDelight

SQLDelight generates type-safe Kotlin APIs from SQL statements and supports multiplatform. You write .sq files with plain SQL, and SQLDelight generates models and query functions that work on both Android (SQLite via AndroidX) and iOS (SQLite via native driver). For Android developers used to Room, the mental model is similar: you define your schema and queries, and the library generates the boilerplate. The difference is that SQLDelight starts from SQL rather than annotated Kotlin classes.
sql
-- shared/src/commonMain/sqldelight/com/app/db/Task.sq
CREATE TABLE TaskEntity (
    id TEXT NOT NULL PRIMARY KEY,
    title TEXT NOT NULL,
    description TEXT NOT NULL DEFAULT '',
    isCompleted INTEGER AS Boolean NOT NULL DEFAULT 0,
    createdAt INTEGER NOT NULL,
    updatedAt INTEGER NOT NULL
);

selectAll:
SELECT * FROM TaskEntity
ORDER BY createdAt DESC;

insertOrReplace:
INSERT OR REPLACE INTO TaskEntity(
    id, title, description, isCompleted, createdAt, updatedAt
) VALUES (?, ?, ?, ?, ?, ?);

markCompleted:
UPDATE TaskEntity SET isCompleted = 1,
    updatedAt = ? WHERE id = ?;

deleteById:
DELETE FROM TaskEntity WHERE id = ?;

Testing Shared Code

One of KMP's strongest advantages is that you write tests once in commonTest and they run on all platforms. Your business logic, validation rules, and data transformations get verified against both the JVM and iOS native runtimes. Use kotlin.test for assertions -- it maps to JUnit on Android and XCTest on iOS. Mock your platform dependencies with interfaces and fakes, keeping the shared test suite pure and fast.
kotlin
// shared/src/commonTest/kotlin/TaskRepositoryTest.kt
class TaskRepositoryTest {

    private val fakeApi = FakeTaskApi()
    private val fakeDb = FakeTaskDatabase()
    private val repository = TaskRepository(fakeApi, fakeDb)

    @Test
    fun fetchTasks_cachesLocally() = runTest {
        fakeApi.respondWith(
            listOf(TaskDto("1", "Write tests", false))
        )

        val tasks = repository.getTasks()

        assertEquals(1, tasks.size)
        assertEquals("Write tests", tasks.first().title)
        // Verify it was cached in the database
        assertEquals(1, fakeDb.getAllTasks().size)
    }

    @Test
    fun createTask_validatesTitle() = runTest {
        val result = repository.createTask(title = "")

        assertTrue(result.isFailure)
        assertEquals(
            "Title cannot be empty",
            result.exceptionOrNull()?.message
        )
    }
}

Incremental Migration Strategy

You don't need to migrate everything at once. Start with the lowest-risk, highest-value layer: **Phase 1: Data models and DTOs.** Move your API response models and database entities to the shared module. Zero behavior change, pure structural sharing. **Phase 2: Business logic and validation.** Extract validation rules, formatters, and computation functions. These have no platform dependencies and are easy to test. **Phase 3: Repository layer.** Move your repository implementations to shared code, abstracting platform-specific storage behind expect/actual. **Phase 4: Networking.** Replace Retrofit with Ktor in the shared module. This is the most impactful change but also the most disruptive -- save it for when the team is comfortable with KMP. Keep your ViewModels platform-specific. Android ViewModels use the AndroidX lifecycle; iOS uses ObservableObject. Both consume the same shared repository.
MOD · TAKEAWAYS6 POINTSSUMMARY

Key Takeaways

  1. 1KMP shares business logic and data layers, not UI -- your Android and iOS apps stay fully native.
  2. 2The expect/actual pattern provides clean platform abstraction without runtime overhead.
  3. 3Ktor and SQLDelight are the standard KMP libraries for networking and local persistence.
  4. 4Write tests once in commonTest and run them on all platforms for maximum coverage.
  5. 5Migrate incrementally: data models first, then business logic, then repositories, then networking.
  6. 6Keep ViewModels platform-specific -- they bridge shared logic to platform-native UI lifecycles.
MOD · FAQ2 ENTRIESANSWERED

Frequently Asked

Does KMP increase build complexity?

Initially yes, but the complexity is manageable with proper setup. Benefits outweigh complexity for teams maintaining both Android and iOS apps.

Can I migrate incrementally?

Yes. Start with data models, then networking, then business logic. Each layer can be migrated independently. Keep Android app fully functional throughout migration.

MOD · BUILD · NEXTS/N · AX-CTA-0001READY

Ready to architect your next Android app?

ANDROID-ARCHITECT generates production-ready Kotlin code, architecture blueprints, and CI/CD configurations from plain-language descriptions. Start building for free.

ANDROID-ARCHITECT · CONSOLE
S/N · AX-1A-00001
STRATEGIA-X · ENGINEERED · IN · CALIFORNIA