Шаг за шагом: настройка окружения для Kotlin Multiplatform Mobile

Шаг за шагом: настройка окружения для Kotlin Multiplatform Mobile

03.04.2023
# Для_разработчиков
Author avatar
Амет ХырхараAndroid разработчик

При переходе на Kotlin Multiplatform Mobile (КММ), у Android разработчика могут возникнуть трудности с быстрой настройкой окружения, поскольку официальный сайт Kotlin не предоставляет подробных инструкций. В данной статье мы рассмотрим этапы настройки проекта. 

Что такое KMM

Кotlin Multiplatform Mobile (KMM) – это технология кроссплатформенной разработки, которая позволяет объединять общую бизнес-логику приложения (например, запросы на сервер, обработку данных и работу с БД) в отдельный модуль и при этом использовать нативный UI.

Настройка на Windows

Для того, чтобы создать КММ-проект, нам нужно зайти в Android Studio, далее пройти по пути: File -> Settings -> Plugins, выбрать Marketplace и ввести в поисковую строку название инструмента. После этого установить плагин.

KMM Для Windows

Когда плагин установлен, при создании проекта вам будут доступны следующие варианты. Выбираем первый.

плагин KMM для Windows

При создании в параметре ios framework distribution выбираем Regular Framework.

На этом этапе завершается процесс настройки, и вы можете приступить к написанию кода. Однако простота установки иногда приводит к затруднениям в реализации всего функционала в КММ-проектах.

MacOS

Для полноценной работы с КММ на macOS, включая редактирование специфичного кода для платформы iOS и последующую отладку, необходимо иметь устройство от Apple.
Для начала установки необходимо установить Homebrew – package manager, который позволяет устанавливать недостающие пакеты и библиотеки на macOS и Linux. Для этого можно перейти на официальный сайт и скопировать команду согласно инструкции, а затем вставить её в терминал – аналог командной строки.

KMM на MacOS

После установки необходимо ввести следующую команду: brew install kdoctor. Это инструмент командной строки, который проверяет наличие необходимых файлов и их корректные версии для работы с КММ.

После установки введите команду “kdoctor” и дождитесь результата. Ответ должен быть примерно такого вида:

KMM установка на MacOS

Таким образом, инструмент проверяет:

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 после загрузки всех компонентов, чтобы иметь доступ ко всей файловой системе проекта.

проект KMM

На данном этапе проект отличается от стандартного. Вместо одного модуля 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.

создание проекта на KMM

платформ, но имеет некоторые отличия в реализации.

Например, при использовании вью-моделей в 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()

Должно получиться так:

Kotlin Multiplatform Mobile проект

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

Механизм expect-actual можно воспринимать как интерфейсы. Expect – это интерфейс, который мы “реализуем” (actual) в зависимости от контекста.

В shared модуле также есть свой build.gradle.kts, где мы указываем общие зависимости. Обратите внимание на sourceSets – здесь мы видим знакомые нам модули и их копии с суффиксом Test.

создание проекта на KMM

Зависимости, которые мы размещаем в 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, а затем таски основного проекта. 

KMM проект

КММ изначально собирается дольше, чем обычный 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 в «прогретом» состоянии.

В процессе подготовки системы к кроссплатформенной разработке необходимо учесть, что для владельцев macOS это может занять значительно больше времени, чем для пользователей Windows. В данном тексте описываются инструменты, которые можно использовать для достижения цели, и устраняется возможная путаница с несовместимостью версий.

Проект на старте имеет более сложную файловую структуру и состоит из большего числа модулей. В shared модуле описывается общая логика, используя механизм expect/actual в случае отличия реализации сущностей платформы.

Для централизованного управления зависимостями можно использовать Gradle библиотеку buildSrc, где все зависимости и версии хранятся в одном файле, разбитом на группы для повышения читабельности кода. При сборке проекта этот модуль компилируется первым, что упрощает процесс.

Теперь, когда подготовительный этап завершён, можно переходить к написанию логики самого проекта.

Подписывайся на нас на VC, хабре, ВК, там мы публикуем еще больше полезного материала!
А если хочешь крутой проект, напиши нам, и мы вместе придумаем решение.

Author avatar
Амет ХырхараAndroid разработчик
Поделись статьей в соцмедиа:
Рекомендуемые статьи
Joy Dev в топе рейтинга Tagline 2023 среди лучших IT-компаний России

Joy Dev в топе рейтинга Tagline 2023 среди лучших IT-компаний России

Joy Dev в топе рейтинга Tagline 2023 среди лучших IT-компаний России

Joy Dev в топе рейтинга Tagline 2023 среди лучших IT-компаний России

Joy Dev в топе рейтинга Tagline 2023 среди лучших IT-компаний России

Joy Dev в топе рейтинга Tagline 2023 среди лучших IT-компаний России

Призовое место “Real Cosmetology” в конкурсе Золотое Приложение

Призовое место “Real Cosmetology” в конкурсе Золотое Приложение

Призовое место “Real Cosmetology” в конкурсе Золотое Приложение

Призовое место “Real Cosmetology” в конкурсе Золотое Приложение

Призовое место “Real Cosmetology” в конкурсе Золотое Приложение

Призовое место “Real Cosmetology” в конкурсе Золотое Приложение

Золото в конкурсе Workspace Digital Awards 2023

Золото в конкурсе Workspace Digital Awards 2023

Золото в конкурсе Workspace Digital Awards 2023

Золото в конкурсе Workspace Digital Awards 2023

Золото в конкурсе Workspace Digital Awards 2023

Золото в конкурсе Workspace Digital Awards 2023

Хронофаги и как их усмирить

Хронофаги и как их усмирить

Хронофаги и как их усмирить

Хронофаги и как их усмирить

Хронофаги и как их усмирить

Хронофаги и как их усмирить

Cофт скилы: как получать больше за красивые глаза

Cофт скилы: как получать больше за красивые глаза

Cофт скилы: как получать больше за красивые глаза

Cофт скилы: как получать больше за красивые глаза

Cофт скилы: как получать больше за красивые глаза

Cофт скилы: как получать больше за красивые глаза

Площадки для видеостриминга: обзор-сравнение

Площадки для видеостриминга: обзор-сравнение

Площадки для видеостриминга: обзор-сравнение

Площадки для видеостриминга: обзор-сравнение

Площадки для видеостриминга: обзор-сравнение

Площадки для видеостриминга: обзор-сравнение

OpenCV в медицине: как наши разработчики помогли хирургам

OpenCV в медицине: как наши разработчики помогли хирургам

OpenCV в медицине: как наши разработчики помогли хирургам

OpenCV в медицине: как наши разработчики помогли хирургам

OpenCV в медицине: как наши разработчики помогли хирургам

OpenCV в медицине: как наши разработчики помогли хирургам