# Kotlin Multiplatform 2026: спільний код Android та iOS > Як Kotlin Multiplatform у 2026 році робить бізнес-логіку спільною між Android та iOS: налаштування Gradle, expect/actual, мережа Ktor, інтерфейс Compose Multiplatform і питання співбесіди з 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) дозволяє одній кодовій базі на Kotlin забезпечувати бізнес-логіку застосунків для Android та iOS, тоді як кожна платформа зберігає повністю нативний інтерфейс, продуктивність і доступ до власного SDK. У 2026 році KMP уже не експеримент: він стабільний, офіційно підтримується [Google](https://developer.android.com/kotlin/multiplatform) і працює у продакшені в Netflix, McDonald's, Forbes та Cash App. Цей докладний огляд пояснює, як насправді працює спільне використання коду, як налаштувати спільний модуль і які компроміси варто зрозуміти перед співбесідою з KMP. > **Що саме KMP робить спільним** > > Kotlin Multiplatform робить спільною скомпільовану логіку, а не середовище виконання. Спільний код Kotlin компілюється у байткод JVM для Android і в нативний бінарний файл (через LLVM) для iOS. Немає мосту JavaScript, вбудованого webview чи шару рефлексії, тому спільний код працює з нативною швидкістю на обох платформах. ## Як Kotlin Multiplatform робить код спільним без втрати нативної продуктивності Основний механізм, це ієрархія наборів джерел (source sets). `commonMain` містить незалежний від платформи код Kotlin: мережу, серіалізацію, бізнес-правила та моделі подання. Платформні набори джерел, такі як `androidMain` та `iosMain`, містять код, що звертається до API платформи. Компілятор створює артефакт JVM з `commonMain` плюс `androidMain` для Android і нативний фреймворк з `commonMain` плюс `iosMain` для iOS. У 2026 році Kotlin Multiplatform віддає перевагу спільному використанню того, що справді виграє від єдиного джерела правди: моделей, клієнтів API, кешування та валідації. Інтерфейс можна зробити спільним за допомогою Compose Multiplatform або залишити повністю нативним зі SwiftUI, екран за екраном. Ця модель "спільна логіка, вибір інтерфейсу" є причиною того, чому команди впроваджують KMP без переписування наявних застосунків. Kotlin Multiplatform, це технологія Kotlin, яка компілює один спільний модуль у кілька цілей (Android на JVM, iOS як нативний, а також десктоп і веб), тож бізнес-логіка пишеться один раз, а кожна платформа зберігає свій нативний інтерфейс і повний доступ до SDK. Google тепер підтримує його як варіант першого класу, а бібліотеки Jetpack, такі як Room, DataStore, ViewModel і Paging, пропонують артефакти KMP. Рішення про те, що належить до спільного модуля, є найважливішим архітектурним вибором у проєкті KMP. Наведена нижче таблиця відображає, як більшість продакшн-команд проводять цю межу у 2026 році: усе, що нижче за шар подання, є сильним кандидатом на спільне використання, а все, що стосується UX-конвенцій платформи, зазвичай залишається нативним. | Шар | Спільний у commonMain? | Причина | |-------|---------------------|--------| | Моделі даних і DTO | Так | Ідентичний контракт API на обох платформах | | Мережа і кешування | Так | Один стек HTTP, одна поверхня помилок | | Бізнес-правила і валідація | Так | Домен не відрізняється між ОС | | Моделі подання і стан | Зазвичай | Compose і SwiftUI споживають спільний стан | | Рендеринг інтерфейсу | Опціонально | Спільний з Compose або нативний на кожному екрані | | API платформи (камера, біометрія) | Ні | Натомість надається через expect/actual | Вигода зростає залежно від того, якою частиною нижньої половини цієї таблиці володіє застосунок. Продукти з великою кількістю даних і тонким інтерфейсом роблять спільним найбільше; вузькоспеціалізовані, орієнтовані на анімацію застосунки, найменше. Жодна з крайнощів не є хибною, тому KMP підходить для поступового впровадження, а не для переписування всього одразу. ## Налаштування спільного модуля у Gradle Модуль KMP оголошує свої цілі та набори джерел у Kotlin DSL. Наведений нижче `build.gradle.kts` налаштовує Android і три архітектури iOS (фізичний пристрій, симулятор Intel, симулятор Apple Silicon) і надає вихідні дані iOS як фреймворк з назвою `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") } } } ``` Кожна ціль підтягує специфічний для платформи рушій Ktor (`okhttp` на Android, `darwin` на iOS), тоді як спільний код залежить лише від загального API `ktor-client-core`. Повний набір параметрів наведено в [документації Kotlin Multiplatform](https://kotlinlang.org/docs/multiplatform.html). ## Написання специфічного для платформи коду за допомогою expect і actual Частина логіки не може перебувати в `commonMain`, оскільки їй потрібне API платформи. Механізм `expect`/`actual` оголошує форму у спільному коді та надає конкретну реалізацію для кожної цілі, що вирішується під час компіляції без жодних витрат під час виконання. ```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 } ``` Реалізація для Android читає дані з класу `Build`, а реалізація для iOS, з `UIDevice`. Обидві задовольняють той самий контракт, тож `commonMain` ніколи не знає, з якою з них взаємодіє. ```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 постачає типізовані прив'язки (bindings) для всього SDK iOS, тож типи `UIDevice`, `NSUserDefaults` і Foundation можна викликати з Kotlin без написаного вручну сполучного коду. ## Спільне використання логіки мережі та серіалізації Мережа, це код із найвищою цінністю для спільного використання, оскільки контракти API ідентичні на обох платформах. Один клієнт [Ktor](https://ktor.io/docs/) разом із `kotlinx.serialization` замінює дві паралельні реалізації. Функції `suspend` спираються на [корутини Kotlin](/blog/android/mastering-kotlin-coroutines), які чисто відображаються на `async`/`await` з обох боків. ```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() } ``` Це репозиторій, його логіка повторних спроб і моделі даних написані один раз. Помилка, виправлена в парсері, виправляється на Android та iOS одночасно, і саме тут KMP окупає вартість налаштування. Той самий шаблон поширюється на спільну базу даних із SQLDelight, спільне сховище ключ-значення та спільне впровадження залежностей із Koin, тож увесь шар даних може перебувати в `commonMain` за інтерфейсами, від яких залежить інтерфейс користувача. Поширена помилка, це намагання зробити спільним занадто багато й занадто рано. Прагматичний шлях, почати з одного репозиторію, перевірити збірку та конвеєр CI на обох платформах, а потім переносити функції по черзі, у міру зростання впевненості. ## Побудова спільного інтерфейсу за допомогою Compose Multiplatform Compose Multiplatform досяг стабільного стану на iOS у травні 2025 року (версія 1.8.0) і до середини 2026 року досяг версії 1.11.0 з нативним введенням тексту та ввімкненим за замовчуванням паралельним рендерингом. Функція `@Composable` у `commonMain` рендериться на обох платформах, а спільні моделі подання природно поєднуються з [архітектурою MVVM або 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)) } } } } ``` Спільне використання інтерфейсу є опціональним і виконується екран за екраном. Команда може зробити спільним увесь інтерфейс за допомогою [Compose Multiplatform](https://github.com/JetBrains/compose-multiplatform), залишити SwiftUI для всього застосунку й робити спільною лише логіку, або поєднувати обидва підходи. Екрани, що залежать від специфічного для платформи UX, як-от віджети чи App Clips, зазвичай залишаються нативними. ## Використання спільного коду зі Swift на iOS Спільний модуль компілюється у фреймворк, який Swift імпортує як будь-яку бібліотеку. Історично тертя спричиняли функції `suspend` Kotlin і `Flow`, що з'являлися як незручні API на основі зворотних викликів. [SKIE](https://skie.touchlab.co/) від Touchlab вирішує це, генеруючи ідіоматичний Swift: `suspend` стає нативним `async`/`await`, а `Flow` стає `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")) ?? [] } } } ``` Команда iOS пише звичайний Swift і SwiftUI. Вона використовує спільні моделі та репозиторії точно так само, як використовувала б нативний пакет Swift, що робить KMP невидимим для більшої частини коду iOS. Дві шорсткості все ще варто назвати на співбесіді. По-перше, час збірки: компіляція Kotlin/Native повільніша за чисту збірку Swift, хоча кеш збірки Gradle і розповсюдження попередньо зібраного XCFramework пом'якшують щоденні витрати. По-друге, налагодження через межу Swift-Kotlin покращується, але ще не таке безшовне, як робота в одній мові, що є ще однією причиною, чому команди тримають спільну поверхню свідомо обмеженою, а не розлогою. ## Питання співбесіди з Kotlin Multiplatform, яких варто очікувати Оскільки питання співбесіди з KMP дедалі частіше з'являються в ролях, пов'язаних з Android і мобільними застосунками, кілька повторюваних варто відрепетирувати поряд із ширшою [підготовкою до співбесіди з Android](/technologies/android): - **Чим expect/actual відрізняється від інтерфейсу?** expect/actual вирішується під час компіляції для кожної цілі без динамічного диспетчингу під час виконання й може забезпечувати клас, функцію, властивість або typealias. Інтерфейс вирішується динамічно під час виконання. expect/actual використовують для API платформи, а інтерфейси, для поліморфізму всередині спільного коду. - **Як Kotlin/Native працює з пам'яттю та потоками?** Починаючи з Kotlin 1.7.20 новий менеджер пам'яті прибрав стару модель заморожування об'єктів, тож спільний змінний стан і корутини працюють між потоками приблизно так само, як на JVM. - **Чи може наявний застосунок впроваджувати KMP поступово?** Так. Спільний модуль постачається як AAR для Android і XCFramework для iOS, тож один репозиторій або функція можуть мігрувати по черзі без переписування. - **Коли інтерфейс має залишатися нативним замість використання Compose Multiplatform?** Коли екран спирається на специфічний для платформи UX або коли команда iOS володіє шаром подання й воліє контролювати його. > **Набір інструментів у 2026** > > Варто орієнтуватися на Kotlin 2.3 з компілятором K2, Ktor 3.x для мережі, SQLDelight для спільної бази даних і Koin для впровадження залежностей. Для взаємодії з iOS SKIE слід додати рано; додавати його після написання шару Swift значно болючіше. ## Висновок Kotlin Multiplatform у 2026 році, це прагматичний спосіб скоротити дубльовану логіку між Android та iOS, не відмовляючись від нативного інтерфейсу чи продуктивності: - Робіть спільною логіку, яка має єдине джерело правди (моделі, мережа, валідація), і обирайте нативний або Compose інтерфейс на кожному екрані. - Використовуйте `expect`/`actual` для API платформи; воно вирішується під час компіляції без накладних витрат під час виконання. - Пишіть мережу один раз за допомогою Ktor і `kotlinx.serialization`, щоб виправлення потрапляли на обидві платформи одночасно. - Впроваджуйте поступово через AAR і XCFramework, а не наважуйтеся на повне переписування. - Додайте SKIE від самого початку, щоб Swift споживав `suspend` і `Flow` як нативні `async`/`await` та `AsyncSequence`. --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/android/kotlin-multiplatform-2026