# Kotlin Multiplatform w 2026: wspólny kod Android i iOS > Jak Kotlin Multiplatform współdzieli logikę biznesową między Androidem a iOS w 2026: konfiguracja Gradle, expect/actual, sieć Ktor, interfejs Compose Multiplatform i pytania rekrutacyjne o KMP. - Published: 2026-06-26 - Updated: 2026-07-06 - Author: SharpSkill - Tags: Kotlin Multiplatform, KMP, Android, iOS, Cross-platform - Reading time: 10 min --- Kotlin Multiplatform (KMP) pozwala jednej bazie kodu Kotlin obsługiwać logikę biznesową aplikacji na Androida i iOS, podczas gdy każda platforma zachowuje w pełni natywny interfejs, wydajność oraz dostęp do własnego SDK. W 2026 roku KMP nie jest już eksperymentem: jest stabilny, oficjalnie wspierany przez [Google](https://developer.android.com/kotlin/multiplatform) i działa na produkcji w Netflix, McDonald's, Forbes oraz Cash App. Ten szczegółowy przegląd wyjaśnia, jak faktycznie działa współdzielenie kodu, jak skonfigurować wspólny moduł oraz jakie kompromisy warto zrozumieć przed rozmową kwalifikacyjną dotyczącą KMP. > **Co KMP naprawdę współdzieli** > > Kotlin Multiplatform współdzieli skompilowaną logikę, a nie środowisko uruchomieniowe. Wspólny kod Kotlin kompiluje się do bajtkodu JVM na Androida oraz do natywnego pliku binarnego (poprzez LLVM) na iOS. Nie ma mostu JavaScript, osadzonego webview ani warstwy refleksji, więc współdzielony kod działa z natywną szybkością na obu platformach. ## Jak Kotlin Multiplatform współdzieli kod bez utraty natywnej wydajności Podstawowym mechanizmem jest hierarchia zestawów źródeł (source sets). `commonMain` zawiera kod Kotlin niezależny od platformy: sieć, serializację, reguły biznesowe oraz modele widoku. Zestawy źródeł platform, takie jak `androidMain` i `iosMain`, zawierają kod korzystający z API danej platformy. Kompilator tworzy artefakt JVM z `commonMain` oraz `androidMain` dla Androida, a natywny framework z `commonMain` oraz `iosMain` dla iOS. Kotlin Multiplatform w 2026 roku preferuje współdzielenie tego, co rzeczywiście zyskuje na jednym źródle prawdy: modeli, klientów API, buforowania oraz walidacji. Interfejs użytkownika można współdzielić za pomocą Compose Multiplatform albo pozostawić w pełni natywnym z SwiftUI, ekran po ekranie. Ten model "współdziel logikę, wybierz interfejs" sprawia, że zespoły wdrażają KMP bez przepisywania istniejących aplikacji. Kotlin Multiplatform to technologia Kotlin, która kompiluje jeden współdzielony moduł do wielu celów (Android na JVM, iOS jako natywny, a także desktop i web), dzięki czemu logikę biznesową pisze się raz, a każda platforma zachowuje swój natywny interfejs oraz pełny dostęp do SDK. Google wspiera go teraz jako opcję pierwszej klasy, a biblioteki Jetpack, takie jak Room, DataStore, ViewModel i Paging, oferują artefakty KMP. Decyzja o tym, co należy do współdzielonego modułu, to najważniejszy wybór architektoniczny w projekcie KMP. Poniższa tabela odzwierciedla, jak większość zespołów produkcyjnych wyznacza tę granicę w 2026 roku: wszystko poniżej warstwy prezentacji jest silnym kandydatem do współdzielenia, natomiast to, co dotyka konwencji UX danej platformy, zwykle pozostaje natywne. | Warstwa | Współdzielić w commonMain? | Powód | |-------|---------------------|--------| | Modele danych i DTO | Tak | Identyczny kontrakt API na obu platformach | | Sieć i buforowanie | Tak | Jeden stos HTTP, jedna powierzchnia błędów | | Reguły biznesowe i walidacja | Tak | Domena nie różni się między systemami | | Modele widoku i stan | Zwykle | Compose i SwiftUI korzystają ze współdzielonego stanu | | Renderowanie UI | Opcjonalnie | Współdzielone z Compose lub natywne na każdym ekranie | | API platformy (aparat, biometria) | Nie | Udostępniane zamiast tego przez expect/actual | Zysk rośnie wraz z tym, jak dużą częścią dolnej połowy tej tabeli zarządza aplikacja. Produkty mocno oparte na danych z lekkim interfejsem współdzielą najwięcej; wysoce spersonalizowane, sterowane animacjami aplikacje współdzielą najmniej. Żadna skrajność nie jest błędna, dlatego KMP nadaje się do stopniowego wdrażania, a nie do przepisywania wszystkiego naraz. ## Konfiguracja współdzielonego modułu w Gradle Moduł KMP deklaruje swoje cele i zestawy źródeł w Kotlin DSL. Poniższy `build.gradle.kts` konfiguruje Androida oraz trzy architektury iOS (fizyczne urządzenie, symulator Intel, symulator Apple Silicon) i udostępnia wyjście iOS jako framework o nazwie `Shared`. ```kotlin // shared/build.gradle.kts plugins { kotlin("multiplatform") kotlin("plugin.serialization") id("com.android.library") } kotlin { // Android target compiles commonMain + androidMain to JVM bytecode androidTarget() // Three iOS targets: device, Intel sim, Apple Silicon sim listOf(iosArm64(), iosX64(), iosSimulatorArm64()).forEach { target -> target.binaries.framework { baseName = "Shared" // becomes "import Shared" in Swift isStatic = true } } sourceSets { commonMain.dependencies { implementation("io.ktor:ktor-client-core:3.3.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.2") } androidMain.dependencies { implementation("io.ktor:ktor-client-okhttp:3.3.0") } iosMain.dependencies { implementation("io.ktor:ktor-client-darwin:3.3.0") } } } ``` Każdy cel pobiera silnik Ktor specyficzny dla platformy (`okhttp` na Androidzie, `darwin` na iOS), podczas gdy współdzielony kod zależy wyłącznie od wspólnego API `ktor-client-core`. Pełny zestaw opcji znajduje się w [dokumentacji Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). ## Pisanie kodu specyficznego dla platformy za pomocą expect i actual Część logiki nie może znajdować się w `commonMain`, ponieważ wymaga API platformy. Mechanizm `expect`/`actual` deklaruje kształt we wspólnym kodzie i dostarcza konkretną implementację dla każdego celu, rozwiązywaną w czasie kompilacji bez żadnego kosztu w czasie działania. ```kotlin // commonMain/kotlin/Platform.kt // commonMain declares what it needs but cannot implement here expect class Platform() { val name: String val osVersion: String } // Shared logic can now depend on Platform freely fun analyticsUserAgent(): String { val platform = Platform() return "SharpSkill/" + platform.name + " " + platform.osVersion } ``` Implementacja na Androida odczytuje dane z klasy `Build`, a implementacja na iOS z `UIDevice`. Obie spełniają ten sam kontrakt, więc `commonMain` nigdy nie wie, z którą z nich rozmawia. ```kotlin // androidMain/kotlin/Platform.kt import android.os.Build // actual supplies the Android-specific implementation actual class Platform actual constructor() { actual val name: String = "Android" actual val osVersion: String = Build.VERSION.RELEASE } ``` ```kotlin // iosMain/kotlin/Platform.kt import platform.UIKit.UIDevice // actual uses UIKit directly, no Objective-C bridging code required actual class Platform actual constructor() { actual val name: String = "iOS" actual val osVersion: String = UIDevice.currentDevice.systemVersion } ``` Kotlin/Native dostarcza typowane powiązania (bindings) dla całego SDK iOS, więc typy `UIDevice`, `NSUserDefaults` oraz Foundation można wywoływać z Kotlina bez ręcznie pisanego kodu łączącego. ## Współdzielenie logiki sieci i serializacji Sieć to kod o najwyższej wartości do współdzielenia, ponieważ kontrakty API są identyczne na obu platformach. Jeden klient [Ktor](https://ktor.io/docs/) w połączeniu z `kotlinx.serialization` zastępuje dwie równoległe implementacje. Funkcje `suspend` opierają się na [korutynach Kotlina](/blog/android/mastering-kotlin-coroutines), które mapują się czysto na `async`/`await` po obu stronach. ```kotlin // commonMain/kotlin/data/InterviewRepository.kt import io.ktor.client.HttpClient import io.ktor.client.call.body import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.request.get import io.ktor.serialization.kotlinx.json.json import kotlinx.serialization.Serializable // One serializable model, zero duplication across platforms @Serializable data class Question(val id: String, val prompt: String, val difficulty: String) // One HttpClient, one JSON parser, one repository for both apps class InterviewRepository { private val client = HttpClient { install(ContentNegotiation) { json() } } suspend fun fetchQuestions(tech: String): List = client.get("https://api.sharpskill.dev/questions/" + tech).body() } ``` To repozytorium, jego logika ponawiania oraz modele danych są napisane raz. Błąd naprawiony w parserze zostaje naprawiony jednocześnie na Androidzie i iOS, i właśnie tam KMP zwraca koszt konfiguracji. Ten sam wzorzec rozszerza się na współdzieloną bazę danych z SQLDelight, współdzielone przechowywanie klucz-wartość oraz współdzielone wstrzykiwanie zależności z Koin, więc cała warstwa danych może znajdować się w `commonMain` za interfejsami, od których zależy interfejs użytkownika. Częstym błędem jest próba współdzielenia zbyt wielu rzeczy zbyt wcześnie. Pragmatyczna droga to zacząć od jednego repozytorium, sprawdzić działanie kompilacji i procesu CI na obu platformach, a następnie przenosić funkcje po kolei, wraz z rosnącą pewnością. ## Budowanie współdzielonego interfejsu z Compose Multiplatform Compose Multiplatform osiągnął status stabilny na iOS w maju 2025 (wersja 1.8.0), a do połowy 2026 roku osiągnął wersję 1.11.0 z natywnym wprowadzaniem tekstu i domyślnie włączonym renderowaniem współbieżnym. Funkcja `@Composable` w `commonMain` renderuje się na obu platformach, a współdzielone modele widoku naturalnie łączą się z [architekturą MVVM lub MVI](/blog/android/mvvm-vs-mvi-architecture). ```kotlin // commonMain/kotlin/ui/QuestionList.kt import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Card import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.compose.foundation.layout.padding // This list renders identically on Android and iOS @Composable fun QuestionList(questions: List) { LazyColumn { items(questions) { question -> Card(modifier = Modifier.padding(8.dp)) { Text(text = question.prompt, modifier = Modifier.padding(16.dp)) } } } } ``` Współdzielenie interfejsu jest opcjonalne i realizowane ekran po ekranie. Zespół może współdzielić cały interfejs za pomocą [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform), zachować SwiftUI dla całej aplikacji i współdzielić tylko logikę, albo łączyć oba podejścia. Ekrany zależne od UX specyficznego dla platformy, takie jak widżety czy App Clips, zwykle pozostają natywne. ## Korzystanie ze współdzielonego kodu ze Swifta na iOS Współdzielony moduł kompiluje się do frameworka, który Swift importuje jak każdą inną bibliotekę. Historycznie tarcie powodowały funkcje `suspend` Kotlina oraz `Flow`, które pojawiały się jako niewygodne API oparte na callbackach. [SKIE](https://skie.touchlab.co/) od Touchlab rozwiązuje ten problem, generując idiomatyczny Swift: `suspend` staje się natywnym `async`/`await`, a `Flow` staje się `AsyncSequence`. ```swift // iosApp/ContentView.swift import SwiftUI import Shared // the KMP framework, baseName = "Shared" struct ContentView: View { @State private var questions: [Question] = [] private let repository = InterviewRepository() var body: some View { List(questions, id: \.id) { question in Text(question.prompt) } .task { // With SKIE, the Kotlin suspend fun is a Swift async fun questions = (try? await repository.fetchQuestions(tech: "android")) ?? [] } } } ``` Zespół iOS pisze zwykły Swift i SwiftUI. Korzysta ze współdzielonych modeli i repozytoriów dokładnie tak, jak korzystałby z natywnego pakietu Swift, co sprawia, że KMP pozostaje niewidoczny dla większości kodu iOS. Dwie chropowatości nadal warto wymienić na rozmowie kwalifikacyjnej. Po pierwsze, czasy kompilacji: kompilacja Kotlin/Native jest wolniejsza niż czysta kompilacja Swift, choć pamięć podręczna kompilacji Gradle i dystrybucja gotowego XCFramework łagodzą codzienny koszt. Po drugie, debugowanie przez granicę Swift-Kotlin poprawia się, ale nie jest jeszcze tak płynne jak pozostawanie w jednym języku, co jest kolejnym powodem, dla którego zespoły utrzymują współdzieloną powierzchnię świadomie ograniczoną, a nie rozrastającą się. ## Pytania rekrutacyjne o Kotlin Multiplatform, których można się spodziewać Ponieważ pytania rekrutacyjne o KMP coraz częściej pojawiają się w rolach związanych z Androidem i aplikacjami mobilnymi, kilka powracających warto przećwiczyć obok szerszego [przygotowania do rozmowy o Androidzie](/technologies/android): - **Jak expect/actual różni się od interfejsu?** expect/actual rozwiązuje się w czasie kompilacji dla każdego celu, bez dynamicznego rozdzielania w czasie działania, i może obsługiwać klasę, funkcję, właściwość lub typealias. Interfejs rozwiązuje się dynamicznie w czasie działania. expect/actual stosuje się do API platformy, a interfejsy do polimorfizmu wewnątrz współdzielonego kodu. - **Jak Kotlin/Native obsługuje pamięć i wątki?** Od Kotlin 1.7.20 nowy menedżer pamięci usunął stary model zamrażania obiektów, więc współdzielony zmienny stan i korutyny działają między wątkami w podobny sposób jak na JVM. - **Czy istniejąca aplikacja może wdrożyć KMP stopniowo?** Tak. Współdzielony moduł dostarczany jest jako AAR na Androida i XCFramework na iOS, więc jedno repozytorium lub funkcja może migrować pojedynczo, bez przepisywania. - **Kiedy interfejs powinien pozostać natywny zamiast używać Compose Multiplatform?** Gdy ekran opiera się na UX specyficznym dla platformy lub gdy zespół iOS jest właścicielem warstwy prezentacji i woli ją kontrolować. > **Zestaw narzędzi w 2026** > > Warto celować w Kotlin 2.3 z kompilatorem K2, Ktor 3.x do sieci, SQLDelight do współdzielonej bazy danych oraz Koin do wstrzykiwania zależności. Dla interoperacyjności z iOS warto dodać SKIE wcześnie; dokładanie go po napisaniu warstwy Swift jest znacznie bardziej bolesne. ## Podsumowanie Kotlin Multiplatform w 2026 roku to pragmatyczny sposób na ograniczenie zduplikowanej logiki między Androidem a iOS bez rezygnacji z natywnego interfejsu i wydajności: - Współdziel logikę, która ma jedno źródło prawdy (modele, sieć, walidacja), i wybieraj natywny interfejs lub Compose na każdym ekranie. - Używaj `expect`/`actual` dla API platformy; rozwiązuje się w czasie kompilacji bez narzutu w czasie działania. - Pisz sieć raz za pomocą Ktor i `kotlinx.serialization`, aby poprawki trafiały na obie platformy jednocześnie. - Wdrażaj stopniowo poprzez AAR i XCFramework, zamiast decydować się na przepisywanie od nowa. - Dodaj SKIE od początku, aby Swift korzystał z `suspend` i `Flow` jako natywnych `async`/`await` oraz `AsyncSequence`. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/pl/blog/android/kotlin-multiplatform-2026