Автор статьи: Сергей Прощаев (@sproshchaev), Руководитель направления Java-разработки в FinTech

Введение 

Kotlin давно перерос статус языка «только для Android». Благодаря технологии Kotlin Multiplatform (KMP), он позволяет создавать приложения, которые работают на JVM, iOS, macOS, вебе и других платформах, сохраняя единый код бизнес-логики. 

В отличие от React Native, Flutter или Xamarin, KMP не навязывает свои UI-компоненты, а интегрируется с нативными инструментами, что делает его гибким инструментом для кросс-платформенной разработки.

Почему Kotlin Multiplatform выделяется из остальных решений?

Традиционные фреймворки требуют писать UI и платформенные функции на их собственных API, что часто приводит к изучению новых инструментов и дублированию кода. KMP, в отличие от этого, предлагает иной подход: 

  1. Общий код (например, бизнес-логика, алгоритмы, работа с данными) пишется один раз на Kotlin. 

  2. Платформенный код (UI, доступ к железу) реализуется отдельно для каждой системы с использованием её родных технологий. Это устраняет необходимость изучать несколько UI-фреймворков и позволяет использовать уже знакомые инструменты, такие как Swift для iOS или Jetpack Compose для Android.

В условиях растущего спроса на приложения, работающие на множестве платформ, KMP выделяется как передовая технология, отвечающая вызовам современной разработки. Вместо создания изолированных кодовых баз для iOS, Android, веба или десктопа, KMP позволяет разделять до 80% кода — включая бизнес-логику, работу с сетью и аналитику — между всеми платформами. Это не только сокращает затраты времени и ресурсов, но и гарантирует консистентность функционала, упрощая поддержку и последующие обновления продукта.

Кроме того, KMP позволяет упрощать тестирование. Ведь общий код тестируется один раз, а его независимость от платформенных деталей уменьшает количество потенциальных ошибок. Разработчики могут сосредоточиться на сложных задачах, вместо того чтобы дублировать рутинные операции для каждой ОС. 

Как будет выглядеть классический “Hello, World!”, реализуемый на Kotlin Multiplatform для четырёх платформ?

Безусловно то, что невозможно рассказать все о KMP в рамках одной статьи, но возможно описать основные принципы, которые заложены в этом подходе.

Для начала разберем типовую структуру самого простого проекта на Kotlin, содержащего код для нескольких платформ:

my-kmp-project/
├── build.gradle.kts
└── src/
    ├── commonMain/
    │   └── kotlin/
    │       └── Common.kt  
    ├── jvmMain/
    │   └── kotlin/
    │       └── JvmPlatform.kt 
    ├── jsMain/
    │   └── kotlin/
    │       └── JsPlatform.kt  
    ├── iosArm64Main/
    │   └── kotlin/
    │       └── IosPlatform.kt 
    └── macosX64Main/
        └── kotlin/
            └── MacosPlatform.kt 

В commonMain содержится код, общий для всех платформ. В Common.kt выносим логику, которая не зависит от конкретной платформы:

expect fun platformName(): String

fun greet() {
    println("Hello, ${platformName()}!")
}

Далее каждый каталог вида <target>Main содержит реализации для конкретной платформы.

В каталог jvmMain в JvmPlatform.kt помещаем JVM-специфичный код, который работает на Windows, Linux, macOS через виртуальную машину Java:

actual fun platformName(): String = "JVM"

В jsMain в JsPlatform.kt будет находится JavaScript-код для браузеров и Node.js:

actual fun platformName(): String = "JavaScript"

В iosArm64Main в файл IosPlatform.kt пишем нативный код для iOS-устройств: 

actual fun platformName(): String = "iOS"

В macosX64Main создаем файл MacosPlatform.kt для нативного кода macOS:

actual fun platformName(): String = "macOS"

В конфигурацию build.gradle.kts добавляем:

plugins {
    kotlin("multiplatform") version "1.9.20"
}

kotlin {
    jvm()          // Для Windows, Linux, macOS (JVM)
    macosX64()     // macOS x64
    iosArm64()     // iOS ARM64
    js(IR) {       // JavaScript
        browser()
    }

    sourceSets {
        val commonMain by getting {
            dependencies {
                implementation(kotlin("stdlib-common"))
            }
        }
        val jvmMain by getting { dependencies { implementation(kotlin("stdlib-jdk8")) } }
        val jsMain by getting { dependencies { implementation(kotlin("stdlib-js")) } }
    }
}

В блоке kotlin { ... } у нас определены целевые платформы: jvm(), macosX64()iosArm64() и js(IR)

Блок sourceSets { ... } описывает расположение исходников и зависимостей для каждой из платформ:

  1. commonMain — содержит общую логику для всех платформ, которая размещается в каталоге src/commonMain/kotlin,

  2. jvmMain — указывает на код для JVM, расположенный в каталоги src/jvmMain/kotlin и зависимость stdlib-jdk8 которая предоставляет нашему проекту расширенную библиотеку для работы с JVM/JDK 8+,

  3. jsMain — определяет каталог src/jsMain/kotlin с исходниками для JavaScript и зависимость stdlib-js в которой находится библиотека, адаптированная для JS. В последних версиях Kotlin stdlib-js подключается автоматически и ее можно не указывать явно.

Для iosArm64Main и macosX64Main зависимости не указаны — они используют stdlib-common по умолчанию.

Как это работает?

При компиляции Gradle объединяет общий код, который находится в commonMain с платформенным кодом. Например, для сборки под macOS

  1. берётся Common.kt из commonMain

  2. добавляется MacosPlatform.kt из macosX64Main

  3. и все компилируется в нативный macOS-бинарник.

Этот процесс автоматически разрешает зависимости между общим и платформенным кодом. Такой подход гарантирует, что бизнес-логика остаётся единой, а платформенные нюансы обрабатываются локально, что упрощает поддержку и тестирование.

Заключение или почему все-таки Kotlin Multiplatform актуален сегодня?

1. Универсальность без компромиссов: KMP не ограничивает выбор инструментов: он гармонично интегрируется с нативными фреймворками (SwiftUI, Jetpack Compose, React) и позволяет использовать платформенные API там, где это необходимо. Например, UI остаётся нативным, а логика обработки данных — общей. Это делает приложения производительными и соответствующими гайдлайнам каждой ОС. 

2. Поддержка всех ключевых платформ: c KMP можно компилировать код для JVM (Android, серверы), iOS/macOS (через нативные бинарники), JavaScript (фронтенд/бэкенд) и даже встраиваемых систем. Такая гибкость критически важна для проектов, где требуется охват desktop-, mobile- и web-аудитории. 

3. Активное развитие и индустриальная поддержка: JetBrains, который  является создателем Kotlin, постоянно улучшает KMP, добавляя поддержку новых платформ (например, Kotlin/Wasm) и интеграцию с инструментами вроде Xcode и CocoaPods. 

Технология активно используется гигантами типа Netflix, CashApp и Autodesk, что подтверждает её зрелость и применимость в production. 

KMP — это не просто «ещё один кроссплатформенный инструмент», а целое  стратегическое решение для команд, которые стремятся к эффективности, сохраняя при этом качество и нативный пользовательский опыт. В условиях, где время вывода продукта на рынок и поддержка множества платформ становятся ключевыми факторами успеха, Kotlin Multiplatform предлагает оптимальный баланс между унификацией и гибкостью!


Если вы хотите глубже разобраться в возможностях Kotlin, заглянуть за рамки привычной разработки и изучить реальные приёмы, которые применяются в продакшене — присмотритесь к ближайшим занятиям в Otus. Это не обзорные лекции, а практичные уроки по конкретным темам:

А если вам интересно узнать свой уровень знаний Kotlin, пройдите вступительное тестирование.

Комментарии (17)


  1. dkfbm
    13.05.2025 08:50

    Платформенный код (UI, доступ к железу) реализуется отдельно для каждой системы с использованием её родных технологий. Это устраняет необходимость изучать несколько UI-фреймворков и позволяет использовать уже знакомые инструменты, такие как Swift для iOS или Jetpack Compose для Android.

    Ээээ, наоборот. КМР требует знания как себя самого, так и нативных инструментов – ну или чего-то типа JetPack Compose. В то время как флаттер или (не к ночи будь помянут) реакт самодостаточны.


    1. pwn3r
      13.05.2025 08:50

      Согласен, KMP требует знания как самого Kotlin, так и нативных инструментов. Однако он дает больше гибкости и позволяет работать с уже привычными фреймворками и API. Это плюс для тех, кто хочет сохранить нативный вид и производительность приложения, не изучая новые UI-фреймворки с нуля. Flutter или React Native, это тоже отличные решения, но они немного более ограничены в плане интеграции с нативными платформами.


      1. dkfbm
        13.05.2025 08:50

        Однако он дает больше гибкости

        Можете привести пример функционала, который невозможно реализовать встроенными средствами того же флаттера, обязательно требующего использования нативных API?

        и позволяет работать с уже привычными фреймворками и API

        Так в том-то и дело, что нужна команда, обладающая знаниями как КМР, так и каждой из целевых платформ.

        Flutter или React Native, это тоже отличные решения, но они немного более ограничены в плане интеграции с нативными платформами.

        Вот-вот, именно об этом поподробнее, пожалуйста. В каком конкретно месте обнаружились критичные ограничения? Не придираюсь, реально интересно.


        1. pwn3r
          13.05.2025 08:50

          Ограничения Flutter и React Native чаще всего всплывают, когда нужно что-то сделать на уровне самой платформы. Например, на iOS довольно геморно работать с CallKit, Live Activities или App Clips, без нативного кода это не решить. На Android, история с Foreground Services, всякими Broadcast Receiver’ами или глубоким контролем фоновых процессов. Без Kotlin/Java туда не влезешь.

          Если говорить о Bluetooth с какой-то специфической логикой (особенно на iOS), то тоже часто приходится уходить в натив. Ну и типичная ситуация, доступ к новым возможностям ОС сразу после их релиза. Flutter и RN обычно отстают, пока не появится официальная поддержка или кто-то не напишет нужный плагин.

          Kotlin Multiplatform в этом плане дает больше свободы: бизнес-логику пишешь один раз, а платформенные штуки спокойно реализуешь на Swift или Kotlin, как привык. Ничего не мешает нативно встраиваться, и при этом кодовая база остается максимально общей.


          1. dkfbm
            13.05.2025 08:50

            CallKit, Live Activities или App Clips, без нативного кода это не решить

            Первые два вопроса у нас были решены во флаттере без проблем, третий в процессе, скоро будет. Из натива понадобился один файл на Swift, буквально пару десятков срок, как часть того же флаттер проекта – без него не получалось надёжно принимать пуши, если приложение в бэкграунде или выгружено.

            С Bluetooth не работали, но библиотек полно.

            Ну и типичная ситуация, доступ к новым возможностям ОС сразу после их релиза.

            Вообще-то это противоречит идее кроссплатформы – которая подразумевает, что приложение делает ровно то же самое везде. Соответственно, если в одной ОС появилась фича, которой нет в других – это не про нас. Да и вообще ИМХО не стоит так уж суетиться.

            платформенные штуки спокойно реализуешь на Swift или Kotlin, как привык

            То есть опять команда универсальных солдат.

            при этом кодовая база остается максимально общей.

            Существенно менее общей, чем с флаттером всё же – где она просто единая.


            1. pwn3r
              13.05.2025 08:50

              Да, согласен, во Flutter многое реально сделать, особенно если есть опыт и не лень копать в сторону нативных интеграций. Но тут скорее вопрос не в том, можно или нельзя, а в том, насколько это удобно, стабильно и легко потом поддерживать.

              Добавить файл на Swift, не проблема. Но когда таких мест становится много, это уже не совсем "единый код". А если проект растет, начинаешь думать, стоит ли вообще всё это пихать в Flutter, или проще часть фич делать нативно, как и задумывалось платформой.

              Насчет "фича только на одной ОС", ну, зависит от задач. Если надо просто одинаково везде, то да, Flutter топ. Но если хочется выжать максимум из iOS или Android, использовать какие-нибудь Live Activities, Android-specific background-ограничения, или просто следовать нативным UX-гайдлайнам, тут уже начинаются компромиссы. Flutter это всё позволяет, но через танцы с бубном и нативные вкрапления. И чем больше таких мест, тем меньше ощущение "кроссплатформы".

              KMP тут даёт немного другой подход, логика общая, UI и доступ к железу, нативный. Да, нужно разбираться в каждой платформе, но зато ты не воюешь с фреймворком, а просто используешь родные инструменты. У каждого подхода свои плюсы, вопрос в приоритетах проекта.