Мы влюбились в него с самого начала — от выразительности системы типов до поддержки функций более высокого порядка. Было здорово заново открыть для себя разработку для Android с помощью такого мощного инструмента. Нам нравится думать, что это было только начало. Мы внимательно следим за последними обновлениями от команды JetBrains. Наибольший интерес вызывает команда, которая делает мультиплатформенный Kotlin. Это означает, что помимо возможности использования Kotlin в приложениях для Android, теперь мы можем писать приложения для iOS, бэкэнд и веб-интерфейс на нашем любимом языке!
Мы решили продолжить исследование и создать приложение для двух ведущих платформ — iOS и Android, используя мультиплатформенный Kotlin. Мы все еще продолжаем исследования и хотим реализовать много вещей, но уже готовы рассказать вам о том, что нашли.
Бесплатный курс “Разработка Android-приложений на Kotlin” от Google
Модель
Мультиплатформенные проекты Kotlin обычно делятся на несколько модулей. Будет один для каждой платформы, где мы хотим выпустить приложение — в данном случае один для iOS и один для Android. Мы создаем еще один модуль для повторного использования кода на обеих платформах, shared или common модуль. Многоплатформенная сборка работает через создание артефакта общего модуля для каждой целевой целевой платформы — чтобы впоследствии использовать его на ней.
Это означает, что Kotlin будет генерировать для конечных проектов оба файла, и файл jar, и файл фреймворка, из одного и того же кода. Пунктир на рисунке выше показывает, чего мы хотим достичь. Несмотря на то, что Kotlin Multiplatform предназначен и для использования в Backend и Web, мы не будем их задействовать в рамках нашего небольшого эксперимента.
Чтобы настроить многоплатформенный проект и все его зависимости, нам нужно сначала создать новый файл Gradle для создания shared модуля.
shared/build.gradle
kotlin { targets { fromPreset(presets.jvm, "jvm") fromPreset(presets.iosX64, "ios_x86_64") fromPreset(presets.iosArm64, "ios_arm64") configure([ios_x86_64, ios_arm64]) { compilations.main.outputKinds("FRAMEWORK") } } sourceSets { commonMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib" } } jvmMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" } } iosMain { dependencies { } } configure([ios_x86_64Main, ios_arm64Main]) { dependsOn iosMain } } } configurations { compileClasspath }
Мы можем видеть множество iOS-настроек, которые мы рассмотрим позже. Затем мы определяем конфигурацию Android-проекта и говорим Gradle добавить зависимость к общему модулю.
androidApp/build.gradle.kts
android { buildToolsVersion = "28.0.3" compileSdkVersion(28) defaultConfig { minSdkVersion(21) targetSdkVersion(28) multiDexEnabled = true versionCode = 1 testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" } buildTypes { getByName("release") { isMinifyEnabled = false proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") } } sourceSets { getByName("main").java.srcDirs("src/main/kotlin") } } dependencies { implementation("com.android.support.constraint:constraint-layout:1.1.3") implementation("com.android.support.constraint:constraint-layout:1.1.3") implementation("com.android.support:appcompat-v7:28.0.0") implementation("com.android.support:recyclerview-v7:28.0.0") implementation(kotlin("stdlib-jdk7", KotlinCompilerVersion.VERSION)) implementation(project(":shared")) }
Наконец, мы должны настроить iOS-проект. Это самый сложный шаг в этом процессе. Ведь iOS работает со своим собственным набором инструментов для сборки. Хитрость заключается в том, чтобы создать новую задачу Gradle для создания артефакта фреймворка. Важно помнить, что нам нужно создать артефакт для разных архитектур, x86_64 для симулятора и arm64 для реальных устройств.
shared/build.gradle
task packForXCode(type: Sync) { final File frameworkDir = new File(buildDir, "xcode-frameworks") final String configuration = project.findProperty("CONFIGURATION")?.toUpperCase() ?: "DEBUG" final String arch = project.findProperty("ARCHS") ?: "x86_64" dependsOn kotlin.targets."ios_${arch}".compilations.main.linkTaskName("FRAMEWORK", configuration) from { kotlin.targets."ios_${arch}".compilations.main.getBinary("FRAMEWORK", configuration).parentFile } into frameworkDir } tasks.build.dependsOn packForXCode
Теперь мы можем настроить сборку XCode для компилирования общего модуля через новую задачу Gradle, и включить сгенерированный фреймворк в проект.
cd ${SRCROOT}/.. ./gradlew :shared:packForXCode \ -PCONFIGURATION=${CONFIGURATION} \ -PARCHS=${ARCHS}
Выполняем основные проверки
Когда проект запущен для обеих платформ, мы хотим инвестировать в качественную разработку. Мы настроим SwiftLint в проекте iOS, добавив Podfile с зависимостью:
platform :ios, "11.4" target "Multiplatform app" do use_frameworks! pod "SwiftLint" end
Для Android мы использовали наши обычные инструменты — ktlint и detekt. Добавить их легко, так как в Android-проекте они уже используются.
shared/build.gradle
apply plugin: "io.gitlab.arturbosch.detekt" apply plugin: "org.jlleitschuh.gradle.ktlint" ktlint { version = "0.30.0" verbose = true android = false outputToConsole = true reporters = [ReporterType.PLAIN, ReporterType.CHECKSTYLE] filter { exclude("**/generated/**") include("**/kotlin/**") } } detekt { toolVersion = "1.0.0-RC14" input = files("src/main/kotlin") filters = ".*/resources/.*,.*/build/.*" } check.dependsOn ktlintCheck check.dependsOn "detekt"
CI
Самое главное после установки автоматических проверок в нашем проекте — запускать их время от времени. Мы используем Bitrise, и с самого начала мы выбрали два разных проекта, по одному для каждой платформы. Таким образом, мы можем настроить стек для каждого приложения независимо и запускать тесты для обоих платформ параллельно!
Для Android мы запускаем все юнит-тесты и проверки lint. Для iOS мы постарались сделать то же самое, но мы все еще сталкиваемся с некоторыми проблемами, пытаясь сформировать стек, поддерживающий современные версии Gradle и XCode.
Итого
Мы по-прежнему усердно работаем над решением некоторых проблем. Это исправление нашего CI для iOS -сборок или максимально эффективное использование нашей архитектуры для повторного использования как можно большего кода. Оставляйте комментарии и следите за публикациями.