CodeFest 2019. Илья Матвеев (JetBrains) — Мультиплатформенные проекты в Kotlin 1.3

CodeFest 2019. Илья Матвеев (JetBrains) — Мультиплатформенные проекты в Kotlin 1.3

Многие знают Котлин в первую очередь как язык для разработки под Android и JVM. Однако это не единственные поддерживаемые платформы, и Котлин компилируется также в JavaScript и нативный код для различных архитектур. Естественным результатом такого разнообразия компиляторов стало появление проектов, в которых переиспользуется код под разные платформы.

Начиная с версии 1.2 экспериментальная поддержка таких мультиплатформенных проектов была добавлена в язык на уровне синтаксиса и со стороны тулинга. На практике это означает, что у программиста на Котлине появилась возможность использовать один и тот же код на разных платформах, при этом не теряя доступа к платформенно-специфичным API. Эта фича носит статус экспериментальной, а, значит, это одно из тех мест в языке, где изменения происходят чаще всего. Релиз 1.3 не стал исключением и поддержка мультиплатформенных проектов в нем была заметно переработана.

Этот доклад посвящён как самой по себе концепции мультиплатформенных проектов в Котлине, так и тем изменениям, которые появились в ней начиная с версии 1.3. Поэтому он будет интересен и тем, кто еще не знаком с этой фичей, и тем, кто уже успел попробовать ее в Kotlin 1.2.
В докладе поговорим:

— О том, что такое мультиплатформенные проекты вообще, зачем они нужны и какие возможности дают программисту.
— О том, как такие проекты выглядят со стороны IDE и билд-системы, какие сложности при этом возникают и как мы их решаем.
— О том, как использовать такие проекты для разработки приложений под Android и iOS.

16b6c87229eaf58768d25ed7b2bbbf52?s=128

CodeFest

April 06, 2019
Tweet

Transcript

  1. Мультиплатформенные проекты в Kotlin 1.3 Илья Матвеев ilmat192@gmail.com JetBrains

  2. О чём будем говорить? • Лирика: общие концепции MPP* •

    Теория: как это выглядит в языке и тулинге • Практика: приложение iOS + Android 2 *MPP - мультиплатформенное программирование
  3. Мультиплатформенные проекты 3

  4. Мультиплатформенные проекты 4

  5. Мультиплатформенные проекты 5

  6. Что хотим? • Переиспользовать код на разных платформах 6

  7. Что хотим? • Переиспользовать код на разных платформах • Иметь

    доступ к платформенно-специфичным API 7
  8. Что хотим? • Переиспользовать код на разных платформах • Иметь

    доступ к платформенно-специфичным API • Пользоваться поддержкой со стороны IDE 8
  9. Как это работает

  10. Как это работает Android код Разделяемый код iOS код Платформенный

    код • UI • Платформенные API Разделяемый код • Высокоуровневая логика • Работа с сетью/БД 10
  11. Как это работает Android код Разделяемый код iOS код 11

    Kotlin/JVM *.dex
  12. Как это работает Android код Разделяемый код iOS код 12

    Kotlin/JVM *.dex Kotlin/Native iOS binary
  13. MPP и синтаксис 13

  14. Expect/actual expect class Logger { fun log(message: String) } 14

  15. Expect/actual expect class Logger { fun log(message: String) } import

    android.util.Log actual class Logger { fun log(message: String) { Log.i("Tag", message) } } import platform.Foundation.* actual class Logger() { fun log(message: String) { NSLog(message) } } 15
  16. Expect/actual expect class Logger { constructor(tag: String) // ... }

    expect fun withLogger(action: Logger.() -> Unit) expect fun Logger.logError(exception: Throwable) 16 Expect-класс может иметь конструктор Можно объявлять expect-функции
  17. Expect/actual package kotlin.collections expect class ArrayList<E> : MutableList<E>, RandomAccess {

    /* ... */ } 17
  18. Expect/actual package kotlin.collections expect class ArrayList<E> : MutableList<E>, RandomAccess {

    /* ... */ } package kotlin.collections actual typealias ArrayList<E> = java.util.ArrayList<E> 18
  19. Expect/actual package kotlin.test expect annotation class Test 19

  20. Expect/actual package kotlin.test expect annotation class Test package kotlin.test actual

    typealias Test = org.junit.Test 20
  21. Expect/actual package kotlin.test expect annotation class Test package kotlin.test actual

    typealias Test = org.junit.Test 21 package kotlin.test actual typealias Test = org.testng.annotations.Test
  22. MPP и Gradle (1.2) 22

  23. Мультиплатформа и Gradle (1.2) 23 :common :android :ios Root project

  24. Мультиплатформа и Gradle (1.2) 24 :common :android :ios Root project

    apply plugin: 'kotlin-platform-common' dependencies { compile 'org.jetbrains.kotlin:kotlin-stdlib-common' } apply plugin: 'kotlin-platform-android' dependencies { expectBy project( ':common') compile 'org.jetbrains.kotlin:kotlin-stdlib' }
  25. Мультиплатформа и Gradle (1.2) 25 :common :android :ios Root project

    apply plugin: 'kotlin-platform-common' dependencies { compile 'org.jetbrains.kotlin:kotlin-stdlib-common' } apply plugin: 'kotlin-platform-android' dependencies { expectBy project(':common') compile 'org.jetbrains.kotlin:kotlin-stdlib' } Подключаем плагины
  26. Мультиплатформа и Gradle (1.2) 26 :common :android :ios Root project

    apply plugin: 'kotlin-platform-common' dependencies { compile 'org.jetbrains.kotlin:kotlin-stdlib-common' } apply plugin: 'kotlin-platform-android' dependencies { expectBy project(':common') compile 'org.jetbrains.kotlin:kotlin-stdlib' } Добавляем внешние зависимости
  27. Мультиплатформа и Gradle (1.2) 27 :common :android :ios Root project

    apply plugin: 'kotlin-platform-common' dependencies { compile 'org.jetbrains.kotlin:kotlin-stdlib-common' } apply plugin: 'kotlin-platform-android' dependencies { expectBy project( ':common') compile 'org.jetbrains.kotlin:kotlin-stdlib' } Связываем платформенный и common код
  28. Мультиплатформа и Gradle (1.2) 28 :common :android :ios Root project

    expectBy
  29. Проблемы модели 1.2 29 :common :android :ios :macos :windows :linux

    Root project
  30. Проблемы модели 1.2 30 :common :android :ios :macos :windows :linux

    :common_native Root project
  31. Проблемы модели 1.2 31 https://youtrack.jetbrains.com/issue/KT-23930

  32. Проблемы модели 1.2 1. Число модулей растет с числом платформ

    32
  33. Проблемы модели 1.2 33 org.example:my-library:1.0 Root project :common :android :ios

    :macos :windows :linux
  34. Проблемы модели 1.2 34 org.example:my-library:1.0 org.example:my-application:1.0 Depends on Root project

    :common :android :ios :macos :windows :linux Root project :common :android :ios :macos :windows :linux
  35. Проблемы модели 1.2 35 Depends on org.example:my-library:1.0 org.example:my-application:1.0 Root project

    :common :android :ios :macos :windows :linux
  36. Проблемы модели 1.2 36 org.example:my-library:1.0 dependencies { compile 'org.example:my-library-common' }

    dependencies { compile 'org.example:my-library-ios' } dependencies { compile 'org.example:my-library-linux' } ...
  37. Проблемы модели 1.2 1. Число модулей растет с числом платформ

    2. Зависимости настраиваются вручную 37
  38. MPP и Gradle (1.3) 38

  39. Мультиплатформа и Gradle в 1.3 commonMain commonTest jvmTest jvmMain iosMain

    iosTest 39 Source sets
  40. Мультиплатформа и Gradle в 1.3 commonMain commonTest jvmTest jvmMain iosMain

    iosTest jvm ios 40 Source sets Targets
  41. Мультиплатформа и Gradle в 1.3 commonMain commonTest jvmTest jvmMain iosMain

    iosTest jvm main test ios main test 41 Source sets Targets Compilations
  42. Мультиплатформа и Gradle в 1.3 commonMain commonTest jvmTest jvmMain iosMain

    iosTest jvm main test ios main test JVM classes iOS binary 42 Source sets Targets Compilations
  43. Мультиплатформа и Gradle в 1.3 commonMain androidMain androidRelease android release

    debug 43 Source sets Targets ... ... androidDebug Release classes Debug classes
  44. Мультиплатформа и Gradle в 1.3 apply plugin: "kotlin-multiplatform" kotlin {

    sourceSets.commonMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-common" } } android { /* Android-specific configuration */ } iosArm64 { /* iOS-specific configuration */ } } 44
  45. Мультиплатформа и Gradle в 1.3 apply plugin: "kotlin-multiplatform" kotlin {

    sourceSets.commonMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-common" } } android { /* Android-specific configuration */ } iosArm64 { /* iOS-specific configuration */ } } 45 Подключаем плагин
  46. Мультиплатформа и Gradle в 1.3 apply plugin: "kotlin-multiplatform" kotlin {

    sourceSets.commonMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-common" } } android { /* Android-specific configuration */ } iosArm64 { /* iOS-specific configuration */ } } 46 Конфигурируем source set-ы
  47. Мультиплатформа и Gradle в 1.3 apply plugin: "kotlin-multiplatform" kotlin {

    sourceSets.commonMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-common" } } android { /* Android-specific configuration */ } iosArm64 { /* iOS-specific configuration */ } } 47 Конфигурируем source set-ы Настраиваем зависимости
  48. Мультиплатформа и Gradle в 1.3 apply plugin: "kotlin-multiplatform" kotlin {

    sourceSets.commonMain { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-common" } } android { /* Android-specific configuration */ } iosArm64 { /* iOS-specific configuration */ } } 48 Конфигурируем таргеты
  49. Проблемы модели 1.2 1. Число модулей растет с числом платформ

    2. Зависимости настраиваются вручную 49
  50. Управление зависимостями 50 commonMain.dependencies { implementation "org.example:my-library-common" } iosMain.dependencies {

    implementation "org.example:my-library-ios" } androidMain.dependencies { implementation "org.example:my-library-android" } Платформенные артефакты
  51. Управление зависимостями 51 commonMain.dependencies { implementation "org.example:my-library" } Мультиплатформенный артефакт

  52. Variant-aware dependency management org.example: my-library:1.0 52

  53. Variant-aware dependency management org.example: my-library:1.0 53 my-library-ios.klib my-library-android.aar my-library-jvm.jar

  54. Variant-aware dependency management org.example: my-library:1.0 54 my-library-ios.klib platform = ios_arm64

    ... my-library-android.aar platform = android ... my-library-jvm.jar platform = jvm ...
  55. Variant-aware dependency management org.example: my-library:1.0 55 my-library-ios.klib platform = ios_arm64

    ... my-library-android.aar platform = android ... my-library-jvm.jar platform = jvm ... Consumer platform = android ...
  56. Variant-aware dependency management org.example: my-library:1.0 56 my-library-ios.klib platform = ios_arm64

    ... my-library-android.aar platform = android ... my-library-jvm.jar platform = jvm ... Consumer platform = android ...
  57. Публикация 57 org.example:my-library:1.0

  58. Публикация 58 org.example:my-library:1.0

  59. Публикация 59 org.example:my-library:1.0 Gradle metadata

  60. Публикация 60 org.example:my-library:1.0 Корневой артефакт Gradle metadata

  61. Gradle metadata 61 "variants": [ { "name": "android-releaseApiElements", "attributes": {

    "org.gradle.usage": "java-api", "org.jetbrains.kotlin.platform.type": "androidJvm", ... }, "available-at": { "url": "../../my-library-android/1.0/my-library-android-1.0.module", ... } }, ... ] Имя варианта Атрибуты Адрес артефакта
  62. Публикация 62 org.example:my-library:1.0 commonMain.dependencies { api "org.example:my-library" } Корневой артефакт

  63. Проблемы модели 1.2 1. Число модулей растет с числом платформ

    2. Зависимости настраиваются вручную 63
  64. Gradle metadata Gradle metadata имеет экспериментальный статус → Нет совместимости

    между разными версиями* 64 *До версии 5.3: https://docs.gradle.org/5.3/release-notes.html
  65. Gradle metadata 65 org.example:my-library:1.0 org.example:my-library:1.0 org.example:my-library:1.0 org.example: my-application:1.0 Gradle: 5.0

    Metadata: 0.4 Gradle: 4.7 Metadata: 0.3 Depends on
  66. Gradle metadata 66 org.example:my-library:1.0 org.example:my-library:1.0 org.example:my-library:1.0 org.example: my-application:1.0 Gradle: 5.0

    Metadata: 0.4 Gradle: 4.7 Metadata: 0.3 Depends on FAILURE: Build failed with an exception. ... > Could not parse module metadata my-library-1.0.module > Unsupported format version '0.4' specified in module metadata. This version of Gradle supports format version 0.3 only.
  67. Gradle metadata 1. Не критично для мультиплатформенных проектов • Метаданные

    можно вырубить 67
  68. Gradle metadata 1. Не критично для мультиплатформенных проектов • Метаданные

    можно вырубить 2. Критично для библиотек, доступных вне MPP 68
  69. Gradle metadata 1. Не критично для мультиплатформенных проектов • Метаданные

    можно вырубить 2. Критично для библиотек, доступных вне MPP • JVM/JS публикуем без метаданных • Native - с метаданными 69
  70. Приложение Android + iOS 70

  71. Пример: приложение Android + iOS https://kotlinlang.org/docs/tutorials/native/mpp-ios-android.html 71

  72. Структура проекта 72 Мультиплатформенная библиотека Android-приложение (Android Studio) iOS-приложение (Xcode)

  73. Структура проекта 73 В 1.2 было бы так:

  74. Библиотека SharedCode 74

  75. Библиотека SharedCode 75 expect fun platformName(): String fun screenMessage(): String

    = "Kotlin Rocks on ${platformName()}"
  76. Библиотека SharedCode 76 actual fun platformName() = "Android"

  77. Библиотека SharedCode 77 // Call iOS platform API. actual fun

    platformName() = with(UIDevice.currentDevice) { "$systemName $systemVersion" }
  78. Сборка проекта 78 JAR ObjC framework

  79. Сборка проекта 79 JAR ObjC framework APK

  80. Сборка проекта 80 JAR ObjC framework iOS binary

  81. Android-приложение 81 apply plugin: 'com.android.application' apply plugin: 'kotlin-android' dependencies {

    implementation project(':SharedCode') }
  82. Android-приложение 82 import org.kotlin.mpp.mobile.* // ... val view = findViewById<TextView>(

    R.id.main_text ) view.text = screenMessage()
  83. iOS-приложение 83

  84. iOS-приложение 84 Копируем фреймворк в нужную директорию

  85. iOS-приложение iosMain iosSimulatorMain ios-simulator main 85 Source sets Targets commonMain

    ios-device main iosDeviceMain
  86. ios simulator device iOS-приложение iosMain iosSimulatorMain 86 Source sets Targets

    commonMain iosDeviceMain
  87. iOS-приложение iosMain iosSimulatorMain 87 commonMain iosDeviceMain iOS SDK iOS Simulator

    SDK ios simulator device
  88. iOS-приложение iosMain iosSimulatorMain 88 commonMain iosDeviceMain iOS SDK iOS Simulator

    SDK ios simulator device ?
  89. iOS-приложение 89 kotlin { if (System.getenv('SDK_NAME')?.startsWith("iphoneos")) { iosArm64('iOS') } else

    { iosX64('iOS') } targets.iOS { /* Configure iOS. */ } }
  90. iOS-приложение 90 import SharedCode // ... @IBOutlet weak var label:

    UILabel! // … label.text = CommonKt.screenMessage()
  91. Заключение 91

  92. Больше примеров • Kotlin/Native samples: https://github.com/JetBrains/kotlin-native/tree/master/samples • Kotlinx-coroutines: https://github.com/Kotlin/kotlinx.coroutines/ •

    Ktor https://github.com/ktorio/ktor • Kotlin/Native обертка для libui https://github.com/msink/kotlin-libui 92
  93. Обратная связь • Kotlin Slack: kotlinlang.slack.com (канал #multiplatform) • Баг-трекер:

    youtrack.jetbrains.com/issues/KT • GitHub: ◦ github.com/JetBrains/kotlin ◦ github.com/JetBrains/kotlin-native 93
  94. Мультиплатформенные проекты в Kotlin 1.3 Илья Матвеев ilmat192@gmail.com JetBrains

  95. Мультиплатформенные проекты в Kotlin 1.3 Матвеев Илья, JetBrains ilmat192@gmail.com CodeFest

    2019