Недавно я начал экспериментировать с Kotlin Multiplatform, которая все еще находится в экспериментальном состоянии. Прежде чем я смог начать делать что-то продуктивное, я столкнулся с несколькими проблемами и хотел сообщить, как их исправить, если вы также столкнетесь с ними.

Установка пути к Android SDK

Первая проблема, с которой вы автоматически столкнетесь после создания проекта:

Please fix the ‘sdk.dir’ property in the local.properties file

Просто перейдите в Android Studio, откройте свой local.properties и скопируйте значение sdk.dir оттуда в соответствующий файл в многоплатформенном проекте. Теперь вы можете продолжить сборку своего проекта.

Переименование исходного набора Android

Проект создается с несколькими исходными наборами _3 _, _ 4_, iosMain и т. Д. Однако пакеты Android называются main и test, что меня очень сбивает с толку - почему не androidMain? К сожалению, простое переименование пакета приведет к ошибкам сборки.

Кроме того, я открыл app/build.gradle и поместил это в блок android {}:

sourceSets {
    main {
        manifest.srcFile 'src/androidMain/AndroidManifest.xml'
        java.srcDirs = ['src/androidMain/java']
        res.srcDirs = ['src/androidMain/res']
    }
}

сообщая Gradle, где вместо этого он может найти код Android.

Написание тестов для классов в общем модуле

Приятно иметь возможность писать код, который будет совместно использоваться Android и iOS. Но мне тоже нужно протестировать этот код. Как и в IntelliJ, я нажал ⌘ + ⇧ + T внутри класса, чтобы создать тест для этого класса.

Я прочитал, что мне нужно использовать kotlin-test - он уже добавлен в путь к классам, но нет возможности создать «тест Kotlin», и все другие предлагаемые для использования среды тестирования неверны. Я просто выбрал один и нажал «продолжить». К счастью, вы можете просто начать вводить @Test, и IntelliJ попросит вас импортировать правильную библиотеку kotlin.test.Test. Но потом я заметил, что он создал мой тест в пакете test, который является пакетом для тестов Android, вместо пакета commonTest.

Извлеченный урок: прямо сейчас создайте все ваши общие тесты вручную в правильном пакете.

Выполнение тестов

После того, как я наконец написал тест, я, конечно, захотел его выполнить - но это было бы слишком просто, если бы он просто работал!

В моем случае я добавил библиотеку kotlinx-serialization, и при попытке запустить тест для общего модуля моя сборка не удалась.

Task :app:compileDebugAndroidTestKotlinAndroid FAILED
e: /mpp-test/app/src/commonTest/kotlin/sample/SampleTests.kt: (3, 16): Unresolved reference: serialization

Я быстро заметил, что это не удается при компиляции тестовых ресурсов Android, но я просто хотел запустить общий код, он не имеет ничего общего с Android 🤷‍♂

После некоторого поиска я обнаружил, что в настоящее время это ошибка, которую необходимо решить с помощью Kotlin 1.3.40. А пока вам явно нужно добавить все мультиплатформенные зависимости, предназначенные для androidMain, в androidTest.

После того, как я исправил это, я столкнулся с другой проблемой: щелкнув правой кнопкой мыши на commonTest и выбрав «Запустить все тесты», я получил:

Нет доступных задач

Я подумал: «Хорошо, тогда давайте запустим тест, щелкнув правой кнопкой мыши тестовый класс и выбрав« Выполнить »отсюда».

Это сработало, однако закончилось:

Тестовые события не получены

Глядя на вывод консоли, он пытался запустить Exeuting task ‘-tests “sample.sampleTests"'. Он пытался выполнить… что?

Опять же, просмотрев открытые ошибки мультиплатформенности, я нашел решение:

Перейдите к Preferences | Build, Execution, Deployment | Build Tools | Gradle | Runner, Run tests using и выберитеPlatform Test Runner вместо Gradle Test Runner - теперь вы можете запускать тесты.

Обертка Gradle отсутствует

Пытался запустить что-нибудь из командной строки с помощью Gradle? В настоящее время в новые проекты не добавляется оболочка Gradle. Что касается меня, я пошел в другой проект и скопировал gradlew, gradlew.bat и каталог gradle/wrapper в многоплатформенный проект. Впоследствии вы можете использовать ./gradlew build и тому подобное.

Невозможно запустить приложение с Ktor в качестве зависимости

Если вы хотите наладить работу в сети, вам нужно использовать Ktor. Я добавил все зависимости для Multiplatform и попытался запустить свое приложение. Это не удалось с

FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ‘:app:transformResourcesWithMergeJavaResForDebug’.
> More than one file was found with OS independent path ‘META-INF/ktor-http.kotlin_module’

Добавьте это в свой android {} блок:

packagingOptions {
    exclude 'META-INF/*.kotlin_module'
}

Использование Ktor с Kotlinx. Сериализация на Kotlin / Native

Итак, вы можете добавить JsonFeature в Ktor, что позволяет Ktor автоматически анализировать некоторый JSON, полученный из вызова API, к некоторому объекту:

install(JsonFeature) { serializer = KotlinxSerializer() }

Если вы сейчас выполните HTTP-вызов с помощью Ktor и определите тип возвращаемого значения функции как вашу сущность, все остальное Ktor сделает за вас самостоятельно:

suspend fun doHttpCall(): YourEntity {        
  return client.get { ... }
}

Это работало нормально, когда я тестировал его на Android. При реализации части iOS с использованием Kotlin / Native возникло исключение:

Получение сериализатора из KClass недоступно на родном языке из-за отсутствия отражения. «+» Используйте .serializer () непосредственно в сериализуемом классе.

В настоящее время это TODO на Kotlin / Native. Итак, просто измените свой метод примерно так:

val response = client.get<HttpResponse> { ... }
val jsonBody = response.readText()        
return Json.parse(YourEntity.serializer(), jsonBody)

Будьте осторожны с одиночками

Модель параллелизма Kotlin / Native спроектирована таким образом, что она замораживает графы объектов, чтобы гарантировать неизменность. Подробнее об этом можно прочитать здесь.

В конце указывается, при каких обстоятельствах объекты автоматически замораживаются. Определенно я должен прочитать это заранее.

Я создал Kotlin object с именем ServiceLocator, который должен служить мне очень простым инструментом для внедрения зависимостей. Я поместил свой HTTP-клиент Ktor внутрь и получил исключение:

kotlin.native.concurrent.InvalidMutabilityException: попытка мутации замороженного io.ktor.client.request.HttpRequestPipeline

что было вызвано именно этой проблемой. Однако мне потребовалось много времени, чтобы понять это.

Вам нужно добавить @ThreadLocal в свой object или переместить его из синглтона. Однако добавление @ThreadLocal может иметь нежелательные побочные эффекты, поэтому будьте осторожны с его использованием и не рассматривайте его как окончательное решение.

Это была проблема, на поиск и решение которой мне потребовалось время 😉 Счастливое многоплатформенное кодирование.