Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Hi, have you met Kotlin Multiplatform? | Marca User Group

Hi, have you met Kotlin Multiplatform? | Marca User Group

Kotlin Multiplatform is getting more and more hype every day, even if it’s still in alpha. We constantly read of new companies and teams that are trying KMP for experiments and production projects alike. And we’re left wondering: it safe to do so? Why picking KMP over any another cross-platform solution? How to approach it? And, most importantly, is it possible to start using it in existing projects?
In this talk, I’ll answer to these questions, clarifying all the doubts and making you ready to use and love Kotlin Multiplatform.

9da5d5cc4b6a9f28058152e28364b02a?s=128

Marco Gomiero

November 26, 2020
Tweet

Transcript

  1. Marco Gomiero Marca User Group Hi, have you met Kotlin

    Multiplatform?
  2. Marca User Group - @marcoGomier Marco Gomiero Tech Lead @

    Uniwhere Co-Lead @ GDG Venezia > Twitter: @marcoGomier > Github: prof18 > Website: marcogomiero.com
  3. Marca User Group - @marcoGomier https://netflixtechblog.com/netflix-android-and-ios-studio-apps-kotlin-multiplatform-d6d4d8d25d23

  4. Marca User Group - @marcoGomier <Your name here> https://kotlinlang.org/lp/mobile/case-studies/

  5. Marca User Group - @marcoGomier https://twitter.com/piannaf/status/1206691901015044096

  6. Marca User Group @marcoGomier So, what is Kotlin Multiplatform?

  7. Marca User Group - @marcoGomier “Classic” Cross Platform Solutions React

    Native Flutter
  8. Marca User Group - @marcoGomier Unify UI declaration across platforms

  9. Marca User Group - @marcoGomier React Native: call native widgets

    through a “bridge”
  10. Marca User Group - @marcoGomier Flutter: uses its own widget

  11. Marca User Group - @marcoGomier • Cons: use of “middlemen”,

    custom widgets • Unifying UI declaration between platform is complicated • Different platforms have different patterns “Classic” Cross Platform Solutions
  12. Marca User Group - @marcoGomier • Not about compiling all

    code for all platforms • Share as much [NO UI] code as possible Kotlin Multiplatform
  13. Marca User Group - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOs macOS watchOS tvOS Linux Windows
  14. Marca User Group - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOs macOS watchOS tvOS Linux Windows Mobile App
  15. Marca User Group - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOs macOS watchOS tvOS Linux Windows Mobile & Desktop App + JVM Backend
  16. Marca User Group - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOs macOS watchOS tvOS Linux Windows Full Stack JS App
  17. Marca User Group - @marcoGomier • expect/actual mechanism • Declaration

    in common target and implementation in all specific targets • Works for functions, classes, interfaces, enumerations, properties, and annotations. Platform-specific APIs?
  18. Marca User Group - @marcoGomier Common expect fun debugLog(tag: String,

    message: String)
  19. Marca User Group - @marcoGomier Android expect fun debugLog(tag: String,

    message: String) import android.util.Log actual fun debugLog(tag: String, message: String) { Log.d(tag, message) }
  20. Marca User Group - @marcoGomier iOs expect fun debugLog(tag: String,

    message: String) import platform.Foundation.NSLog actual fun debugLog(tag: String, message: String) { if (Platform.isDebugBinary) { NSLog("%s: %s", tag, message) } }
  21. Marca User Group - @marcoGomier https://kotlinlang.org/lp/mobile/

  22. Marca User Group - @marcoGomier https://kotlinlang.org/docs/mobile/home.html

  23. Marca User Group - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/Native Android

    iOs Mobile App
  24. Marca User Group - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework
  25. Marca User Group - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework Greenfield Project Same Repository
  26. Marca User Group - @marcoGomier Same Repository

  27. Marca User Group - @marcoGomier Android "// settings.gradle.kts include(":shared") "//

    build.gradle.kts implementation(project(":shared"))
  28. Marca User Group - @marcoGomier iOs: packForXcode val packForXcode by

    tasks.creating(Sync"::class) { group = "build" val mode = System.getenv("CONFIGURATION") "?: "DEBUG" val sdkName = System.getenv("SDK_NAME") "?: "iphonesimulator" val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64" val framework = kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode) inputs.property("mode", mode) dependsOn(framework.linkTask) val targetDir = File(buildDir, "xcode-frameworks") from({ framework.outputDirectory }) into(targetDir) } tasks.getByName("build").dependsOn(packForXcode) Auto generated when creating a Kotlin Multiplatform mobile app
  29. Marca User Group - @marcoGomier Pod"::Spec.new do |spec| spec.name =

    'shared' spec.version = '1.0-SNAPSHOT' spec.homepage = 'Link to a Kotlin/Native module homepage' spec.source = { :git "=> "Not Published", :tag "=> "Cocoapods/"#{spec.name}/"#{spec.version}" } spec.authors = '' spec.license = '' spec.summary = 'Some description for a Kotlin/Native module' spec.static_framework = true spec.vendored_frameworks = "build/cocoapods/framework/shared.framework" spec.libraries = "c"++" spec.module_name = ""#{spec.name}_umbrella" spec.pod_target_xcconfig = { 'KOTLIN_TARGET[sdk=iphonesimulator*]' "=> 'ios_x64', 'KOTLIN_TARGET[sdk=iphoneos*]' "=> 'ios_arm', 'KOTLIN_TARGET[sdk=watchsimulator*]' "=> 'watchos_x86', 'KOTLIN_TARGET[sdk=watchos*]' "=> 'watchos_arm', 'KOTLIN_TARGET[sdk=appletvsimulator*]' "=> 'tvos_x64', 'KOTLIN_TARGET[sdk=appletvos*]' "=> 'tvos_arm64', 'KOTLIN_TARGET[sdk=macosx*]' "=> 'macos_x64' } spec.script_phases = [ { :name "=> 'Build shared', :execution_position "=> :before_compile, :shell_path "=> '/bin/sh', :script "=> ""<<-SCRIPT set -ev REPO_ROOT="$PODS_TARGET_SRCROOT" "$REPO_ROOT/"../gradlew" -p "$REPO_ROOT" :shared:syncFramework \ -Pkotlin.native.cocoapods.target=$KOTLIN_TARGET \ -Pkotlin.native.cocoapods.configuration=$CONFIGURATION \ -Pkotlin.native.cocoapods.cflags="$OTHER_CFLAGS" \ -Pkotlin.native.cocoapods.paths.headers="$HEADER_SEARCH_PATHS" \ -Pkotlin.native.cocoapods.paths.frameworks="$FRAMEWORK_SEARCH_PATHS" SCRIPT } ] end CocoaPods plugin: shared.podspec https://kotlinlang.org/docs/reference/native/cocoapods.html
  30. Marca User Group - @marcoGomier } spec.script_phases = [ {

    :name "=> 'Build shared', :execution_position "=> :before_compile, :shell_path "=> '/bin/sh', :script "=> ""<<-SCRIPT set -ev REPO_ROOT="$PODS_TARGET_SRCROOT" "$REPO_ROOT/"../gradlew" -p "$REPO_ROOT" :shared:syncFramework \ -Pkotlin.native.cocoapods.target=$KOTLIN_TARGET \ -Pkotlin.native.cocoapods.configuration=$CONFIGURATION \ -Pkotlin.native.cocoapods.cflags="$OTHER_CFLAGS" \ -Pkotlin.native.cocoapods.paths.headers="$HEADER_SEARCH_PATHS" \ -Pkotlin.native.cocoapods.paths.frameworks="$FRAMEWORK_SEARCH_PATHS" SCRIPT } ] end https://kotlinlang.org/docs/reference/native/cocoapods.html
  31. Marca User Group - @marcoGomier iOs Project: Podfile pod 'shared',

    :path "=> '"../shared'
  32. Marca User Group - @marcoGomier https://github.com/prof18/MoneyFlow

  33. Marca User Group - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework
  34. Marca User Group - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework Brownfield Project Android App Repository KMP Repository iOs App Repository
  35. Marca User Group - @marcoGomier Android implementation("com.prof18.hn.foundation:hn-foundation-android:1.0.0")

  36. Marca User Group - @marcoGomier iOs Project: Podfile pod 'HNFoundation',

    :git "=> "git@github.com:prof18/hn-foundation-cocoa.git", :tag "=> '1.0.0'
  37. Marca User Group - @marcoGomier https://github.com/prof18/shared-hn-android-ios-backend

  38. Marca User Group @marcoGomier How to publish: Android

  39. Marca User Group - @marcoGomier plugins { "//""... id("maven-publish") }

    group = "com.prof18.hn.foundation" version = "1.0" publishing { repositories { maven{ credentials { username = "username" password = "pwd" } url = url("https:"//mymavenrepo.it") } } } How to publish Android: Maven repository
  40. Marca User Group @marcoGomier How to publish: iOs

  41. Marca User Group - @marcoGomier Fat Framework

  42. Marca User Group - @marcoGomier packForXcode Auto generated when creating

    a Kotlin Multiplatform mobile app val packForXcode by tasks.creating(Sync"::class) { group = "build" val mode = System.getenv("CONFIGURATION") "?: "DEBUG" val sdkName = System.getenv("SDK_NAME") "?: "iphonesimulator" val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64" val framework = kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode) inputs.property("mode", mode) dependsOn(framework.linkTask) val targetDir = File(buildDir, "xcode-frameworks") from({ framework.outputDirectory }) into(targetDir) } tasks.getByName("build").dependsOn(packForXcode)
  43. Marca User Group - @marcoGomier spec.pod_target_xcconfig = { 'KOTLIN_TARGET[sdk=iphonesimulator*]' "=>

    'ios_x64', 'KOTLIN_TARGET[sdk=iphoneos*]' "=> 'ios_arm', 'KOTLIN_TARGET[sdk=watchsimulator*]' "=> 'watchos_x86', 'KOTLIN_TARGET[sdk=watchos*]' "=> 'watchos_arm', 'KOTLIN_TARGET[sdk=appletvsimulator*]' "=> 'tvos_x64', 'KOTLIN_TARGET[sdk=appletvos*]' "=> 'tvos_arm64', 'KOTLIN_TARGET[sdk=macosx*]' "=> 'macos_x64' } spec.script_phases = [ { :name "=> 'Build shared', :execution_position "=> :before_compile, :shell_path "=> '/bin/sh', :script "=> ""<<-SCRIPT set -ev REPO_ROOT="$PODS_TARGET_SRCROOT" "$REPO_ROOT/"../gradlew" -p "$REPO_ROOT" :shared:syncFramework \ -Pkotlin.native.cocoapods.target=$KOTLIN_TARGET \ -Pkotlin.native.cocoapods.configuration=$CONFIGURATION \ -Pkotlin.native.cocoapods.cflags="$OTHER_CFLAGS" \ -Pkotlin.native.cocoapods.paths.headers="$HEADER_SEARCH_PATHS" \ -Pkotlin.native.cocoapods.paths.frameworks="$FRAMEWORK_SEARCH_PATHS" SCRIPT } ] CocoaPods plugin: shared.podspec https://kotlinlang.org/docs/reference/native/cocoapods.html
  44. Marca User Group - @marcoGomier val libName = "HNFoundation" tasks

    { register("universalFrameworkDebug", org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask"::class) { baseName = libName from( iosArm64().binaries.getFramework(libName, "Debug"), iosX64().binaries.getFramework(libName, "Debug") ) destinationDir = buildDir.resolve("$rootDir/"../"../hn-foundation-cocoa") group = libName description = "Create the debug framework for iOs" dependsOn("linkHNFoundationDebugFrameworkIosArm64") dependsOn("linkHNFoundationDebugFrameworkIosX64") } ""... } Custom Gradle Task: universalFrameworkDebug https://github.com/prof18/shared-hn-android-ios-backend/blob/master/hn-foundation/build.gradle.kts#L100
  45. Marca User Group - @marcoGomier Custom Gradle Task: universalFrameworkDebug val

    libName = "HNFoundation" tasks { register("universalFrameworkDebug", org.jetbrains.kotlin.gradle.tasks.FatFrameworkTask"::class) { baseName = libName from( iosArm64().binaries.getFramework(libName, "Debug"), iosX64().binaries.getFramework(libName, "Debug") ) destinationDir = buildDir.resolve("$rootDir/"../"../hn-foundation-cocoa") group = libName description = "Create the debug framework for iOs" dependsOn("linkHNFoundationDebugFrameworkIosArm64") dependsOn("linkHNFoundationDebugFrameworkIosX64") } ""... } https://github.com/prof18/shared-hn-android-ios-backend/blob/master/hn-foundation/build.gradle.kts#L100
  46. Marca User Group - @marcoGomier https://github.com/prof18/hn-foundation-cocoa/ CocoaPod Repository

  47. None
  48. https:"//guides.cocoapods.org/making/private-cocoapods.html

  49. Automatic publishing task register("publishFramework") { description = "Publish iOs framework

    to the Cocoa Repo" project.exec { workingDir = File("$rootDir/"../"../hn-foundation-cocoa") commandLine("git", "checkout", "master").standardOutput } "// Create Release Framework for Xcode dependsOn("universalFrameworkRelease") "// Replace doLast { val dir = File("$rootDir/"../"../hn-foundation-cocoa/HNFoundation.podspec") val tempFile = File("$rootDir/"../"../hn-foundation-cocoa/HNFoundation.podspec.new") val reader = dir.bufferedReader() val writer = tempFile.bufferedWriter() var currentLine: String?
  50. Marca User Group - @marcoGomier https://github.com/prof18/shared-hn-android-ios-backend/blob/master/hn-foundation/build.gradle.kts

  51. Marca User Group @marcoGomier How to approach

  52. Marca User Group - @marcoGomier How to approach • Existing

    projects • New projects
  53. Marca User Group - @marcoGomier New Projects: Android Studio plugin

  54. Marca User Group - @marcoGomier New Projects: Android Studio plugin

  55. Marca User Group - @marcoGomier New Projects: Intellij Wizard

  56. Marca User Group - @marcoGomier Marca User Group- @marcoGomier .

    └── kmm-project ├── androidApp ├── iosApp └── shared Photo by Ashkan Forouzani on Unsplash
  57. Marca User Group - @marcoGomier Existing Project

  58. Marca User Group - @marcoGomier Existing Projects: Intellij Wizard

  59. Marca User Group - @marcoGomier You don’t have enough time

    for a big rewrite Photo by Ashkan Forouzani on Unsplash
  60. Marca User Group - @marcoGomier . └── kmm-project ├── androidApp

    ├── iosApp └── shared Photo by Ashkan Forouzani on Unsplash Marca User Group- @marcoGomier
  61. Marca User Group - @marcoGomier Photo by Erwan Hesry on

    Unsplash You can choose a little piece of tech stack to start with
  62. Marca User Group - @marcoGomier • Boring code to write

    multiple times • Code/feature that centralises the source of truth (i.e. a field is nullable or not) • Code/feature that can be gradually extracted Where to start?
  63. Marca User Group - @marcoGomier • DTOs • Common Models

    • Utility methods, aka `object Utils {}` Where to start?
  64. Marca User Group - @marcoGomier Start little

  65. Marca User Group - @marcoGomier Start little Go bigger then

  66. Marca User Group - @marcoGomier Start little then go bigger

    • Validated the process with “little” effort • Next, go bigger and share more “features” For example the data layer, the network layer, etc
  67. Marca User Group @marcoGomier Concurrency

  68. Marca User Group - @marcoGomier • Kotlin/Native different from the

    JVM • One thread "-> no problems, you can use mutable objects • Many thread "-> only immutable objects Concurrency https://kotlinlang.org/docs/mobile/concurrency-overview.html
  69. Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared <object>@c2ddb8

    from other thread
  70. Marca User Group - @marcoGomier Freeze the object to share

    it between threads
  71. Marca User Group - @marcoGomier BaseDTO.kt open class BaseDTO {

    fun makeFrozen() { freeze() } }
  72. Marca User Group - @marcoGomier https://github.com/touchlab/Stately

  73. Marca User Group - @marcoGomier

  74. Marca User Group - @marcoGomier https:"//www.youtube.com/watch?v=oxQ6e1VeH4M

  75. Marca User Group - @marcoGomier

  76. Marca User Group - @marcoGomier https:"//dev.to/kpgalligan/series/3739

  77. Marca User Group - @marcoGomier • 1.4.1 "-> single thread

    on K/N • 1.4.1-native-mt "-> multithreaded version Coroutines https://kotlinlang.org/docs/mobile/concurrency-and-coroutines.html
  78. Marca User Group - @marcoGomier https://blog.jetbrains.com/kotlin/2020/07/kotlin-native-memory-management-roadmap/

  79. Marca User Group @marcoGomier Ecosystem

  80. Marca User Group - @marcoGomier • Networking: Ktor • Persistence:

    SQLDelight, multiplatform-settings • Serialization: kotlinx.serialization • Dependency Injection: koin, Kodein-DI • Reactive: Reaktive • Date/Time: kotlinx-datetime (still experimental) Common Libraries
  81. Marca User Group - @marcoGomier https://github.com/AAkira/Kotlin-Multiplatform-Libraries

  82. Marca User Group - @marcoGomier https://github.com/touchlab/xcode-kotlin

  83. Marca User Group - @marcoGomier

  84. Marca User Group - @marcoGomier https://github.com/touchlab/CrashKiOS

  85. Marca User Group - @marcoGomier

  86. Marca User Group - @marcoGomier

  87. Marca User Group - @marcoGomier https://github.com/getsentry/sentry-cocoa/issues/741

  88. Marca User Group - @marcoGomier Jetpack Compose

  89. Marca User Group - @marcoGomier https://www.jetbrains.com/lp/compose/

  90. Marca User Group - @marcoGomier https://twitter.com/_gurupreet/status/1330118622895869956

  91. Marca User Group - @marcoGomier https://twitter.com/joreilly/status/1324122305866457088

  92. Marca User Group @marcoGomier Conclusions

  93. Marca User Group - @marcoGomier • Kotlin Multiplatform "!= Cross

    Platform • Early adoption stage • It’s the future Conclusions
  94. Marca User Group - @marcoGomier Join the Kotlin Slack https://kotlinlang.org/community/

  95. Marco Gomiero Marca User Group Thank you! > Twitter: @marcoGomier

    > Github: prof18 > Website: marcogomiero.com