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

Introducing Kotlin Multiplatform in an existing project | Kotlin Budapest Meetup - December

Introducing Kotlin Multiplatform in an existing project | Kotlin Budapest Meetup - December

After discovering a new interesting technology or framework, you will probably start asking yourself how to integrate it into an existing project. That’s because, the possibility to start with a blank canvas is rare (not impossible, but rare).

This is also the case for Kotlin Multiplatform, and even though it is still in alpha, you can already start to use it in production applications.

In this talk, we will understand which part of the code can be a starting point for sharing, how to consume the shared code and how to structure an existing project to have an as smooth as possible integration.

9da5d5cc4b6a9f28058152e28364b02a?s=128

Marco Gomiero

December 14, 2021
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. Marco Gomiero Kotlin Budapest Meetup Introducing Kotlin Multiplatform in an

    existing project
  2. Kotlin Budapest Meetup - @marcoGomier Marco Gomiero 👨💻 Android Engineer

    @ TIER 🛴 🇩🇪 🇮🇹 
 Google Developer Expert for Kotlin > Twitter: @marcoGomier 
 > Github: prof18 
 > Website: marcogomiero.com
  3. Kotlin Budapest Meetup - @marcoGomier Kotlin Multiplatform

  4. Kotlin Budapest Meetup - @marcoGomier Kotlin Multiplatform • Not about

    compiling all code for all platforms • Share as much [NO UI] code as possible
  5. Kotlin Budapest Meetup - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOS macOS watchOS tvOS Linux Windows
  6. Kotlin Budapest Meetup - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOS macOS watchOS tvOS Linux Windows Mobile App
  7. Kotlin Budapest Meetup - @marcoGomier https://kotlinlang.org/lp/mobile/

  8. Kotlin Budapest Meetup @marcoGomier New Project

  9. Kotlin Budapest Meetup - @marcoGomier New Projects: Android Studio plugin

  10. Kotlin Budapest Meetup - @marcoGomier New Projects: Android Studio plugin

  11. Kotlin Budapest Meetup - @marcoGomier Kotlin Budapest Meetup - @marcoGomier

    . └── kmm-project ├── androidApp ├── iosApp └── shared Same Repository
  12. Kotlin Budapest Meetup - @marcoGomier shared androidApp Gradle Module Android

  13. Kotlin Budapest Meetup - @marcoGomier // settings.gradle.kts include(":shared") // build.gradle.kts

    implementation(project(":shared")) Android
  14. Kotlin Budapest Meetup - @marcoGomier shared androidApp iosApp Gradle Module

    Framework iOS
  15. Kotlin Budapest Meetup - @marcoGomier New Projects: Android Studio plugin

  16. Kotlin Budapest Meetup - @marcoGomier embedAndSignAppleFrameworkForXcode https://blog.jetbrains.com/kotlin/2021/07/multiplatform-gradle-plugin-improved-for-connecting-kmm-modules/ packForXcode From Kotlin

    1.5.20 iOS
  17. Kotlin Budapest Meetup - @marcoGomier

  18. Kotlin Budapest Meetup - @marcoGomier CocoaPods integration https://kotlinlang.org/docs/reference/native/cocoapods.html 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.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
  19. Kotlin Budapest Meetup - @marcoGomier iOS Project: Podfile pod 'shared',

    :path => ' .. /shared'
  20. Kotlin Budapest Meetup @marcoGomier Existing Project

  21. Kotlin Budapest Meetup - @marcoGomier Photo by Ashkan Forouzani on

    Unsplash 🙅 shared androidApp iosApp Gradle Module Framework Same Repository
  22. Kotlin Budapest Meetup - @marcoGomier Photo by Erwan Hesry on

    Unsplash 💡 Create a library!
  23. Kotlin Budapest Meetup - @marcoGomier Photo by Erwan Hesry on

    Unsplash A little piece of tech stack to start with
  24. Kotlin Budapest Meetup - @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?
  25. Kotlin Budapest Meetup - @marcoGomier • DTOs • Common Models

    • Utility methods, aka `object Utils {}` • Analytics • . . . Where to start?
  26. Kotlin Budapest Meetup - @marcoGomier Existing Projects: Intellij Wizard

  27. Kotlin Budapest Meetup - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework 
 Maven 
 Cocoa Repo
  28. Kotlin Budapest Meetup - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework 
 Maven 
 Cocoa Repo Android App Repository KMP Repository iOs App Repository
  29. Kotlin Budapest Meetup @marcoGomier How to publish: Android

  30. Kotlin Budapest Meetup - @marcoGomier https://github.com/prof18/shared-hn-android-ios-backend * based on a

    true story project
  31. Kotlin Budapest Meetup - @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") } } } Setup a Maven repository to share the artifacts: build.gradle.kts
  32. Kotlin Budapest Meetup - @marcoGomier Publish the artifacts ./gradlew publish

  33. Kotlin Budapest Meetup - @marcoGomier Publish the artifacts ./gradlew publish

    ./gradlew publishToMavenLocal
  34. Kotlin Budapest Meetup - @marcoGomier Android implementation(“com.prof18.hn.foundation:hn-foundation-android:2.0.0")

  35. Kotlin Budapest Meetup @marcoGomier How to publish: iOS

  36. Kotlin Budapest Meetup - @marcoGomier 🥵

  37. Kotlin Budapest Meetup - @marcoGomier embedAndSignAppleFrameworkForXcode packForXcode CocoaPods integration

  38. Kotlin Budapest Meetup - @marcoGomier 🤔

  39. Kotlin Budapest Meetup - @marcoGomier XCFramework

  40. Kotlin Budapest Meetup - @marcoGomier NEW https://devstreaming-cdn.apple.com/videos/wwdc/2019/416h8485aty341c2/416/416_binary_frameworks_in_swift.pdf

  41. Kotlin Budapest Meetup - @marcoGomier https://devstreaming-cdn.apple.com/videos/wwdc/2019/416h8485aty341c2/416/416_binary_frameworks_in_swift.pdf NEW iOS mac OS

    watch OS tvOS
  42. Kotlin Budapest Meetup - @marcoGomier

  43. Kotlin Budapest Meetup - @marcoGomier https: // developer.apple.com/videos/play/wwdc2019/416/

  44. Kotlin Budapest Meetup - @marcoGomier XCFramework Official Support from Kotlin

    1.5.30 https://kotlinlang.org/docs/whatsnew1530.html#support-for-xcframeworks
  45. XCFramework prior to Kotlin 1.5.30 https://github.com/prof18/kmp-xcframework-sample/blob/pre-kotlin-1.5.30/build.gradle.kts#L86 register("buildReleaseXCFramework", Exec :: class.java)

    { description = "Create a Release XCFramework" dependsOn("link${libName}ReleaseFrameworkIosArm64") dependsOn("link${libName}ReleaseFrameworkIosX64") val arm64FrameworkPath = "$rootDir/build/bin/iosArm64/${libName}ReleaseFramework/${libName}.framework" val arm64DebugSymbolsPath = "$rootDir/build/bin/iosArm64/${libName}ReleaseFramework/${libName}.framework.dSYM" val x64FrameworkPath = "$rootDir/build/bin/iosX64/${libName}ReleaseFramework/${libName}.framework" val x64DebugSymbolsPath = "$rootDir/build/bin/iosX64/${libName}ReleaseFramework/${libName}.framework.dSYM" val xcFrameworkDest = File("$rootDir/ .. /kmp-xcframework-dest/$libName.xcframework") executable = "xcodebuild" args(mutableListOf<String>().apply { add("-create-xcframework") add("-output") add(xcFrameworkDest.path) // Real Device add("-framework") add(arm64FrameworkPath) add("-debug-symbols") add(arm64DebugSymbolsPath) // Simulator add("-framework") add(x64FrameworkPath) add("-debug-symbols") add(x64DebugSymbolsPath) }) doFirst { xcFrameworkDest.deleteRecursively() } }
  46. Kotlin Budapest Meetup - @marcoGomier XCFramework prior to Kotlin 1.5.30

  47. Kotlin Budapest Meetup - @marcoGomier XCFramework prior to Kotlin 1.5.30

    https: / / www.marcogomiero.com/posts/2021/build-xcframework-kmp/
  48. Kotlin Budapest Meetup - @marcoGomier XCFramework: build.gradle.kts https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/build.gradle.kts import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework

    val libName = "LibraryName" kotlin { val xcFramework = XCFramework(libName) ios { binaries.framework(libName) { xcFramework.add(this) } } ... }
  49. Kotlin Budapest Meetup - @marcoGomier XCFramework: build.gradle.kts https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/build.gradle.kts import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework

    val libName = "LibraryName" kotlin { val xcFramework = XCFramework(libName) ios { binaries.framework(libName) { xcFramework.add(this) } } ... }
  50. Kotlin Budapest Meetup - @marcoGomier XCFramework: build.gradle.kts https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/build.gradle.kts import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework

    val libName = "LibraryName" kotlin { val xcFramework = XCFramework(libName) ios { binaries.framework(libName) { xcFramework.add(this) } } ... }
  51. Kotlin Budapest Meetup - @marcoGomier XCFramework: build.gradle.kts https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/build.gradle.kts import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFramework

    val libName = "LibraryName" kotlin { val xcFramework = XCFramework(libName) ios { binaries.framework(libName) { xcFramework.add(this) } } ... }
  52. Kotlin Budapest Meetup - @marcoGomier assemble${libName}XCFramework assemble${libName}DebugXCFramework assemble${libName}ReleaseXCFramework XCFramework: Gradle

    tasks
  53. Kotlin Budapest Meetup - @marcoGomier . ├── build ├── XCFrameworks

    ├── debug │ └── LibraryName.xcframework └── release └── LibraryName.xcframework XCFramework
  54. Kotlin Budapest Meetup - @marcoGomier https://github.com/prof18/hn-foundation-cocoa-xcframework CocoaPod Repository

  55. Kotlin Budapest Meetup - @marcoGomier HNFoundation.podspec https://github.com/prof18/hn-foundation-cocoa-xcframework/blob/main/HNFoundation.podspec Pod :: Spec.new

    do |s| # ――― Spec Metadata ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――― # # # These will help people to find your library, and whilst it # can feel like a chore to fill in it's definitely to your advantage. The # summary should be tweet-length, and the description more in depth. # s.name = "HNFoundation" s.version = "2.0.0" s.summary = "HNFoundation KMP library" s.homepage = "https: // github.com/prof18/hn-foundation-cocoa-xcframework" s.license = "Apache" s.author = { "Marco Gomiero" = > "mg@me.com" } s.vendored_frameworks = 'HNFoundation.xcframework' s.source = { :git => "git@github.com:prof18/hn-foundation-cocoa-xcframework", :tag => " #{ s.version}" } s.exclude_files = "Classes/Exclude" end
  56. None
  57. https: // guides.cocoapods.org/making/private-cocoapods.html

  58. Kotlin Budapest Meetup - @marcoGomier iOs Project: Podfile # For

    develop releases: pod 'HNFoundation', :git => “git@github.com:prof18/hn-foundation-cocoa-xcframework.git", :branch = > 'develop'
  59. Kotlin Budapest Meetup - @marcoGomier iOs Project: Podfile # For

    develop releases: pod 'HNFoundation', :git => “git@github.com:prof18/hn-foundation-cocoa-xcframework.git", :branch = > 'develop' # For stable releases pod 'HNFoundation', :git => “git@github.com:prof18/hn-foundation-cocoa-xcframework.git", :tag => ‘2.0.0'
  60. register("publishDevFramework") { description = "Publish iOs framework to the Cocoa

    Repo" doFirst { project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "checkout", "develop").standardOutput } } dependsOn("assemble${libName}DebugXCFramework") doLast { copy { from("$buildDir/XCFrameworks/debug") into("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") } .. .. https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/build.gradle.kts#L110 Publish Dev Framework Task
  61. Kotlin Budapest Meetup - @marcoGomier workingDir = File("$rootDir/ .. /

    .. /hn-foundation-cocoa-xcframework") commandLine("git", "checkout", "develop").standardOutput } } dependsOn("assemble${libName}DebugXCFramework") doLast { copy { from("$buildDir/XCFrameworks/debug") into("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") } val dir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework/$libName.podspec") val tempFile = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework/$libName.podspec.new") val reader = dir.bufferedReader() val writer = tempFile.bufferedWriter() var currentLine: String? while (reader.readLine().also { currLine -> currentLine = currLine } != null) { if (currentLine ?. startsWith("s.version") == true) { writer.write("s.version = \"${libVersionName}\"" + System.lineSeparator())
  62. Kotlin Budapest Meetup - @marcoGomier into("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework")

    } val dir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework/$libName.podspec") val tempFile = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework/$libName.podspec.new") val reader = dir.bufferedReader() val writer = tempFile.bufferedWriter() var currentLine: String? while (reader.readLine().also { currLine -> currentLine = currLine } != null) { if (currentLine ?. startsWith("s.version") == true) { writer.write("s.version = \"${libVersionName}\"" + System.lineSeparator()) } else { writer.write(currentLine + System.lineSeparator()) } } writer.close() reader.close() val successful = tempFile.renameTo(dir) if (successful) { project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework")
  63. Kotlin Budapest Meetup - @marcoGomier if (successful) { project.exec {

    workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "add", ".").standardOutput } val dateFormatter = SimpleDateFormat("dd/MM/yyyy - HH:mm", Locale.getDefault()) project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine( "git", "commit", "-m", "\"New dev release: ${libVersionName}-${dateFormatter.format(Date())}\"" ).standardOutput } project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "push", "origin", "develop").standardOutput } }
  64. https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/build.gradle.kts#L177 Publish Release Framework Task register("publishFramework") { description = "Publish

    iOs framework to the Cocoa Repo" doFirst { project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "checkout", "main").standardOutput } } dependsOn("assemble${libName}ReleaseXCFramework") doLast { copy { from("$buildDir/XCFrameworks/release") into("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") } .. ..
  65. Kotlin Budapest Meetup - @marcoGomier project.exec { workingDir = File("$rootDir/

    .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "checkout", "main").standardOutput } } dependsOn("assemble${libName}ReleaseXCFramework") doLast { copy { from("$buildDir/XCFrameworks/release") into("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") } val dir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework/$libName.podspec") val tempFile = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework/$libName.podspec.new") val reader = dir.bufferedReader() val writer = tempFile.bufferedWriter() var currentLine: String? while (reader.readLine().also { currLine -> currentLine = currLine } != null) { if (currentLine ?. startsWith("s.version") == true) {
  66. Kotlin Budapest Meetup - @marcoGomier project.exec { workingDir = File("$rootDir/

    .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "add", ".").standardOutput } project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "commit", "-m", "\"New release: ${libVersionName}\"").standardOu } project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "tag", libVersionName).standardOutput } project.exec { workingDir = File("$rootDir/ .. / .. /hn-foundation-cocoa-xcframework") commandLine("git", "push", "origin", "main", " -- tags").standardOutput } } } }
  67. Kotlin Budapest Meetup - @marcoGomier 🎉

  68. Kotlin Budapest Meetup @marcoGomier Concurrency

  69. Kotlin Budapest Meetup - @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
  70. Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared <object>@c2ddb8

    from other thread
  71. Kotlin Budapest Meetup - @marcoGomier 🥶 Freeze the object to

    share it between threads
  72. Kotlin Budapest Meetup - @marcoGomier override fun deserialize(jsonString: String): NewsDTO

    { val newsDTO: NewsDTO = json.decodeFromString(jsonString) newsDTO.freeze() return newsDTO }
  73. Kotlin Budapest Meetup - @marcoGomier https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/

  74. Kotlin Budapest Meetup @marcoGomier Conclusions

  75. Kotlin Budapest Meetup - @marcoGomier Faced [and resolved] difficulties •

    How to organise the project without a monorepo • How to effectively distribute the iOS framework
  76. Kotlin Budapest Meetup - @marcoGomier Start little

  77. Kotlin Budapest Meetup - @marcoGomier Start little Go bigger then

  78. Kotlin Budapest Meetup - @marcoGomier Start little then go bigger

    • Validate the process with “little” effort • Then you can go bigger and share more “features” 

  79. Bibliography / Useful Links • https: / / kotlinlang.org/lp/mobile/ •

    https: / / www.marcogomiero.com/posts/2020/my-2cents-cross-platform/ • https: / / giansegato.com/essays/a-technical-framework-for-early-stage-startups/ • https: / / giansegato.com/essays/the-ebb-and-the-flow-of-product-development/ • https: / / www.marcogomiero.com/posts/2021/build-xcframework-kmp/ • https: / / www.marcogomiero.com/posts/2021/kmp-xcframework-official-support/ • https: / / www.marcogomiero.com/posts/2020/kotlin-serialization-alamofire/ • https: / / www.marcogomiero.com/posts/2021/kmp-existing-project/ • https: / / www.youtube.com/watch?v=oxQ6e1VeH4M • https: / / dev.to/kpgalligan/series/3739 • https: / / blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager- development-preview/
  80. Marco Gomiero Kotlin Budapest Meetup Thank you! > Twitter: @marcoGomier

    
 > Github: prof18 
 > Website: marcogomiero.com