Время на прочтение9 мин
Количество просмотров5.9K
Всем привет! Меня зовут Амет Хырхара, я Android разработчик в компании Joy Dev.
Первое, в чём может возникнуть затруднение у Android разработчика при переходе на КММ — это быстрая настройка окружения. Официальный сайт Kotlin не пестрит подробными инструкциями, и можно потратить несколько часов в поисках нужной информации. В данной статье мы пройдёмся по этапам настройки проекта.
Что такое КММ
КММ, или Kotlin Multiplatform Mobile, это технология кроссплатформенной разработки, которая даёт возможность объединить общую бизнес-логику приложения (к примеру, запросы на сервер, обработка данных, работа с БД) в отдельный модуль и при этом использовать нативный UI.
Настройка на Windows
Для того, чтобы создать КММ-проект, нам понадобится зайти в Android Studio, пройти по пути: File -> Settings -> Plugins, выбрать Marketplace и ввести в поисковую строку название инструмента. После этого установить плагин.
Когда плагин установлен, при создании проекта у вас появятся следующие опции. Выбираем первый вариант.
При создании в параметре ios framework distribution выбираем Regular Framework.
На этом всё, можно начинать писать. Простота установки окупается невозможностью полноценно писать КММ проекты.
MacOS
Чтобы в полную силу работать с КММ: редактировать специфический для iOS-платформ код и в последующем проводить отладку – необходимо устройство от Apple.
Для начала нам необходимо установить Homebrew — это package manager, который позволяет устанавливать недостающие пакеты и библиотеки на macOS и Linux. Открываем официальный сайт и, согласно инструкции, копируем команду, далее вставляем её в терминал — аналог командной строки.
После установки вводим следующую команду: brew install kdoctor
— инструмент командной строки, который проверяет вашу систему на наличие нужных файлов и их корректные версии для работы с КММ.
После установки вводим команду kdoctor
и ждём результат. Ответ должен быть примерно такой:
То есть, инструмент проверяет:
1) Систему. Если она не подходит, то, увы, не судьба.
2) Наличие JDK.
3) Наличие Android Studio и плагина КММ (устанавливаем, как в Windows)
4) Наличие xCode.
5) Наличие Cocoapods и его совместимость с Ruby.
Проблемы, связанные с первыми четырьмя пунктами, описаны здесь. На моём опыте проблемы возникли с Cocoapods и его совместимостью с Ruby, так как это не освещено в официальной документации. Но, прежде чем начать, давайте разберём, что такое cocoapods, Ruby, а также rvm и rbenv.
Ruby – язык программирования, на котором написаны некоторые библиотеки, используемые в проекте.
Cocoapods – менеджер зависимостей (dependency manager), который, в частности, написан на Ruby. Там прописываются наши зависимости (примерно, как в gradle). Список зависимостей обычно указывается в Podfile.
rvm и rbenv – инструменты, которые позволяют нам управлять версиями Ruby, обновлять или делать откаты.
После того, как kdoctor проанализировал систему, он подскажет, какую версию Ruby нам нужно установить (подсказки начинаются с *).
Чтобы установить Ruby нужной версии, пишем в терминал brew install ruby@2.7
Для установки cocoapods используйте команды sudo gem install cocoapods
и sudo gem install cocoapods-generate
Если ваша версия Kotlin меньше, чем 1.7.0, то Cocoapods-generate не установятся на Ruby версии 3.0.0 и выше.
Одна из возможных ошибок, которая может возникнуть на данном этапе, это совместимость версий, потому что КММ стабильно работает на версиях ниже текущих, а также версия Ruby, установленная по умолчанию, может отличаться от нужной нам (проверить, какая версия Ruby используется у нас, можно с помощью команды ruby -v).
Установка искомой версии Ruby. Здесь у нас есть два пути: rbenv и rvm.
rbenv
1) Устанавливаем с помощью Homebrew brew install rbenv ruby-build
2) Используем rbenv install –list
, чтобы увидеть список доступных версий, выбираем нужную
3) Устанавливаем версию rbenv install <версия>
4) Ставим её по умолчанию rbenv global <версия>
rvm
1) Установка \curl -sSL https://get.rvm.io | bash -s stable
2) Список версий rvm list known
3) Установка конкретной версии rvm install <версия>
4) Установка версии по умолчанию rvm use <версия> --default
После очередной работы kdoctor видим долгожданную надпись: Your system is ready for Kotlin Multiplatform Mobile Development! Можем создавать проект.
Использование Kotlin Multiplatform Wizard
Существует другой способ создания КММ-проекта: использовать Kotlin Multiplatform Wizard. Преимуществом будет выступать возможность автоматического подключения базовых библиотек, плагинов и выбор платформ, с которыми вы хотите работать (при обычном создании появляются только androidApp и iosApp). Затем скачать архив и открыть проект.
Обзор созданного проекта
После загрузки всех компонентов рекомендуем переключиться с вида Android на Project, чтобы видеть всю файловую систему проекта.
Модули и их предназначение
На этом этапе уже видно, что проект отличается от привычного. Вместо одного app модуля у нас создались три: androidApp, iosApp и shared. Если мы откроем shared/src, то увидим ещё три модуля – androidMain, commonMain, iosMain.
Давайте разбираться.
androidApp – здесь мы прописываем наши UI элементы (Android View или Jetpack Compose) и логику, которые присущи исключительно Android и не имеют альтернативы на других платформах. Например, Activity или Broadcast Receiver – в iOS нет таких сущностей в подобном виде, поэтому они могут использоваться только в androidApp модуле. Также в директории мы видим build.gradle.kts, где будем указывать зависимости исключительно для Android (фрагменты, навигация и т.п.), и тут же подключается shared модуль, который мы рассмотрим далее: implementation(project(":shared")).
iosApp – аналогия androidApp для iOS. Для того, чтобы редактировать код в этом модуле, понадобится xCode. Чтобы открыть проект на xCode, запускаем среду разработки, кликаем Open a project or file, в директории вашего проекта заходим в iosApp и открываем файл iosApp.xcodeproj.
Если вы изначально создавали проект на Windows, а затем перешли на macOS, у вас скорее всего появится ошибка gradlew Permission denied. Для её решения в терминале Android Studio следует ввести следующую команду chmod +x gradlew
shared – здесь мы описываем бизнес-логику приложения: от запросов на сервер до вью моделей. Основная часть кода будет расположена в commomMain.
Два других модуля нужны для написания логики, которая присуща обеим платформам, но немного отличается реализацией.
Например, в Android при использовании вью моделей мы наследуемся от класса ViewModel(), но в iOS этого делать не нужно. Поэтому в commomMain создаём директорию presentation, а в ней — абстрактный класс CommonViewModel. Добавляем ключевое слово expect (перед abstract), которое означает, что ожидается имплементация этого класса на каждой из платформ, в данном случае – androidMain и iosMain.
commonMain:
expect abstract class CommonViewModel()
В этих модулях мы тоже создаем папку presentation и абстрактный класс CommonViewModel, но expect заменяем на actual, означающее, что данный класс будет имплементирован. Перед конструктором также придётся прописать ключевое слово actual constructor. В iosMain мы оставим так, а в Android пронаследуемся от ViewModel().
iosMain:
expect abstract class CommonViewModel() actual constructor()
androidMain:
actual abstract class CommonViewModel actual constructor() : ViewModel()
Должно получиться как-то так:
Теперь при создании вью модели мы будем наследоваться от CommonViewModel и дёргать реализацию в соответствии с платформой, на которой мы её используем.
Использование expect-actual механизма легче понять, если воспринимать его как интерфейсы. Expect — это, по сути, интерфейс, который мы «переопределяем» (actual) в зависимости от ситуации.
В shared модуле также есть свой build.gradle.kts, где мы будем указывать наши общие зависимости. Обращаем внимание на sourceSets – здесь мы видим перечень знакомых нам модулей и их копий с суффиксом Test.
Зависимости, размещённые в commonMain, будут относиться ко всем целевым платформам. В androidMain – к Android. В iosMain – к iosX64Main, iosArm64Main, iosSimulatorArm64Main (на это намекает метод dependsOn()). При подключении библиотек путаница не возникает, потому что на официальных порталах всегда детально указывается, как их следует подключать. Следующим этапом мы разберём удобную технологию управления зависимостями в КММ.
Управление зависимостями с помощью buildSrc
По мере разрастания проекта и увеличения числа подключаемых библиотек возникают трудности с мониторингом версий и дублированием. В таком случае хорошим решением будет поместить все имеющие зависимости в отдельный модуль. Таким модулем может выступить buildSrc – специальная библиотека, которая подключается к Gradle-проекту (само название также зарезервировано Gradle-ом) и при сборке проекта компилируется первой.
Чтобы создать buildSrc модуль, на уровне проекта добавляем папку с соответствующим названием. Внутри создаём build.gradle.kts файл, вставляем следующий код и жмём sync.
repositories {
mavenCentral()
}
plugins {
`kotlin-dsl`
}
После этого в buildSrc модуле создаём директорию src/main/kotlin – если синхронизация прошла успешно, она должна появиться в предложенных. Далее в папке kotlin создаем файл Dependencies.kt. Здесь мы и будем хранить наши зависимости.
В файле создаём три объекта: Plugins, Versions, Deps (внутри него для удобства можно отдельно создать объекты Android и Multiplatform) и вставляем следующий код:
В Plugins указываем базовые плагины:
object Plugins {
const val androidApp = "com.android.application"
const val android = "android"
const val multiplatform = "multiplatform"
const val androidLib = "com.android.library"
}
В Versions указываем версии SDK, подключаемых плагинов и библиотек:
object Versions {
// SDK
const val compileSdk = 32
const val targetSdk = 32
const val minSdk = 21
// Plugins
const val android_version = "7.3.1"
const val kotlin_version = "1.7.10"
const val compose_version = "1.2.1"
const val compose_activity_version = "1.5.1"
// Coroutines
const val coroutines_version = "1.6.4"
}
В Deps прописываем зависимости для наших библиотек, используя ранее введённые версии:
Gradle-плагины для Kotlin и Android:
const val kotlin_gradle_plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin_version}"
const val android_gradle_plugin = "com.android.tools.build:gradle:${Versions.android_version}"
Зависимости для Android:
object Android {
// Compose
const val compose_ui = "androidx.compose.ui:ui:${Versions.compose_version}"
const val compose_ui_tooling = "androidx.compose.ui:ui-tooling:${Versions.compose_version}"
const val compose_ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:${Versions.compose_version}"
const val compose_foundation = "androidx.compose.foundation:foundation:${Versions.compose_version}"
const val compose_material = "androidx.compose.material:material:${Versions.compose_version}"
const val compose_activity = "androidx.activity:activity-compose:${Versions.compose_activity_version}"
// Coroutines
const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines_version}"
}
Зависимости для КММ:
object Multiplatform {
// Coroutines
const val coroutines_core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines_version}"
}
Примечание: в статье используются зависимости, которые подключаются при создании проекта, кроме корутин, которые специально взяты для демонстрации их включения в shared модуль.
Следующий шаг — преобразовать build.gradle.kts
файлы нашего проекта. Открываем корневой файл и заменяем строки на наши переменные.
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies {
classpath(Deps.android_gradle_plugin)
classpath(Deps.kotlin_gradle_plugin)
}
}
В build.gradle.kts (:shared) обновляем плагины:
plugins {
kotlin(Plugins.multiplatform)
id(Plugins.androidLib)
}
Примечание: ключевое слово kotlin используется для автоматического добавления префикса org.jetbrains.kotlin в название плагина. То есть, выражения id(org.jetbrains.kotlin.multiplatform)
и kotlin(multiplatform)
тождественны.
Зависимости:
sourceSets {
val commonMain by getting {
dependencies {
implementation(Deps.Multiplatform.coroutines_core)
}
}
...
}
Так как мы добавили библиотеку корутин в commonMain, мы можем её использовать как для Android, так и для iOS.
SDK:
android {
namespace = "com.example.newkmm"
compileSdk = Versions.compileSdk
defaultConfig {
minSdk = Versions.minSdk
targetSdk = Versions.targetSdk
}
}
В build.gradle.kts (:android):
plugins {
id(Plugins.androidApp)
kotlin(Plugins.android)
}
android {
namespace = "com.example.newkmm.android"
compileSdk = Versions.compileSdk
defaultConfig {
applicationId = "com.example.newkmm.android"
minSdk = Versions.minSdk
targetSdk = Versions.targetSdk
versionCode = 1
versionName = "1.0"
}
...
}
dependencies {
implementation(project(":shared"))
implementation(Deps.Android.compose_ui)
implementation(Deps.Android.compose_ui_tooling)
implementation(Deps.Android.compose_ui_tooling_preview)
implementation(Deps.Android.compose_foundation)
implementation(Deps.Android.compose_material)
implementation(Deps.Android.compose_activity)
}
Синхронизируем проект. Во вкладке Build убеждаемся, что сначала выполняются таски, связанные с buildSrc, а затем таски основного проекта.
КММ изначально собирается дольше, чем обычный Android проект, поэтому открываем в gradle.properties и добавляем следующие строки:
org.gradle.caching=true // 1
org.gradle.parallel=true // 2
org.gradle.daemon=true // 3
1 – включаем кеширование.
2 – включаем параллельную сборку не взаимосвязанных задач, так как по умолчанию Gradle выполняет одну задачу за раз. Фича особенно полезна в КММ.
3 – Gradle Daemon по умолчанию включен, но желательно указывать его явно. Этот компонент занимается кешированием, мониторингом файловой системы для определения необходимых для билда файлов и держит JVM в «прогретом» состоянии.
После этого проект будет собираться значительно быстрее.
Итог
Подготовка системы к кроссплатформенной разработке, которая от пользователей Windows требует пары минут, может занять продолжительное время для владельцев macOS, поэтому мы постарались подробно описать, где и какие инструменты можно использовать для достижения цели, а также устранили возможную путаницу с несовместимостью версий.
Проект на старте имеет более сложную файловую систему и изначально состоит из большего числа модулей. В shared модуле мы описываем общую логику, которую имеет приложение и по случаю прибегаем к expect/actual механизму (который легко воспринимать в качестве интерфейса), когда сущности платформы частично отличаются реализацией.
Для централизованного управления зависимостями можно использовать Gradle библиотеку buildSrc, которая позволяет хранить все зависимости и версии в одном файле, разбив их на группы, что также улучшает читабельность кода. При сборке этот модуль компилируется первым, поэтому проблем на этом этапе не возникает. Существует также альтернатива в виде композитной сборки, использовать её следует по ситуации. Об этом есть статья.
Теперь, когда самое скучное позади, можем приступать к написанию логики самого проекта.
Настройте среду
Прежде чем создать свое первое приложение, работающее как на iOS, так и на Android, вам необходимо настроить среду для разработки многоплатформенных мобильных приложений Kotlin .
Мы рекомендуем вам установить последние стабильные версии для обеспечения совместимости и лучшей производительности.
Tool |
Comments |
---|---|
Android Studio |
Вы будете использовать Android Studio для создания многоплатформенных приложений и запуска их на имитируемых или аппаратных устройствах. |
Xcode |
Большую часть времени Xcode будет работать в фоновом режиме. Вы будете использовать его для добавления кода Swift или Objective-C в ваше приложение iOS. |
JDK |
Чтобы проверить, установлен ли он, выполните следующую команду в терминале Android Studio или в командной строке: java -version |
Kotlin Многоплатформенный мобильный плагин |
В Android Studio выберите «Настройки/Предпочтения» | «Плагины», найдите в Marketplace Kotlin Multiplatform Mobile и установите его. |
Kotlin plugin |
Плагин Kotlin поставляется с каждым релизом Android Studio. Однако его все равно необходимо обновить до последней версии, чтобы избежать проблем с совместимостью. Чтобы обновить плагин, на экране приветствия Android Studio выберите Plugins | Installed. Нажмите Update рядом с Kotlin . Вы также можете проверить версию Kotlin в Tools | Kotlin | Configure Kotlin Plugin Updates. Плагин Kotlin должен быть совместим с плагином Kotlin Multiplatform Mobile. См. compatibility table . |
Проверьте свое окружение
Чтобы убедиться, что все работает так, как и ожидалось, установите и запустите инструмент KDoctor:
-
В терминале Android Studio или в инструменте командной строки выполните следующую команду, чтобы установить инструмент с помощью Homebrew:
brew install kdoctor
Если у вас еще нет Homebrew, install it или см. KDoctor README для других способов его установки.
-
После завершения установки вызовите KDoctor в консоли:
kdoctor
-
Если KDoctor обнаружит какие-либо проблемы при проверке вашей среды, просмотрите выходные данные на предмет проблем и возможных решений:
-
Исправьте все неудачные проверки (
[x]
). Описания проблем и возможные решения можно найти после символа*
. -
Проверьте предупреждения (
[!]
) и успешные сообщения ([v]
). Они также могут содержать полезные заметки и советы.
-
Возможные проблемы и решения
- Android Studio
-
Убедитесь, что у вас установлена Android Studio. Вы можете получить ее из official website.
- Java и JDK
-
-
Убедитесь, что у вас установлен JDK. Его можно получить из official website .
-
Android Studio использует связанный JDK для выполнения задач Gradle. Чтобы настроить Gradle JDK в Android Studio, выберите Settings/Preferences | Build, Execution, Deployment | Build Tools | Gradle.
-
Вы можете столкнуться с проблемами, связанными с
JAVA_HOME
. Эта переменная среды указывает местоположение двоичного файла Java, необходимого для Xcode и Gradle. Если это так, следуйте советам KDoctor, чтобы исправить проблемы.
-
- Xcode
-
-
Убедитесь, что у вас установлен Xcode. Его можно получить из official website .
-
Запустите Xcode в отдельном окне, чтобы принять условия его лицензии и разрешить ему выполнить некоторые необходимые начальные задачи.
-
Error: can't grab Xcode schemes
. Если вы столкнулись с такой ошибкой, в Xcode выберите Settings/Preferences | Locations. В поле Command Line Tools выберите ваш Xcode.
-
- Kotlin plugins
-
Kotlin Многоплатформенный мобильный плагин
-
Убедитесь, что плагин Kotlin Mobile Multiplatform установлен и включен. На экране приветствия Android Studio выберите Plugins | Installed. Убедитесь, что плагин включен. Если его нет в списке Installed, найдите его в Marketplace и установите.
-
Если плагин устарел, нажмите Обновить рядом с названием плагина. То же самое можно сделать в разделе Настройки/Предпочтения | Инструменты | Плагины.
-
Проверьте совместимость плагина Kotlin Multiplatform Mobile с вашей версией Kotlin в таблице Release details .
Kotlin plugin
Убедитесь, что плагин Kotlin обновлен до последней версии. Для этого на экране приветствия Android Studio выберите Plugins | Installed. Нажмите Update рядом с Kotlin .
Вы также можете проверить версию Kotlin в разделе Инструменты | Kotlin | Настроить обновления плагина Kotlin .
-
- Command line
-
Убедитесь, что у вас установлены все необходимые инструменты:
-
command not found: brew
— install Homebrew . -
command not found: java
— install Java .
-
Next step
В следующей части урока вы создадите свое первое кроссплатформенное мобильное приложение.
Proceed to the next part
Get help
-
Kotlin Slack. Получите invite и присоединяйтесь к каналу #multiplatform .
-
Kotlin трекер проблем. Report a new issue .
Последнее изменение: 11 апреля 2023 г.
Kotlin Multiplatform Mobile находится в стадии альфа-тестирования. Особенности языка и
инструментарий могут быть изменены в будущих версиях Kotlin.Бета версия ожидается весной 2022 г. Посмотрите “Ключевые моменты видео Kotlin Multiplatform Mobile Beta Roadmap”,
чтобы узнать о планируемом функционале. Также вы можете изучить, как разные компании
уже используют Kotlin для разработки кроссплатформенного программного обеспечения.
Kotlin Multiplatform Mobile (KMM) — это средства разработки ПО, предназначенные для упрощения разработки
кроссплатформенных приложений для мобильных устройств. Вы можете использовать общий код в программах для iOS и Android и
писать код, специфичный для каждой из платформ, только там, где это необходимо. Например, при реализации нативного UI
или при работе с API, которые ограничены конкретной платформой.
Посмотрите вводное видео, в котором менеджер по продуктовому маркетингу
Kotlin Екатерина Петрова объясняет, что такое Kotlin Multiplatform Mobile и как использовать его в вашем проекте. Вместе
с ней вы настроите среду разработки и подготовитесь к созданию вашего первого кроссплатформенных мобильного приложения.
Также вы можете посмотреть другие видео о Kotlin Multiplatform Multiverse
на YouTube.
Поддерживаемые платформы
- Приложения и библиотеки для Android,
- Android NDK (ARM64 и ARM32),
- Устройства Apple iOS (ARM64 и ARM32) и симуляторы,
- Устройства Apple watchOS (ARM64 и ARM32) и симуляторы.
Технология Kotlin Multiplatform также поддерживает другие платформы,
включая JavaScript, Linux, Windows, и WebAssembly.
Начало с нуля
- Настройте окружение для кроссплатформенной мобильной разработки,
- Создайте в IDE ваше первое приложение, работающее и на Android, и на iOS,
- Изучите примеры проектов,
- Познакомьте свою команду с кроссплатформенной мобильной разработкой.
Настройте ваше Android приложение для работы на iOS
Если у вас уже есть мобильное Android приложение и вы хотите сделать его кроссплатформенным, то вот что вам может помочь
начать:
- Настройте окружение для кроссплатформенной мобильной разработки,
- Сделайте так, чтобы пример Android приложения, хорошо работал под iOS.
Получить помощь
- Kotlin Slack: получите приглашение и присоединитесь к
каналу #multiplatform; - StackOverflow: подпишитесь на метку «kotlin-multiplatform»;
- Kotlin issue tracker: сообщите о новой проблеме.
В настоящее время мы переживаем бум появления новых технологий и подходов к написанию мобильных приложений. Одной из них является развивающийся SDK от компании JetBrains для мультиплатформенной разработки Kotlin Multiplatfrom (KMP) .
Основная идея KMP, как и других кросс-платформенных SDK — оптимизация разработки путем написания кода один раз и последующего его использования на разных платформах.
Согласно концепции JetBrains, Kotlin Multiplatform не является фреймворком. Это именно SDK, который позволяет создавать модули с общим кодом, подключаемые к нативным приложениям.
Написанный на Kotlin модуль компилируется в JVM байткод для Android и LLVM байткод для iOS.
Этот модуль (Shared, Common) содержит переиспользуемую бизнес-логику. Платформенные модули iOS/Android, к которым подключен Shared/Common, либо используют написанную логику напрямую, либо имплементируют свою реализацию в зависимости от особенностей платформы.
Общая бизнес-логика может включать в себя:
- сервисы для работы с сетью;
- сервисы для работы с БД;
- модели данных.
Также в нее могут входить архитектурные компоненты приложения, напрямую не включающие UI, но с ним взаимодействующие:
- ViewModel;
- Presenter;
- Интеракторы и т.п.
Концепцию Kotlin Multiplatform можно сравнить с реализацией Xamarin Native. Однако, в KMP нет модулей или функционала, реализующих UI. Эта логическая нагрузка ложится на подключенные нативные проекты.
Рассмотрим подход на практике и попробуем написать наше первое приложение Kotlin Multiplatform.
Для начала нам потребуется установить и настроить инструменты:
- Android Sdk
- Xcode с последним iOS SDK.
- Intelij IDEA CE или Android Studio. Обе IDE позволяют создавать и настраивать проекты для Kotlin Multiplatform. Но если в Intelij IDEA проект создается автоматически, то в Android Studio большую часть настроек надо сделать вручную. Если вам привычнее работать именно с Android Studio, то подробное руководство по созданию проекта можно посмотреть в документации на Kotlinlang.org
Мы рассмотрим создание проекта с помощью Intelij IDEA.
Выбираем меню File → New → Create Project:
В появившемся окне выбираем тип проекта Kotlin → Mobile Android/iOS|Gradle
Далее стандартно задаем путь к JDK, имя и расположение проекта
После нажатия кнопки Finish проект сгенерируется и будет почти готов к работе.
Рассмотрим, что у нас получилось:
Мультиплатформенные проекты Kotlin обычно делятся на несколько модулей:
- модуль переиспользуемой бизнес-логики (Shared, commonMain и т.п);
- модуль для IOS приложения (iOSMain, iOSTest);
- модуль для Android приложения (androidMain, androidTest).
В них располагается наша бизнес-логика. Сам код базового примера мы разберем немного позже.
Код нативного Android приложения располагается в каталоге main, как если бы мы создавали проект по шаблону обычного Android.
iOS приложение создается автоматически и располагается в каталоге iOSApp:
Перед тем, как мы проверим работоспособность базового решения, необходимо сделать следующие финальные настройки:
В local.properties зададим путь к SDK Android:
Создадим конфигурацию для работы Android приложения:
Готово.
Теперь вызовем команду gradle wrapper
для сборки нашего модуля общей логики:
После сборки модуль для бизнес-логики для Android приложения доступен в app/build/libs:
Путь к библиотеке прописывается стандартно, в блоке dependencies
файла build.gradle:
Теперь наш проект сконфигурирован для запуска Android приложения:
Осталось сделать настройки для запуска приложения iOS.
В файле build.gradle(:app) необходимо изменить настройку архитектура проекта, чтобы наше приложение поддерживало как реальные устройства, так и эмуляторы.
Меняем:
На
После выполнения сборки создастся фреймворк в app/build/bin/ios:
Intelij IDEA автоматически создает в gradle файле код для генерации, подключения и встраивания фреймворка в IOS проект:
При ручной настройке проекта (например, через Android Studio) этот код потребуется указать самостоятельно.
После синхронизации gradle iOS проект готов к запуску и проверке с помощью XCode.
Проверяем, что у нас получилось. Открываем проект iOS через iosApp.xcodeproj:
Проект имеет стандартную структуру, за исключением раздела app, где мы получаем доступ к коду наших модулей на Kotlin.
Фреймворк действительно подключен автоматически во всех соответствующих разделах проекта:
Запускаем проект на эмуляторе:
Теперь разберем код самого приложения на базовом примере.
Используемую в проекте бизнес-логику можно разделить на:
- переиспользуемую (общую);
- платформенную реализацию.
Переиспользуемая логика располагается в проекте commonMain в каталоге kotlin и разделяется на package. Декларации функций, классов и объектов, обязательных к переопределению, помечаются модификатором expect
:
Реализация expect
-функционала задается в платформенных модулях и помечается модификатором actual
:
Вызов логики производится в нативном проекте:
Все очень просто.
Теперь попробуем по тем же принципам сделать что-то посложнее и поинтереснее. Например, небольшое приложение для получения и отображение списка новостей для iOS и Android.
Приложение будет иметь следующую структуру:
В общей части (Common) расположим бизнес-логику:
- сетевой сервис;
- сервис для запросов новостей.
В модулях iOS/Android приложений оставим только UI компоненты для отображения списка и адаптеры. iOS часть будет написана на Swift, Android – на Kotlin. Здесь в плане работы не будет ничего нового.
Организуем архитектуру приложений по простому паттерну MVP. Презентер, обращающийся к бизнес-логике, также вынесем в Common часть. Также поступим и с протоколом для связи между презентером и экранами UI:
interface INewsListView :IView {
fun setupNews(list: List)
}
Начнем с бизнес-логики. Т.к весь функционал будет в модуле common, то мы будем использовать в качестве библиотек решения для Kotlin Multiplatform:
1. Ktor – библиотека для работы с сетью и сериализации.
В build.gradle (:app) пропишем следующие зависимости:
commonMain {
dependencies {
…
implementation("io.ktor:ktor-client-core:1.3.2")
implementation("io.ktor:ktor-client-json:1.3.2")
implementation("io.ktor:ktor-client-serialization:1.3.2")
}
}
androidMain {
dependencies {
…..
implementation("io.ktor:ktor-client-android:1.3.2")
implementation("io.ktor:ktor-client-json-jvm:1.3.2")
implementation("io.ktor:ktor-client-serialization-jvm:1.3.2")
}
}
iosMain {
dependencies {
//….
implementation("io.ktor:ktor-client-ios:1.3.2")
implementation("io.ktor:ktor-client-json-native:1.3.2")
implementation("io.ktor:ktor-client-serialization-native:1.3.2")
}
}
Также добавим поддержку плагина сериализации:
plugins {
….
id 'org.jetbrains.kotlin.plugin.serialization' version '1.3.72'
}
apply plugin: 'kotlinx-serialization'
2. Kotlin Coroutines – для организации многопоточной работы.
commonMain {
dependencies {
…
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.7")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:0.14.0")
….
}
}
androidMain {
dependencies {
…
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime:0.14.0")
….
}
}
iosMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:1.3.5-native-mt")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:0.14.0")
….
}
}
При добавлении зависимости в iOS проект обратите внимание, что версия библиотеки должна быть обязательно native-mt и совместима с версией плагина Kotlin multiplatform.
При организации многопоточности с помощью Coroutines необходимо передавать контекст потока (CoroutineContext
), в котором логика будет исполняться. Это платформозависимая логика, поэтому используем кастомизацию с помощью expect
/actual
.
В commonMain создадим Dispatchers.kt, где объявим переменные:
expect val defaultDispatcher: CoroutineContext
expect val uiDispatcher: CoroutineContext
Реализация в androidMain создается легко. Для доступа к соответствующим потокам используем CoroutineDispatchers Main
(UI поток) и Default
(стандартный для Coroutine
):
actual val uiDispatcher: CoroutineContext
get() = Dispatchers.Main
actual val defaultDispatcher: CoroutineContext
get() = Dispatchers.Default
С iOS труднее. Та версия Kotlin Native LLVM компилятора, которая используется в Kotlin Multiplatform, не поддерживает background очереди. Это давно известная проблема, которая к сожалению, еще не исправлена
Поэтому попробуем обходной маневр как временное решение проблемы.
actual val uiDispatcher: CoroutineContext
get() = MainDispatcher
actual val defaultDispatcher: CoroutineContext
get() = MainDispatcher
private object MainDispatcher: CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatch_async(dispatch_get_main_queue()) {
try {
block.run().freeze()
} catch (err: Throwable) {
throw err
}
}
}
}
Мы создаем свой CoroutineDispatcher
, где прописываем выполнение логики в асинхронной очереди dispatch_async
.
Также нам понадобится свой scope для работы сетевого клиента:
iOS
actual fun ktorScope(block: suspend () -> Unit) {
GlobalScope.launch(MainDispatcher) { block() }
}
Android
actual fun ktorScope(block: suspend () -> Unit) {
GlobalScope.launch(Dispatchers.Main) { block() }
}
Применим это при реализации сетевого клиента на Ktor:
interface INetworkService {
suspend fun getData(path: String, serializer: KSerializer,completed: (ContentResponse)->Unit)
}
class NetworkService : INetworkService{
private val httpClient = HttpClient()
override suspend fun getData(path: String, serializer: KSerializer,completed: (ContentResponse)->Unit){
//Для ktor используем свой скоуп
ktorScope {
var contentResponse = ContentResponse()
try {
val json = httpClient.get {
url {
protocol = URLProtocol.HTTPS
host = NetworkConfig.shared.apiUrl
encodedPath = path
header("X-Api-Key", NetworkConfig.shared.apiKey)
}
}
print(json)
val response = kotlinx.serialization.json.Json.nonstrict.parse(serializer, json)
contentResponse.content = response
} catch (ex: Exception) {
val error = ErrorResponse()
error.message = ex.message.toString()
contentResponse.errorResponse = error
print(ex.message.toString())
}
//Ответ отдаем в UI-поток
withContext(uiDispatcher) {
completed(contentResponse)
}
}
}
}
Парсинг реализуем с помощью сериализатора типа KSerializer<T>
. В нашем случае это NewsList.serializer()
. Пропишем реализацию в сервисе новостей:
@TheadLocal
class NewsService{
companion object {
val shared = NewsApi()
}
val networkService = NetworkService()
suspend fun getNewsList(completed: (ContentResponse)->Unit){
val path = "v2/top-headlines?language=en"
networkService.getData(path, NewsList.serializer(),completed)
}
}
Вызывать бизнес-логику будем в презентере. Для полноценной работы с coroutines нам надо будет создать scope:
class PresenterCoroutineScope(
context: CoroutineContext
) : CoroutineScope {
private var onViewDetachJob = Job()
override val coroutineContext: CoroutineContext = context + onViewDetachJob
fun viewDetached() {
onViewDetachJob.cancel()
}
}
и добавить его в презентер. Вынесем в базовый класс:
abstract class BasePresenter(private val coroutineContext: CoroutineContext) {
protected var view: T? = null
protected lateinit var scope: PresenterCoroutineScope
fun attachView(view: T) {
scope = PresenterCoroutineScope(coroutineContext)
this.view = view
onViewAttached(view)
}
…
}
Теперь создадим презентер NewsListPresenter
для нашего модуля. В инициализатор передадим defaultDispatcher
:
class NewsPresenter:BasePresenter(defaultDispatcher){
var service: NewsApi = NewsApi.shared
var data: ArrayList = arrayListOf()
fun loadData() {
//запускаем в скоупе
scope.launch {
service.getNewsList {
val result = it
if (result.errorResponse == null) {
data = arrayListOf()
data.addAll(result.content?.articles ?: arrayListOf())
view?.setupNews(data)
}
}
}
}
}
Обратите внимание! Из-за особенностей текущей работы Kotlin Native с многопоточностью в IOS работа с синглтонами может привести к крашу. Поэтому для корректной работы надо добавить аннотацию @ThreadLocal
для используемого объекта:
class NewsService{
@ThreadLocal
companion object {
val shared = NewsService()
}
…
}
Осталось подключить логику к нативным IOS и Android модулям и обработать ответ от Presenter:
class NewsListVC: UIViewController {
private lazy var presenter: NewsPresenter? = {
let _presenter = NewsPresenter()
_presenter.attachView(view: self)
return _presenter
}()
override func viewDidLoad() {
super.viewDidLoad()
self.presenter?.loadData()
}
extension NewsListVC : INewsListView {
func setupNews(list: [NewsItem]) {
…
}
Android:
class NewsActivity : AppCompatActivity(), INewsListView {
….
private var _presenter: NewsPresenter? = null
override fun onCreate(savedInstanceState: Bundle?) {
_presenter = NewsPresenter()
_presenter?.attachView(this)
…
}
override fun setupNews(list: List) {
….
}
Запускаем сборку common модуля gradle wrapper, чтобы сборки обновились. Проверяем работу приложений:
Android
iOS
Готово. Вы великолепны.
Оба наши приложения работают и работают одинаково.
Ссылка на ресурсы.
Информационные материалы, которые использовались:
При переходе на Kotlin Multiplatform Mobile (КММ), у Android разработчика могут возникнуть трудности с быстрой настройкой окружения, поскольку официальный сайт Kotlin не предоставляет подробных инструкций. В данной статье мы рассмотрим этапы настройки проекта.
Что такое KMM
Кotlin Multiplatform Mobile (KMM) – это технология кроссплатформенной разработки, которая позволяет объединять общую бизнес-логику приложения (например, запросы на сервер, обработку данных и работу с БД) в отдельный модуль и при этом использовать нативный UI.
Настройка на Windows
Для того, чтобы создать КММ-проект, нам нужно зайти в Android Studio, далее пройти по пути: File -> Settings -> Plugins, выбрать Marketplace и ввести в поисковую строку название инструмента. После этого установить плагин.
Когда плагин установлен, при создании проекта вам будут доступны следующие варианты. Выбираем первый.
При создании в параметре ios framework distribution выбираем Regular Framework.
На этом этапе завершается процесс настройки, и вы можете приступить к написанию кода. Однако простота установки иногда приводит к затруднениям в реализации всего функционала в КММ-проектах.
MacOS
Для полноценной работы с КММ на macOS, включая редактирование специфичного кода для платформы iOS и последующую отладку, необходимо иметь устройство от Apple.
Для начала установки необходимо установить Homebrew – package manager, который позволяет устанавливать недостающие пакеты и библиотеки на macOS и Linux. Для этого можно перейти на официальный сайт и скопировать команду согласно инструкции, а затем вставить её в терминал – аналог командной строки.
После установки необходимо ввести следующую команду: brew install kdoctor. Это инструмент командной строки, который проверяет наличие необходимых файлов и их корректные версии для работы с КММ.
После установки введите команду “kdoctor” и дождитесь результата. Ответ должен быть примерно такого вида:
Таким образом, инструмент проверяет:
1) Систему. Если она не подходит, то, увы, не судьба.
2) Наличие JDK.
3) Наличие Android Studio и плагина КММ (устанавливаем, как в Windows).
4) Наличие xCode.
5) Наличие Cocoapods и его совместимость с Ruby.
Здесь описаны проблемы, связанные с первыми четырьмя пунктами. Я столкнулся с проблемами совместимости Cocoapods и Ruby, которые не были освещены в официальной документации. Перед тем, как приступить к настройке, давайте рассмотрим, что такое Cocoapods, Ruby, а также инструменты rvm и rbenv.
Ruby – это язык программирования, который используется для написания некоторых библиотек, используемых в проекте.
Cocoapods – это менеджер зависимостей (dependency manager), написанный на Ruby. Он позволяет указать зависимости проекта (подобно gradle), которые обычно перечисляются в Podfile.
rvm и rbenv – инструменты, которые дают нам возможность управлять версиями Ruby, обновлять или делать откаты.
После того, как kdoctor проанализировал систему, он подскажет, какую версию Ruby нам нужно установить (подсказки начинаются с *).
Для установки нужной версии Ruby введите в терминал команду brew install ruby@2.7.
Чтобы установить cocoapods, используйте команды sudo gem install cocoapods и sudo gem install cocoapods-generate.
Обратите внимание, что если ваша версия Kotlin меньше 1.7.0, то cocoapods-generate не установится на версиях Ruby 3.0.0 и выше.
На данном этапе возможны ошибки, связанные с несовместимостью версий. Kotlin Multiplatform Mobile стабильно работает на более ранних версиях, а версия Ruby, установленная по умолчанию, может не соответствовать требуемой (чтобы узнать текущую версию Ruby, введите команду ruby -v).
Установка искомой версии Ruby. Здесь у нас есть два пути: rbenv и rvm.
rbenv
1) Устанавливаем с помощью Homebrew brew install rbenv ruby-build
2) Используем rbenv install –list, чтобы увидеть список доступных версий, выбираем нужную
3) Устанавливаем версию rbenv install <версия>
4) Ставим её по умолчанию rbenv global <версия>
rvm
1) Установка \curl -sSL https://get.rvm.io | bash -s stable
2) Список версий rvm list known
3) Установка конкретной версии rvm install <версия>
4) Установка версии по умолчанию rvm use <версия> –default
Когда kdoctor успешно завершит работу, вы увидите желанную надпись: “Your system is ready for Kotlin Multiplatform Mobile Development!”. Теперь вы можете приступать к созданию проекта.
Использование Kotlin Multiplatform Wizard
Вы можете воспользоваться Kotlin Multiplatform Wizard для создания КММ-проекта. Этот инструмент позволяет автоматически подключить базовые библиотеки, плагины и выбрать платформы для работы (при обычном создании проекта доступны только androidApp и iosApp). После создания проекта с помощью инструмента, вы можете скачать архив и открыть его.
Обзор созданного проекта
Рекомендую переключиться с режима просмотра Android на режим просмотра Project после загрузки всех компонентов, чтобы иметь доступ ко всей файловой системе проекта.
На данном этапе проект отличается от стандартного. Вместо одного модуля app было создано три модуля: androidApp, iosApp и shared. Если мы откроем shared/src, то увидим ещё три модуля: androidMain, commonMain, iosMain.
Давайте разберёмся, что представляет каждый модуль.
Модуль androidApp используется для разработки пользовательского интерфейса (UI) и логики, которые характерны только для Android и не могут быть использованы на других платформах. Например, элементы пользовательского интерфейса Android View или Jetpack Compose, а также Activity или Broadcast Receiver – такие сущности отсутствуют в iOS в подобном виде. В файле build.gradle.kts мы указываем зависимости, специфичные только для Android, такие как фрагменты, навигация и т.д. Также мы подключаем shared модуль, который будет рассмотрен далее: implementation(project(“:shared”)).
Модуль iosApp является аналогом androidApp для iOS. Для редактирования кода в этом модуле потребуется xCode. Чтобы открыть проект в xCode, необходимо запустить среду разработки, выбрать “Open a project or file”, затем в директории проекта перейти в iosApp и открыть файл iosApp.xcodeproj.
Если вы создали проект изначально на Windows, а затем перешли на macOS, вы можете столкнуться с ошибкой “gradlew Permission denied”. Чтобы её исправить, в терминале Android Studio нужно выполнить следующую команду: chmod +x gradlew.
В модуле shared мы описываем бизнес-логику приложения, начиная от запросов к серверу до вью-моделей. Основная часть кода будет находиться в commonMain.
платформ, но имеет некоторые отличия в реализации.
Например, при использовании вью-моделей в Android мы наследуемся от класса ViewModel(), но это не требуется в iOS. Поэтому мы создаём директорию presentation в commonMain и в ней – абстрактный класс CommonViewModel. Мы добавляем ключевое слово “expect” перед “abstract”, что означает, что каждая из платформ (в данном случае androidMain и iosMain) должна имплементировать этот класс.
commonMain:
expect abstract class CommonViewModel()
В этих модулях мы тоже создаём папку presentation и абстрактный класс CommonViewModel, но expect заменяем на actual, означающее, что данный класс будет имплементирован. Перед конструктором также придётся прописать ключевое слово actual constructor. В iosMain мы оставим так, а в Android пронаследуемся от ViewModel().
iosMain:
expect abstract class CommonViewModel() actual constructor()
androidMain:
actual abstract class CommonViewModel actual constructor() : ViewModel()
Должно получиться так:
Теперь, когда мы создаём вью-модель, мы будем наследоваться от CommonViewModel и использовать соответствующую реализацию для платформы, на которой используем эту модель.
Механизм expect-actual можно воспринимать как интерфейсы. Expect – это интерфейс, который мы “реализуем” (actual) в зависимости от контекста.
В shared модуле также есть свой build.gradle.kts, где мы указываем общие зависимости. Обратите внимание на sourceSets – здесь мы видим знакомые нам модули и их копии с суффиксом Test.
Зависимости, которые мы размещаем в commonMain, будут относиться ко всем платформам. Если мы размещаем зависимости в androidMain, то они будут относиться только к Android, а если в iosMain, то к iosX64Main, iosArm64Main и iosSimulatorArm64Main (как указано в методе dependsOn()). При подключении библиотек нет путаницы, потому что официальные порталы всегда подробно описывают, как их следует подключать. В следующем этапе мы рассмотрим удобную технологию управления зависимостями в КММ.
По мере роста проекта и увеличения числа подключаемых библиотек может возникнуть сложность в контроле версий и избежании дублирования. Решением этой задачи может стать создание отдельного модуля для всех зависимостей. В качестве такого модуля может выступать buildSrc – специальная библиотека, которая подключается к Gradle-проекту (само название также зарезервировано Gradle-ом) и при сборке проекта компилируется первой.
Для создания модуля buildSrc необходимо на уровне проекта создать папку с соответствующим названием и внутри неё создать файл build.gradle.kts с определенным кодом, после чего выполнить синхронизацию проекта, нажав sync.
repositories {
mavenCentral()
}
plugins {
`kotlin-dsl`
}
Затем в модуле buildSrc необходимо создать директорию src/main/kotlin, которая появится в списке доступных, если синхронизация прошла успешно. После этого внутри директории kotlin создайте файл с названием Dependencies.kt. Этот файл будет содержать все зависимости проекта.
В файле создаём три объекта: Plugins, Versions, Deps (внутри него для удобства можно отдельно создать объекты Android и Multiplatform) и вставляем следующий код:
В Plugins указываем базовые плагины:
object Plugins {
const val androidApp = "com.android.application"
const val android = "android"
const val multiplatform = "multiplatform"
const val androidLib = "com.android.library"
}
В Versions указываем версии SDK, подключаемых плагинов и библиотек:
object Versions {
// SDK
const val compileSdk = 32
const val targetSdk = 32
const val minSdk = 21
// Plugins
const val android_version = "7.3.1"
const val kotlin_version = "1.7.10"
const val compose_version = "1.2.1"
const val compose_activity_version = "1.5.1"
// Coroutines
const val coroutines_version = "1.6.4"
}
В Deps прописываем зависимости для наших библиотек, используя ранее введённые версии:
Gradle-плагины для Kotlin и Android:
const val kotlin_gradle_plugin = "org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin_version}"
const val android_gradle_plugin = "com.android.tools.build:gradle:${Versions.android_version}"
Зависимости для Android:
object Android {
// Compose
const val compose_ui = "androidx.compose.ui:ui:${Versions.compose_version}"
const val compose_ui_tooling = "androidx.compose.ui:ui-tooling:${Versions.compose_version}"
const val compose_ui_tooling_preview = "androidx.compose.ui:ui-tooling-preview:${Versions.compose_version}"
const val compose_foundation = "androidx.compose.foundation:foundation:${Versions.compose_version}"
const val compose_material = "androidx.compose.material:material:${Versions.compose_version}"
const val compose_activity = "androidx.activity:activity-compose:${Versions.compose_activity_version}"
// Coroutines
const val coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Versions.coroutines_version}"
}
Зависимости для КММ:
object Multiplatform {
// Coroutines
const val coroutines_core = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Versions.coroutines_version}"
}
Примечание: в статье используются зависимости, которые подключаются при создании проекта, кроме корутин, которые специально взяты для демонстрации их включения в shared модуль.
Следующий шаг – преобразовать build.gradle.kts файлы нашего проекта. Открываем корневой файл и заменяем строки на наши переменные.
buildscript {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
dependencies {
classpath(Deps.android_gradle_plugin)
classpath(Deps.kotlin_gradle_plugin)
}
}
В build.gradle.kts (:shared) обновляем плагины:
plugins {
kotlin(Plugins.multiplatform)
id(Plugins.androidLib)
}
Примечание: ключевое слово kotlin используется для автоматического добавления префикса org.jetbrains.kotlin в название плагина. То есть, выражения id(org.jetbrains.kotlin.multiplatform) и kotlin(multiplatform) тождественны.
Зависимости:
sourceSets {
val commonMain by getting {
dependencies {
implementation(Deps.Multiplatform.coroutines_core)
}
}
...
}
Так как мы добавили библиотеку корутин в commonMain, мы можем её использовать как для Android, так и для iOS.
SDK:
android {
namespace = "com.example.newkmm"
compileSdk = Versions.compileSdk
defaultConfig {
minSdk = Versions.minSdk
targetSdk = Versions.targetSdk
}
}
В build.gradle.kts (:android):
plugins {
id(Plugins.androidApp)
kotlin(Plugins.android)
}
android {
namespace = "com.example.newkmm.android"
compileSdk = Versions.compileSdk
defaultConfig {
applicationId = "com.example.newkmm.android"
minSdk = Versions.minSdk
targetSdk = Versions.targetSdk
versionCode = 1
versionName = "1.0"
}
...
}
dependencies {
implementation(project(":shared"))
implementation(Deps.Android.compose_ui)
implementation(Deps.Android.compose_ui_tooling)
implementation(Deps.Android.compose_ui_tooling_preview)
implementation(Deps.Android.compose_foundation)
implementation(Deps.Android.compose_material)
implementation(Deps.Android.compose_activity)
}
Синхронизируем проект. Во вкладке Build убеждаемся, что сначала выполняются таски, связанные с buildSrc, а затем таски основного проекта.
КММ изначально собирается дольше, чем обычный Android проект, поэтому открываем в gradle.properties и добавляем следующие строки:
org.gradle.caching=true // 1
org.gradle.parallel=true // 2
org.gradle.daemon=true // 3
- Включаем кеширование.
- Включаем параллельную сборку не взаимосвязанных задач, так как по умолчанию Gradle выполняет одну задачу за раз. Фича особенно полезна в КММ.
- Gradle Daemon по умолчанию включён, но желательно указывать его явно. Этот компонент занимается кешированием, мониторингом файловой системы для определения необходимых для билда файлов и держит JVM в «прогретом» состоянии.
В процессе подготовки системы к кроссплатформенной разработке необходимо учесть, что для владельцев macOS это может занять значительно больше времени, чем для пользователей Windows. В данном тексте описываются инструменты, которые можно использовать для достижения цели, и устраняется возможная путаница с несовместимостью версий.
Проект на старте имеет более сложную файловую структуру и состоит из большего числа модулей. В shared модуле описывается общая логика, используя механизм expect/actual в случае отличия реализации сущностей платформы.
Для централизованного управления зависимостями можно использовать Gradle библиотеку buildSrc, где все зависимости и версии хранятся в одном файле, разбитом на группы для повышения читабельности кода. При сборке проекта этот модуль компилируется первым, что упрощает процесс.
Теперь, когда подготовительный этап завершён, можно переходить к написанию логики самого проекта.
Подписывайся на нас на VC, хабре, ВК, там мы публикуем еще больше полезного материала!
А если хочешь крутой проект, напиши нам, и мы вместе придумаем решение.