Почему Kotlin — это не только для Android?
Если вы до сих пор считаете, что Kotlin — это просто "улучшенная Java" для Android, то вы упускаете главное. В мире, где каждый хочет писать один код и запускать его везде, Kotlin Multiplatform (KMP) превращается в настоящего "убийцу" фреймворков вроде Flutter и React Native. Но в отличие от них, Kotlin не заставляет вас отказываться от нативного UI или учить JavaScript.
Представьте: вы пишете бизнес-логику, сетевые запросы и работу с базой данных один раз на Kotlin. А затем используете этот код на Android, iOS, в вебе и даже на сервере (Ktor). Звучит как фантастика? Добро пожаловать в реальность кроссплатформенной разработки на Kotlin.
В этой статье мы не просто расскажем о теории. Мы покажем реальные примеры кода, сравним KMP с конкурентами и дадим вам пошаговый план, как начать уже сегодня.
Что такое Kotlin Multiplatform (KMP) и зачем он нужен?
KMP — это не фреймворк. Это технология компиляции, встроенная в язык Kotlin. Она позволяет компилировать общий код (common code) в разные платформы:
- Android — байт-код JVM (как обычно).
- iOS — нативный бинарник (через LLVM).
- JavaScript/Web — JS-код для браузера.
- Native (Linux, Windows, macOS) — через Kotlin/Native.
Главная фишка: вы пишете expect/actual — объявляете ожидаемый API в общем коде, а реализуете его под каждую платформу отдельно. Всё остальное — общее.
Пример 1: Общий модуль для сетевых запросов
Давайте напишем простой HTTP-клиент, который будет работать на Android и iOS. Для этого используем Ktor — клиент, написанный на Kotlin.
Шаг 1: Структура проекта (Gradle)
В файле build.gradle.kts вашего общего модуля:
plugins {
kotlin("multiplatform")
id("io.ktor.plugin") version "2.3.7"
}
kotlin {
androidTarget()
iosX64()
iosArm64()
iosSimulatorArm64()
sourceSets {
val commonMain by getting {
dependencies {
implementation("io.ktor:ktor-client-core:2.3.7")
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
}
}
val androidMain by getting {
dependencies {
implementation("io.ktor:ktor-client-android:2.3.7")
}
}
val iosMain by creating {
dependsOn(commonMain)
dependencies {
implementation("io.ktor:ktor-client-darwin:2.3.7")
}
}
}
}
Шаг 2: Общий код (commonMain)
Создадим data class и API-клиент:
// commonMain/kotlin/com/example/User.kt
import kotlinx.serialization.Serializable
@Serializable
data class User(
val id: Int,
val name: String,
val email: String
)
// commonMain/kotlin/com/example/ApiClient.kt
import io.ktor.client.*
import io.ktor.client.call.*
import io.ktor.client.plugins.contentnegotiation.*
import io.ktor.client.request.*
import io.ktor.serialization.kotlinx.json.*
class ApiClient {
private val client = HttpClient {
install(ContentNegotiation) {
json()
}
}
suspend fun fetchUser(id: Int): User {
return client.get("https://jsonplaceholder.typicode.com/users/$id").body()
}
}
Видите? Никаких if-ов, никаких платформенных проверок. Просто чистый Kotlin.
Пример 2: Expect/Actual для работы с SharedPreferences
Допустим, нам нужно сохранить токен. На Android — SharedPreferences, на iOS — UserDefaults. Показываем магию KMP:
Общий код (commonMain):
// commonMain/kotlin/com/example/Platform.kt
expect class PlatformStorage {
fun save(key: String, value: String)
fun get(key: String): String?
}
// Используем в общем коде
class TokenManager(private val storage: PlatformStorage) {
fun saveToken(token: String) = storage.save("auth_token", token)
fun getToken(): String? = storage.get("auth_token")
}
Реализация под Android (androidMain):
// androidMain/kotlin/com/example/Platform.android.kt
import android.content.Context
import android.content.SharedPreferences
actual class PlatformStorage(context: Context) {
private val prefs: SharedPreferences =
context.getSharedPreferences("my_prefs", Context.MODE_PRIVATE)
actual fun save(key: String, value: String) {
prefs.edit().putString(key, value).apply()
}
actual fun get(key: String): String? {
return prefs.getString(key, null)
}
}
Реализация под iOS (iosMain):
// iosMain/kotlin/com/example/Platform.ios.kt
import platform.Foundation.NSUserDefaults
actual class PlatformStorage {
private val defaults = NSUserDefaults.standardUserDefaults
actual fun save(key: String, value: String) {
defaults.setObject(value, forKey = key)
}
actual fun get(key: String): String? {
return defaults.stringForKey(key)
}
}
Итог: 90% вашего кода — общие. Только 10% — платформенные адаптеры.
Сравнение: Kotlin Multiplatform vs Flutter vs React Native
Многие задаются вопросом: "А не проще ли взять Flutter?" Давайте разложим по полочкам.
| Критерий | Kotlin Multiplatform | Flutter (Dart) | React Native (JS) |
|---|---|---|---|
| Язык | Kotlin (статическая типизация, null-safety) | Dart (статическая, менее популярен) | JavaScript/TS (динамическая) |
| UI | Нативный (Jetpack Compose/SwiftUI) | Свой рендер-движок (Skia) | Нативные компоненты (мост JS-Native) |
| Производительность | Нативная (без мостов) | Высокая (но не нативная) | Средняя (мост тормозит) |
| Повторное использование | Только бизнес-логика (UI свой) | 100% (включая UI) | ~70% (UI часто переписывают) |
| Экосистема | Растёт (Jet
БлогиРекомендаций по книгамЧитайте также |