1. Введение
В этом уроке мы разберём многоплатформенную разработку в Kotlin. Мы разработаем простое приложение, которое будет работать на нескольких платформах, таких как JVM, JS и Native.
Это также поможет нам понять преимущества многоплатформенной разработки и различные случаи использования, где мы можем эффективно применять её.
2. Что такое многоплатформенная разработка?
Очень часто мы пишем части программы, которые не зависят от платформы, на которой они выполняются. Например, мы вызываем REST API для получения данных и выполняем дополнительную обработку перед возвратом результата. Нам нужно фактически дублировать этот код на Java для бэкенда, на JS для веба и на Android или iOS для мобильных платформ.
Не было бы здорово, если бы мы могли просто написать наш код один раз и использовать его на нескольких платформах? Это и есть основное обещание многоплатформенной разработки! Многоплатформенная разработка на Kotlin идеально подходит для выполнения этого обещания. Мы увидим, как это работает в этом уроке.
Когда мы пишем программу на высокоуровневом языке, таком как Java или C, мы должны скомпилировать её, чтобы она выполнялась на платформе, такой как Windows или Linux. Однако с Java это компилируется в промежуточный формат, известный как байт-код. Это даёт Java её знаменитый слоган: "напиши один раз, запускай где угодно". Однако для выполнения этого байт-кода нам всё равно нужен JVM, специфичный для целевой платформы.
Kotlin Multiplatform поднимает эту концепцию на новый уровень и обещает запускать один и тот же код на нескольких платформах, таких как JVM, JS или даже Native, напрямую. Это не зависит от виртуальной машины, работающей на целевой платформе. Это делает многоплатформенную разработку одной из ключевых особенностей языка Kotlin.
Кроме того, это существенно снижает усилия, необходимые для написания и поддержки одного и того же кода для разных платформ.
3. Как Kotlin поддерживает многоплатформенность?
Прежде чем мы перейдём к тому, как работает магия многоплатформенной разработки, стоит потратить некоторое время на понимание того, как Kotlin на самом деле поддерживает эту функциональность. В этом разделе мы рассмотрим некоторые инструменты и техники, которые Kotlin предоставляет для облегчения и использования многоплатформенной разработки.
3.1. Основы
Когда мы можем взять какой-то код на Java и выполнить его на любом JVM, сила этого заключается в Java-компиляторе, который преобразует код в байт-код. Это позволяет различным языкам использовать одну и ту же JVM. Например, мы можем выполнять код на Kotlin, Groovy или Scala на одной и той же платформе JVM. Каждый из них имеет компилятор, который может генерировать совместимый байт-код.
Так что нет причин, почему мы не можем создать компиляторы, которые могут взять один и тот же код и преобразовать его в форматы, которые могут быть поняты разными платформами. Конечно, это легче сказать, чем сделать, и в некоторых случаях это может быть невозможно. В некоторых случаях это могут быть не компиляторы, а транспиляторы — переводчики исходного кода в исходный код.
Тем не менее, это именно то, как Kotlin поддерживает многоплатформенную разработку. Чтобы сделать общий код работающим на разных платформах, Kotlin предоставляет специфичные для платформы компиляторы и библиотеки, такие как Kotlin/JVM, Kotlin/JS и Kotlin/Native:
Здесь мы создаём повторно используемые части приложения на общем Kotlin, и благодаря поддержке многоплатформенности это работает на всех целевых платформах. Например, вызов REST API и получение данных может быть хорошим кандидатом для размещения в общей части кода.
3.2. Повторное использование исходного кода между платформами
Kotlin Multiplatform организует исходный код в иерархии, которые делают зависимости явными и позволяют повторно использовать код между исходными наборами (source sets). Все специфичные для платформы исходные наборы по умолчанию зависят от общего исходного набора:
Общий код может зависеть от множества библиотек, которые предоставляет Kotlin для типичных задач, таких как выполнение HTTP-запросов, сериализация данных и управление многозадачностью. Кроме того, платформенные версии Kotlin предоставляют библиотеки, которые позволяют использовать специфичные возможности целевых платформ.
Таким образом, мы решили оставить повторно используемую бизнес-логику в общем коде и разработать части приложения, такие как пользовательский интерфейс, с использованием нативных возможностей. Кроме того, многоплатформенная разработка на Kotlin позволяет нам делиться кодом между всеми платформами или делать это более избирательно:
Например, на изображении выше у нас есть общий код, который используется на всех платформах, но также есть некоторый общий нативный код, который используется только на нативных платформах, таких как Linux, Windows и macOS.
3.3. Разработка платформенных API
До сих пор мы видели, как многоплатформенная разработка на Kotlin позволяет нам повторно использовать общий код через платформенные исходные наборы. Однако в некоторых случаях может быть желательно определить и получить доступ к платформенным API в общем коде. Это особенно полезно в тех областях, где определённые общие и повторно используемые задачи специализированы и более эффективны для использования специфичных возможностей платформы.
Многоплатформенная разработка на Kotlin предоставляет механизм ожидаемых и фактических деклараций для достижения этой цели. Например, общий исходный набор может объявить функцию как ожидаемую, и для платформенных исходных наборов будет обязательным предоставить соответствующую функцию с фактической декларацией:
Здесь, как мы видим, мы используем функцию, объявленную как ожидаемую в общем исходном наборе. Общий код не заботится о том, как она будет реализована. На данный момент целевые платформы предоставляют специфичные для платформы реализации этой функции.
Мы можем использовать эти декларации для функций, классов, интерфейсов, перечислений, свойств и аннотаций.
3.4. Поддержка со стороны инструментов
Поскольку Kotlin разработан компанией JetBrains, которая также является пионером в разработке удобных IDE, таких как IntelliJ IDEA, вполне справедливо ожидать интегрированную поддержку многоплатформенной разработки. На самом деле IntelliJ IDEA предоставляет несколько шаблонов проектов для создания многоплатформенных проектов на Kotlin. Это делает процесс создания многоплатформенного проекта довольно бесшовным и быстрым.
Когда мы создаём многоплатформенный проект, используя шаблон проекта, автоматически применяется плагин kotlin-multiplatform для Gradle:
plugins {
kotlin("multiplatform") version "1.4.0"
}
Этот плагин kotlin-multiplatform настраивает проект для создания приложения или библиотеки, которые будут работать на нескольких платформах. Конфигурация, как обычно, размещается в файле build.gradle или build.gradle.kt, в зависимости от выбранного DSL:
kotlin {
jvm {
withJava()
}
js {
browser {
binaries.executable()
}
}
sourceSets {
val commonMain by getting {
dependencies {
.....
}
}
val commonTest by getting {
dependencies {
.....
}
}
val jvmMain by getting {
dependencies {
.....
}
}
val jsMain by getting {
dependencies {
.....
}
}
}
}
Как мы видим в конфигурации, у нас есть расширение "kotlin" в верхней части, которое включает конфигурации для целевых платформ, исходных наборов и зависимостей.
Кроме того, каждая целевая платформа может иметь одну или несколько компиляций. Многоплатформенные проекты на Kotlin используют компиляции для создания артефактов. Для каждой целевой платформы стандартные компиляции включают "main" и "test" компиляции для платформ JVM, JS и Native.
4. Практическая работа с многоплатформенной разработкой
В этом разделе мы применим некоторые теории, которые мы изучили до сих пор, на практике. Мы разработаем простое приложение калькулятора с общим кодом, который будем использовать на нескольких целевых платформах, таких как JVM, JS и Native.
4.1. Создание многоплатформенного проекта
Мы будем использовать один из шаблонов проекта IDEA для генерации skeleton многоплатформенной библиотеки на Kotlin. Давайте посмотрим на мастер выбора шаблонов проектов в IntelliJ IDEA Community Edition:
Обратите внимание, что мы также можем так же легко создавать skeleton'ы для других типов многоплатформенных проектов, таких как приложение, мобильная библиотека, мобильное приложение, нативное приложение или даже полнофункциональное приложение. После того как мы создадим проект Kotlin Multiplatform Library с помощью мастера, приведённого выше, он предоставит нам настройки конфигурации по умолчанию и структуру кодовой базы:
Как мы видим выше, мастер генерирует конфигурацию и структуру для общего кода и целевых платформ для JVM, JS и Native по умолчанию. Конечно, мы можем вручную удалить целевые платформы или добавить дополнительные, используя пресеты целей для поддерживаемых платформ.
Кроме того, мы можем при необходимости изменить конфигурацию по умолчанию для Gradle. Позже мы увидим, как изменить эти конфигурации, чтобы поддерживать фронтенд-приложение в модуле JavaScript и приложение командной строки в модуле Native.
4.2. Написание общего кода
После того как мы настроили структуру проекта, пришло время написать некоторый общий код, который мы будем использовать на целевых платформах. Общий код размещается в каталогах commonMain и commonTest в созданной ранее структуре проекта.
Напишем простую программу для симуляции калькулятора в commonMain:
fun add(num1: Double, num2: Double): Double {
val sum = num1 + num2
writeLogMessage("The sum of $num1 & $num2 is $sum", LogLevel.DEBUG)
return sum
}
fun subtract(num1: Double, num2: Double): Double {
val diff = num1 - num2
writeLogMessage("The difference of $num1 & $num2 is $diff", LogLevel.DEBUG)
return diff
}
fun multiply(num1: Double, num2: Double): Double {
val product = num1 * num2
writeLogMessage("The product of $num1 & $num2 is $product", LogLevel.DEBUG)
return product
}
fun divide(num1: Double, num2: Double): Double {
val division = num1 / num2
writeLogMessage("The division of $num1 & $num2 is $division", LogLevel.DEBUG)
return division
}
Как мы видим, это простые функции на Kotlin, но они могут быть повторно использованы на нескольких платформах. Таким образом, мы сильно выигрываем от того, что объявляем и поддерживаем их в одном месте.
Единственная интересная деталь здесь — это функция writeLogMessage, которую мы ещё не определяли. Давайте посмотрим, как её определить:
num class LogLevel {
DEBUG, WARN, ERROR
}
internal expect fun writeLogMessage(message: String, logLevel: LogLevel)
Итак, здесь мы объявили функцию writeLogMessage с ключевым словом expect. Как мы уже обсуждали, это заставляет многоплатформенный проект искать специфические для платформы реализации функции, объявленной с ключевым словом actual. Эти декларации должны иметь одинаковые имена и быть в одном пакете.
Теперь, зачем мы это делаем с методом writeLogMessage? Обоснование в том, что может быть некая функция, которая зависит от платформы. Например, запись в журнал может быть выполнена более эффективно с использованием специфичных для платформы возможностей.
Этот пример приведён только для демонстрации и не обязательно представляет собой обоснованный случай использования деклараций expect и actual. Тем не менее, следует быть очень осторожным и использовать декларации expect и actual экономно. Наша задача — реализовать как можно больше функциональности непосредственно в общем модуле.
4.3. Написание тестов для общего кода
Давайте напишем несколько тестов для наших простых калькуляторных функций:
@Test
fun testAdd() {
assertEquals(4.0, add(2.0, 2.0))
}
@Test
fun testSubtract() {
assertEquals(0.0, subtract(2.0, 2.0))
}
@Test
fun testMultiply() {
assertEquals(4.0, multiply(2.0, 2.0))
}
@Test
fun testDivide() {
assertEquals(1.0, divide(2.0, 2.0))
}
Здесь нет ничего сложного, это простые юнит-тесты, которые выполняют свою работу вполне корректно. Однако, интересно, что когда мы пытаемся их запустить, появляется новое окно в IntelliJ IDEA, предлагающее выбрать целевую платформу:
Это понятно, так как нам нужно определить, на какой целевой платформе мы хотим запустить наши тесты. Мы можем выбрать несколько целей, чтобы запустить тесты сразу на всех платформах.
5. Ориентирование на JVM с Kotlin/JVM
Когда Kotlin был разработан, он изначально ориентировался на Java Virtual Machine (JVM) как целевую платформу. Он пытался решить некоторые проблемы, которые были характерны для версии Java того времени. Однако язык программирования Kotlin никогда не был привязан к JVM и всегда имел намерение работать на нескольких платформах.
Так почему бы не перейти на Kotlin для серверной разработки, если мы уже какое-то время используем Java? Хотя Java в последних версиях пытается устранить этот разрыв, она всё равно не может предоставить некоторые преимущества, которые мы получаем от Kotlin сразу. Kotlin прекрасно подходит для написания лаконичного и выразительного кода с такими классными функциями, как структурированная конкуренция с помощью корутин.
Кроме того, Kotlin предоставляет функции и аннотации, специфичные для платформы Java, а также отличную совместимость с Java. Таким образом, мы можем использовать столько Java, сколько нам нужно, или вообще не использовать её. В этом разделе мы рассмотрим, как Kotlin ориентируется на JVM с помощью Kotlin/JVM.
5.1. Компилятор Kotlin/JVM
Kotlin поставляется с компилятором, который компилирует исходные файлы Kotlin в файлы классов Java, которые можно запускать на любом JVM. Обычно, когда мы строим проект на Kotlin, такой как многоплатформенная библиотека, этот компилятор автоматически используется для создания файлов классов. Однако мы также можем использовать командные инструменты, такие как kotlinc и kotlinc-jvm, чтобы компилировать исходные файлы Kotlin в файлы классов.
Можно смешивать код на Java с Kotlin, хотя делать это стоит только в тех случаях, когда это абсолютно необходимо. В модуле JVM многоплатформенного проекта мы можем писать исходный код Kotlin, используя некоторые библиотеки Java, или даже писать исходный код на Java, если это более оправдано.
Однако компилятор Kotlin компилирует только исходные файлы Kotlin и загружает любые ссылки на Java, если это требуется из исходного каталога. Поэтому нам также нужен компилятор Java для компиляции исходных файлов Java, которые могут быть у нас:
Чтобы позволить многоплатформенному проекту содержать как исходные файлы Java, так и файлы Kotlin, необходимо внести изменения в конфигурацию Gradle:
jvm {
withJava()
}
Также можно генерировать файлы классов для конкретной версии JVM. По умолчанию он ориентирован на версию Java 1.6. Мы можем нацелиться на версию Java, например, 1.8, настроив это в конфигурации Gradle:
jvm {
compilations.all {
kotlinOptions.jvmTarget = "1.8"
}
}
5.2. Разработка и повторное использование кода в Kotlin/JVM
Мы добавим код, специфичный для платформы JVM, в каталоги jvmMain и jvmTest нашего проекта. Первое, что нам нужно предоставить для целевой платформы JVM — это реализация метода writeLogMessage:
internal actual fun writeLogMessage(message: String, logLevel: LogLevel) {
println("Running in JVM: [$logLevel]: $message")
}
Здесь нет ничего особенного, что использует преимущества платформы JVM. Однако обратите внимание, что мы пометили эту функцию с помощью декларации actual.
Мы напишем исходный код на Java для этого простого приложения, чтобы продемонстрировать, как он может сосуществовать с Kotlin. Мы напишем простую функцию, которая предоставляет несколько дополнительных математических операций, используя простые операции из общего модуля:
public static Double square(Double number) {
return CalculatorKt.multiply(number, number);
}
Это простая Java-функция, использующая функцию из общего модуля. Обратите внимание, как мы можем получить доступ к функции add в Kotlin как к статическому методу в Java. Обратите внимание, что мы никогда не создавали класс для обёртки нашей функции add. Но компилятор Kotlin/JVM генерирует класс с именем файла и добавляет функцию как статический метод.
6. Ориентирование на JavaScript с Kotlin/JS
Далее мы рассмотрим, как ориентировать проект Kotlin Multiplatform на платформу JavaScript с использованием Kotlin/JS. Прежде чем углубиться, давайте потратим немного времени на понимание преимуществ использования JavaScript как целевой платформы. JavaScript довольно популярен для написания фронтенд-приложений. Отчасти это связано с тем, что JavaScript поддерживается большинством популярных веб-браузеров с самых ранних версий.
Появление более сложных библиотек и фреймворков, таких как Angular, React и Vue, сделало разработку фронтенд-приложений проще и интуитивно понятнее. Более того, добавление Node.js сделало JavaScript популярным выбором для серверной разработки, по крайней мере, для некоторых сценариев. Очевидно, что использование JavaScript как целевой платформы имеет смысл для разработчиков Kotlin.
JetBrains поддерживает несколько обёрток Kotlin для популярных JavaScript библиотек, таких как React, Mocha и styled-components. Эти обёртки предоставляют удобные абстракции для разработчиков Kotlin для написания типобезопасных фронтенд-приложений на Kotlin. Кроме того, плагины Gradle предоставляют множество полезных функций, таких как управление и упаковка приложения с использованием webpack и добавление зависимостей JavaScript через npm с использованием yarn.
6.1. Компилятор Kotlin/JS
Компилятор Kotlin/JS, поставляемый с Kotlin, преобразует исходные файлы Kotlin в исходный код JavaScript. Мы можем выполнить полученный JavaScript исходный код на любом JavaScript-движке, таком как те, что идут с веб-браузерами или Node.js. Таким образом, компилятор Kotlin/JS можно также назвать транспилятором.
Текущий компилятор Kotlin/JS ориентирован на ECMAScript 5 (ES5) и служит стандартом для JavaScript:
Недавние релизы Kotlin включают альтернативный бэкенд компилятора для Kotlin/JS и Kotlin/JVM, основанный на промежуточном представлении (IR). На данный момент это доступно как альфа-версия начиная с Kotlin 1.4.0. Это позволяет создать единый бэкенд на основе IR для Kotlin/JVM, Kotlin/JS и Kotlin/Native.
По сути, вместо того чтобы генерировать JavaScript код напрямую из исходного кода Kotlin, компилятор IR сначала преобразует исходный код Kotlin в промежуточное представление (IR). Затем компилятор IR компилирует эти промежуточные представления в целевые представления, такие как исходный код JavaScript.
Это позволяет компилятору IR выполнять агрессивные оптимизации и другие задачи, которые были сложными с использованием стандартного компилятора. Например, в случае Kotlin/JS, он генерирует более лёгкие исполнимые файлы с помощью удаления мёртвого кода и может генерировать файлы TypeScript для лучшей совместимости.
Мы можем переключиться с обычного компилятора на компилятор IR с помощью простого изменения в конфигурации Gradle:
kotlin {
js(IR) {
}
}
Как мы уже видели, проекты Kotlin/JS могут ориентироваться на две различные среды выполнения. Это могут быть веб-браузеры для клиентского скриптинга в браузере и Node.js для серверного скриптинга вне браузера.
Выбор среды выполнения для Kotlin/JS снова можно сделать с помощью простого изменения в конфигурации Gradle:
kotlin {
js {
browser {
}
}
}
К счастью, плагин Gradle автоматически настраивает свои задачи для работы с выбранной средой, позволяя нам строить, запускать и тестировать проекты Kotlin/JS без дополнительных настроек.
6.2. Разработка и повторное использование кода в Kotlin/JS
Для нашего простого приложения мы разработаем базовый фронтенд для калькулятора, используя React. Код для целевой платформы JavaScript будет находиться в каталогах jsMain и jsTest.
Как и прежде, первым делом нам нужно добавить реализацию функции writeLogMessage, пометив её декларацией actual:
internal actual fun writeLogMessage(message: String, logLevel: LogLevel) {
when (logLevel) {
LogLevel.DEBUG -> console.log("Running in JS: $message")
LogLevel.WARN -> console.warn("Running in JS: $message")
LogLevel.ERROR -> console.error("Running in JS: $message")
}
}
Затем нам нужно добавить необходимые зависимости для фронтенда в конфигурацию Gradle:
val jsMain by getting {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-html-js:0.7.2")
implementation("org.jetbrains:kotlin-react:16.13.1-pre.110-kotlin-1.4.10")
implementation("org.jetbrains:kotlin-react-dom:16.13.1-pre.110-kotlin-1.4.10")
implementation("org.jetbrains:kotlin-styled:1.0.0-pre.110-kotlin-1.4.10")
}
}
Это обёртки Kotlin для использования React в Kotlin.
Для начала в React нам нужно простое HTML-файл, который будет являться якорем для корневого элемента, к которому React будет привязан:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JS Client</title>
</head>
<body>
<script src="kotlin-multiplatform.js"></script>
<div id="root"></div>
</body>
</html>
Затем мы напишем простую функцию для рендеринга нашего React-приложения на этом корневом элементе:
fun main() {
window.onload = {
render(document.getElementById("root")) {
child(Calculator::class) {
attrs {
value = "0"
}
}
}
}
}
Поскольку мы будем передавать свойства нашему компоненту и ожидаем, что он будет поддерживать состояние, нам нужно определить их в Kotlin:
external interface CalculatorProps : RProps {
var value: String
}
data class CalculatorState(val value: String) : RState
Наконец, нам нужно написать класс Calculator, который определит компонент, который мы хотим загрузить:
@JsExport
class Calculator(props: CalculatorProps) : RComponent<CalculatorProps, CalculatorState>(props) {
init {
state = CalculatorState(props.value)
}
override fun RBuilder.render() {
styledLabel {
css {
}
+ "Enter a Number: "
}
styledInput {
css {
}
attrs {
type = InputType.number
value = state.value
onChangeFunction = { event ->
setState(
CalculatorState(value = (event.target as HTMLInputElement).value)
)
}
}
}
styledDiv {
css {
}
+"Square of the Input: ${
multiply(state.value.toDouble(), state.value.toDouble())}"
}
}
}
Это простой компонент React, который определяет ввод и обновляет локальное состояние с тем значением, которое мы вводим. Далее он использует функцию product, которую мы имеем в общем модуле, для вычисления квадрата и отображения его в компоненте.
Плагин Gradle поддерживает webpack-dev-server для обслуживания сгенерированных JavaScript-артефактов. Мы можем запустить следующую задачу для запуска проекта Kotlin/JS:
gradlew jsRun
Затем мы можем получить доступ к нашему простому приложению через любой браузер:
Мы сознательно оставили без внимания стилизацию, но Kotlin/JS полностью поддерживает CSS, и мы можем создать любую интерфейсную часть, которую захотим. Мы сделаем простое изменение в конфигурации Gradle, чтобы включить поддержку CSS и стилей в webpack:
browser {
commonWebpackConfig {
cssSupport.enabled = true
}
}
7. Ориентирование на Native с Kotlin/Native
Нативные платформы, вероятно, являются самыми разнообразными и сложными для поддержки в Kotlin. Kotlin/Native пытается скомпилировать исходный код Kotlin прямо в нативные бинарники, специфичные для поддерживаемой целевой платформы. Но в чём же преимущество такой работы?
Представьте, что мы разрабатываем десктопное приложение, которое мы намерены запускать на Linux, Windows или macOS. Конечно, один из способов — это разрабатывать их отдельно для каждой платформы, но несложно понять, какой это будет трудозатратный процесс. Мы также можем разработать приложение для работы на виртуальной машине, например, JVM, но тогда нам нужно, чтобы такая машина была доступна на всех нативных платформах.
Не было бы замечательно, если бы мы могли просто написать приложение один раз и сгенерировать нативные бинарники для каждой платформы, чтобы запускать их везде без зависимостей? Это и обещает обеспечить Kotlin/Native для нескольких нативных платформ.
Кроме того, это можно применить ко многим другим сценариям, например, для разработки мобильного приложения для нескольких платформ. Одно мобильное приложение, работающее на Android и iOS, может сэкономить нам много усилий для изучения множества платформенных библиотек и их поддержания с течением времени.
7.1. Компилятор Kotlin/Native
Kotlin/Native предоставляет бэкенд на основе LLVM для компилятора Kotlin/Native и нативные реализации стандартной библиотеки Kotlin. Сам компилятор Kotlin/Native называется Konan. LLVM — это инфраструктура компилятора, которую мы можем использовать для разработки фронтенда для любого языка программирования и бэкенда для любой архитектуры набора команд.
LLVM предоставляет портативный, высокоуровневый ассемблерный язык, оптимизированный для различных преобразований, которые служат как независимое от языка промежуточное представление. Изначально реализованный для C и C++, сегодня существует несколько языков с компиляторами, поддерживающими LLVM, включая Kotlin:
Kotlin/Native поддерживает несколько платформ, которые мы можем удобно выбрать через конфигурацию Gradle:
- Linux (x86_64, arm32, arm64, MIPS, MIPS little-endian)
- Windows (mingw x86_64, x86)
- Android (arm32, arm64, x86, x86_64)
- iOS (arm32, arm64, simulator x86_64)
- macOS (x86_64)
- tvOS (arm64, x86_64)
- watchOS (arm32, arm64, x86)
- WebAssembly (wasm32)
Теперь стоит отметить, что в нашей конфигурации Gradle имеется проверка операционной системы хоста, чтобы определить, поддерживается ли она:
kotlin {
val hostOs = System.getProperty("os.name")
val isMingwX64 = hostOs.startsWith("Windows")
val nativeTarget = when {
hostOs == "Mac OS X" -> macosX64("native")
hostOs == "Linux" -> linuxX64("native")
isMingwX64 -> mingwX64("native")
else -> throw GradleException("Host OS is not supported in Kotlin/Native.")
}
}
При разработке многоплатформенного проекта с использованием шаблона проекта IntelliJ IDEA это уже настроено по умолчанию.
7.2. Разработка и повторное использование кода в Kotlin/Native
Как мы видели ранее, разработка многоплатформенных мобильных приложений — это, возможно, один из основных случаев использования Kotlin/Native. Однако разработка и тестирование самих приложений для Android или iOS требует более детального рассмотрения нюансов. Поэтому мы ограничим наше приложение простым приложением командной строки для платформы Windows.
Ранее мы разработали пользовательский интерфейс для вычисления квадрата числа, используя простые операции из общего модуля. Логично расширить это и создать приложение командной строки для выполнения той же задачи.
Следуя принятой конвенции, код для целевой платформы Native будет находиться в каталогах nativeMain и nativeTest. Первое, что нам нужно сделать, это добавить реализацию функции writeLogMessage для платформы Native, пометив её декларацией actual:
internal actual fun writeLogMessage(message: String, logLevel: LogLevel) {
println("Running in Native: [$logLevel]: $message")
}
Затем нам нужно определить точку входа для нашего приложения в конфигурации Gradle:
gradleКопироватьnativeTarget.apply {
binaries {
executable {
entryPoint = "com.baeldung.kotlin.multiplatform.main"
}
}
}
Наконец, нам нужно определить основную функцию, которая будет запускать наше приложение командной строки:
fun main() {
println("Enter a Number:")
val number = readLine()!!.toInt()
println("Square of the Input: ${multiply(number, number)}")
}
Здесь нет ничего особенного, так как мы используем функцию product, которую мы ранее определили в общем модуле.
Мы можем увидеть это в действии, выполнив сгенерированный исполнимый файл через командную строку в Windows:
Аналогично, если мы соберем это приложение на другой платформе, такой как Linux или macOS, оно сгенерирует исполнимый файл для этих платформ, который мы можем запускать нативно без других зависимостей.
8. Заключение
В этом уроке мы изучили основы многоплатформенной разработки и то, как Kotlin поддерживает этот подход. Мы разработали простой многоплатформенный проект с использованием шаблонов проектов IntelliJ IDEA. Это позволило нам создать общий модуль на Kotlin.
Далее мы повторно использовали код из этого общего модуля в Kotlin/JS для разработки интерфейса пользователя на базе React и в Kotlin/Native для разработки приложения командной строки для Windows.
Оригинал статьи: https://www.baeldung.com/kotlin/multiplatform-programming