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.

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 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.
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.
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.
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.
// 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.
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
}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 w połączeniu z kotlinx.serialization zastępuje dwie równoległe implementacje. Funkcje suspend opierają się na korutynach Kotlina, które mapują się czysto na async/await po obu stronach.
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<Question> =
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ą.
Gotowy na rozmowy o Android?
Ćwicz z naszymi interaktywnymi symulatorami, flashcards i testami technicznymi.
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.
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<Question>) {
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, 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 od Touchlab rozwiązuje ten problem, generując idiomatyczny Swift: suspend staje się natywnym async/await, a Flow staje się AsyncSequence.
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:
- 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ć.
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/actualdla 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
suspendiFlowjako natywnychasync/awaitorazAsyncSequence.
Zacznij ćwiczyć!
Sprawdź swoją wiedzę z naszymi symulatorami rozmów i testami technicznymi.
Tagi
Udostępnij
Powiązane artykuły

Kotlin 2.3 dla Androida: dekonstrukcja po nazwach, KMP i pytania rekrutacyjne 2026
Pytania rekrutacyjne z Kotlin 2.3: dekonstrukcja po nazwach, Kotlin Multiplatform, parametry kontekstowe, korutyny i Flow. Przygotowanie do rozmów Android developera w 2026 z praktycznymi przykładami kodu.

Room Database w Androidzie 2026: Migracje, Relacje i Współbieżność z Coroutines
Kompleksowy przewodnik po Room Database w Androidzie 2026 - od migracji schematu, przez relacje między encjami, po integrację z Kotlin Coroutines i najlepsze praktyki.

Kotlin Flow vs StateFlow vs SharedFlow: pytania rekrutacyjne Android w 2026
Pytania o Kotlin Flow vs StateFlow vs SharedFlow, które rekruterzy Androida zadają w 2026 roku, z jasnymi odpowiedziami, tabelą porównawczą i kodem gotowym do produkcji.