Back to blog
December 11, 2025
5 min read

Kotlin Multiplatform Mobile (KMM)

Learn how to share code across Android and iOS platforms

Kotlin Multiplatform Mobile (KMM)

Kotlin Multiplatform Mobile (KMM) allows you to share code between Android and iOS platforms while maintaining native UI and platform-specific features. Let’s explore how to build cross-platform mobile applications with KMM.

Project Setup

Add Dependencies

// build.gradle.kts
plugins {
    kotlin("multiplatform") version "1.8.0"
    kotlin("native.cocoapods") version "1.8.0"
    id("com.android.library")
}

kotlin {
    android()
    ios()

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
                implementation("io.ktor:ktor-client-core:2.3.0")
                implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
            }
        }

        val androidMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-android:2.3.0")
            }
        }

        val iosMain by getting {
            dependencies {
                implementation("io.ktor:ktor-client-darwin:2.3.0")
            }
        }
    }
}

Shared Code Structure

Common Module

// commonMain/kotlin/com/example/shared/Repository.kt
class Repository {
    suspend fun getData(): List<Item> {
        return api.getItems()
    }

    suspend fun saveData(item: Item) {
        api.saveItem(item)
    }
}

// commonMain/kotlin/com/example/shared/Item.kt
@Serializable
data class Item(
    val id: Int,
    val title: String,
    val description: String
)

Platform-Specific Code

// commonMain/kotlin/com/example/shared/Platform.kt
expect class Platform() {
    val platform: String
}

// androidMain/kotlin/com/example/shared/Platform.kt
actual class Platform actual constructor() {
    actual val platform: String = "Android ${android.os.Build.VERSION.SDK_INT}"
}

// iosMain/kotlin/com/example/shared/Platform.kt
actual class Platform actual constructor() {
    actual val platform: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
}

Network Layer

Common Network Code

// commonMain/kotlin/com/example/shared/network/Api.kt
interface Api {
    suspend fun getItems(): List<Item>
    suspend fun saveItem(item: Item)
}

// commonMain/kotlin/com/example/shared/network/HttpClient.kt
expect class HttpClient() {
    fun create(): io.ktor.client.HttpClient
}

// androidMain/kotlin/com/example/shared/network/HttpClient.kt
actual class HttpClient actual constructor() {
    actual fun create(): io.ktor.client.HttpClient {
        return io.ktor.client.HttpClient(Android) {
            install(JsonFeature) {
                serializer = KotlinxSerializer()
            }
        }
    }
}

// iosMain/kotlin/com/example/shared/network/HttpClient.kt
actual class HttpClient actual constructor() {
    actual fun create(): io.ktor.client.HttpClient {
        return io.ktor.client.HttpClient(Darwin) {
            install(JsonFeature) {
                serializer = KotlinxSerializer()
            }
        }
    }
}

Database Layer

Common Database Code

// commonMain/kotlin/com/example/shared/database/Database.kt
expect class Database() {
    fun create(): Database
    suspend fun insertItem(item: Item)
    suspend fun getItems(): List<Item>
}

// androidMain/kotlin/com/example/shared/database/Database.kt
actual class Database actual constructor() {
    actual fun create(): Database {
        return Room.databaseBuilder(
            context,
            AppDatabase::class.java,
            "app_database"
        ).build()
    }

    actual suspend fun insertItem(item: Item) {
        database.itemDao().insert(item)
    }

    actual suspend fun getItems(): List<Item> {
        return database.itemDao().getAll()
    }
}

// iosMain/kotlin/com/example/shared/database/Database.kt
actual class Database actual constructor() {
    actual fun create(): Database {
        return Database(databasePath)
    }

    actual suspend fun insertItem(item: Item) {
        database.insert(item)
    }

    actual suspend fun getItems(): List<Item> {
        return database.getAll()
    }
}

UI Integration

Android Integration

// androidMain/kotlin/com/example/android/MainActivity.kt
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        viewModel.items.observe(this) { items ->
            // Update UI
        }
    }
}

// androidMain/kotlin/com/example/android/MainViewModel.kt
class MainViewModel : ViewModel() {
    private val repository = Repository()
    private val _items = MutableLiveData<List<Item>>()
    val items: LiveData<List<Item>> = _items

    fun loadItems() {
        viewModelScope.launch {
            _items.value = repository.getData()
        }
    }
}

iOS Integration

// iosApp/ContentView.swift
struct ContentView: View {
    @StateObject private var viewModel = MainViewModel()

    var body: some View {
        List(viewModel.items) { item in
            ItemRow(item: item)
        }
        .onAppear {
            viewModel.loadItems()
        }
    }
}

// iosApp/MainViewModel.swift
class MainViewModel: ObservableObject {
    private let repository = Repository()
    @Published var items: [Item] = []

    func loadItems() {
        Task {
            items = try await repository.getData()
        }
    }
}

Dependency Injection

Common DI Setup

// commonMain/kotlin/com/example/shared/di/CommonModule.kt
object CommonModule {
    fun provideRepository(api: Api, database: Database): Repository {
        return Repository(api, database)
    }
}

// androidMain/kotlin/com/example/android/di/AndroidModule.kt
object AndroidModule {
    fun provideApi(): Api {
        return ApiImpl(HttpClient().create())
    }

    fun provideDatabase(): Database {
        return Database().create()
    }
}

// iosMain/kotlin/com/example/ios/di/IosModule.kt
object IosModule {
    fun provideApi(): Api {
        return ApiImpl(HttpClient().create())
    }

    fun provideDatabase(): Database {
        return Database().create()
    }
}

Testing

Common Tests

// commonTest/kotlin/com/example/shared/RepositoryTest.kt
class RepositoryTest {
    private lateinit var repository: Repository
    private lateinit var api: Api
    private lateinit var database: Database

    @BeforeTest
    fun setup() {
        api = mockk()
        database = mockk()
        repository = Repository(api, database)
    }

    @Test
    fun `test get data`() = runTest {
        // Given
        val items = listOf(Item(1, "Test", "Description"))
        coEvery { api.getItems() } returns items

        // When
        val result = repository.getData()

        // Then
        assertEquals(items, result)
    }
}

Best Practices

Code Organization

// commonMain/kotlin/com/example/shared/domain/UseCase.kt
class GetItemsUseCase(private val repository: Repository) {
    suspend operator fun invoke(): List<Item> {
        return repository.getData()
    }
}

// commonMain/kotlin/com/example/shared/domain/Repository.kt
interface Repository {
    suspend fun getData(): List<Item>
    suspend fun saveData(item: Item)
}

// commonMain/kotlin/com/example/shared/data/RepositoryImpl.kt
class RepositoryImpl(
    private val api: Api,
    private val database: Database
) : Repository {
    override suspend fun getData(): List<Item> {
        return try {
            api.getItems()
        } catch (e: Exception) {
            database.getItems()
        }
    }
}

Conclusion

KMM helps you:

  • Share code between platforms
  • Maintain native UI
  • Reduce development time
  • Keep platform-specific features

Remember:

  • Follow clean architecture
  • Use proper dependency injection
  • Write comprehensive tests
  • Handle platform differences

Stay tuned for our next post about Top Kotlin IDE Shortcuts!