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

Introducing Kotlin Multiplatform in an existing project - We Are Developers - Mobile Day

Introducing Kotlin Multiplatform in an existing project - We Are Developers - Mobile Day

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

November 03, 2021
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. Marco Gomiero We Are Developers - Mobile Day Introducing Kotlin

    Multiplatform in an existing project
  2. We Are Developers - Mobile Day - @marcoGomier Marco Gomiero

    👨💻 Android Engineer @ TIER 🛴 🇩🇪 🇮🇹 
 Google Developer Expert for Kotlin > Twitter: @marcoGomier 
 > Github: prof18 
 > Website: marcogomiero.com
  3. We Are Developers - Mobile Day - @marcoGomier Kotlin Multiplatform

  4. We Are Developers - Mobile Day - @marcoGomier Kotlin Multiplatform

    • Not about compiling all code for all platforms • Share as much [NO UI] code as possible
  5. We Are Developers - Mobile Day - @marcoGomier Common Kotlin

    Kotlin/JVM Kotlin/JS Kotlin/Native Java Android Browser NodeJS Android NDK iOS macOS watchOS tvOS Linux Windows
  6. We Are Developers - Mobile Day - @marcoGomier Common Kotlin

    Kotlin/JVM Kotlin/JS Kotlin/Native Java Android Browser NodeJS Android NDK iOS macOS watchOS tvOS Linux Windows Mobile App
  7. We Are Developers - Mobile Day @marcoGomier New Project

  8. We Are Developers - Mobile Day - @marcoGomier New Projects:

    Android Studio plugin
  9. We Are Developers - Mobile Day - @marcoGomier New Projects:

    Android Studio plugin
  10. We Are Developers - Mobile Day - @marcoGomier We Are

    Developers - Mobile Day - @marcoGomier . └── kmm-project ├── androidApp ├── iosApp └── shared Same Repository
  11. We Are Developers - Mobile Day - @marcoGomier shared androidApp

    Gradle Module Android
  12. We Are Developers - Mobile Day - @marcoGomier // settings.gradle.kts

    include(":shared") // build.gradle.kts implementation(project(":shared")) Android
  13. We Are Developers - Mobile Day - @marcoGomier shared androidApp

    iosApp Gradle Module Framework iOS
  14. We Are Developers - Mobile Day - @marcoGomier New Projects:

    Android Studio plugin
  15. We Are Developers - Mobile Day - @marcoGomier embedAndSignAppleFrameworkForXcode https://blog.jetbrains.com/kotlin/2021/07/multiplatform-gradle-plugin-improved-for-connecting-kmm-modules/

    packForXcode From Kotlin 1.5.20 iOS
  16. We Are Developers - Mobile Day - @marcoGomier

  17. We Are Developers - Mobile Day - @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
  18. We Are Developers - Mobile Day - @marcoGomier iOS Project:

    Podfile pod 'shared', :path => ' .. /shared'
  19. We Are Developers - Mobile Day @marcoGomier Existing Project

  20. We Are Developers - Mobile Day - @marcoGomier Photo by

    Ashkan Forouzani on Unsplash 🙅 shared androidApp iosApp Gradle Module Framework Same Repository
  21. We Are Developers - Mobile Day - @marcoGomier Photo by

    Erwan Hesry on Unsplash 💡 Create a library!
  22. We Are Developers - Mobile Day - @marcoGomier Photo by

    Erwan Hesry on Unsplash A little piece of tech stack to start with
  23. We Are Developers - Mobile Day - @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?
  24. We Are Developers - Mobile Day - @marcoGomier • DTOs

    • Common Models • Utility methods, aka `object Utils {}` • Analytics • . . . Where to start?
  25. We Are Developers - Mobile Day - @marcoGomier Existing Projects:

    Intellij Wizard
  26. We Are Developers - Mobile Day - @marcoGomier Common Kotlin

    Android App iOs App .aar Framework 
 Maven 
 Cocoa Repo
  27. We Are Developers - Mobile Day - @marcoGomier Common Kotlin

    Android App iOs App .aar Framework 
 Maven 
 Cocoa Repo Android App Repository KMP Repository iOs App Repository
  28. We Are Developers - Mobile Day @marcoGomier How to publish:

    Android
  29. We Are Developers - Mobile Day - @marcoGomier https://github.com/prof18/shared-hn-android-ios-backend *

    based on a true story project
  30. We Are Developers - Mobile Day - @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
  31. We Are Developers - Mobile Day - @marcoGomier Publish the

    artifacts ./gradlew publish
  32. We Are Developers - Mobile Day - @marcoGomier Publish the

    artifacts ./gradlew publish ./gradlew publishToMavenLocal
  33. We Are Developers - Mobile Day - @marcoGomier Android implementation(“com.prof18.hn.foundation:hn-foundation-android:2.0.0")

  34. We Are Developers - Mobile Day @marcoGomier How to publish:

    iOS
  35. We Are Developers - Mobile Day - @marcoGomier 🥵

  36. We Are Developers - Mobile Day - @marcoGomier embedAndSignAppleFrameworkForXcode packForXcode

    CocoaPods integration
  37. We Are Developers - Mobile Day - @marcoGomier 🤔

  38. We Are Developers - Mobile Day - @marcoGomier XCFramework

  39. We Are Developers - Mobile Day - @marcoGomier NEW https://devstreaming-cdn.apple.com/videos/wwdc/2019/416h8485aty341c2/416/416_binary_frameworks_in_swift.pdf

  40. We Are Developers - Mobile Day - @marcoGomier https://devstreaming-cdn.apple.com/videos/wwdc/2019/416h8485aty341c2/416/416_binary_frameworks_in_swift.pdf NEW

    iOS mac OS watch OS tvOS
  41. We Are Developers - Mobile Day - @marcoGomier

  42. We Are Developers - Mobile Day - @marcoGomier https: //

    developer.apple.com/videos/play/wwdc2019/416/
  43. We Are Developers - Mobile Day - @marcoGomier XCFramework Official

    Support from Kotlin 1.5.30 https://kotlinlang.org/docs/whatsnew1530.html#support-for-xcframeworks
  44. 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() } }
  45. We Are Developers - Mobile Day - @marcoGomier XCFramework prior

    to Kotlin 1.5.30
  46. We Are Developers - Mobile Day - @marcoGomier XCFramework prior

    to Kotlin 1.5.30 https: / / www.marcogomiero.com/posts/2021/build-xcframework-kmp/
  47. We Are Developers - Mobile Day - @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) } } ... }
  48. We Are Developers - Mobile Day - @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. We Are Developers - Mobile Day - @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. We Are Developers - Mobile Day - @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. We Are Developers - Mobile Day - @marcoGomier assemble${libName}XCFramework assemble${libName}DebugXCFramework

    assemble${libName}ReleaseXCFramework XCFramework: Gradle tasks
  52. We Are Developers - Mobile Day - @marcoGomier . ├──

    build ├── XCFrameworks ├── debug │ └── LibraryName.xcframework └── release └── LibraryName.xcframework XCFramework
  53. We Are Developers - Mobile Day - @marcoGomier https://github.com/prof18/hn-foundation-cocoa-xcframework CocoaPod

    Repository
  54. We Are Developers - Mobile Day - @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
  55. None
  56. https: // guides.cocoapods.org/making/private-cocoapods.html

  57. We Are Developers - Mobile Day - @marcoGomier iOs Project:

    Podfile # For develop releases: pod 'HNFoundation', :git => “git@github.com:prof18/hn-foundation-cocoa-xcframework.git", :branch = > 'develop'
  58. We Are Developers - Mobile Day - @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'
  59. 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
  60. We Are Developers - Mobile Day - @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())
  61. We Are Developers - Mobile Day - @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")
  62. We Are Developers - Mobile Day - @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 } }
  63. 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") } .. ..
  64. We Are Developers - Mobile Day - @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) {
  65. We Are Developers - Mobile Day - @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 } } } }
  66. We Are Developers - Mobile Day - @marcoGomier 🎉

  67. We Are Developers - Mobile Day @marcoGomier Concurrency

  68. We Are Developers - Mobile Day - @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. We Are Developers - Mobile Day - @marcoGomier 🥶 Freeze

    the object to share it between threads
  71. We Are Developers - Mobile Day - @marcoGomier override fun

    deserialize(jsonString: String): NewsDTO { val newsDTO: NewsDTO = json.decodeFromString(jsonString) newsDTO.freeze() return newsDTO }
  72. We Are Developers - Mobile Day - @marcoGomier https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/

  73. We Are Developers - Mobile Day @marcoGomier Conclusions

  74. We Are Developers - Mobile Day - @marcoGomier Faced [and

    resolved] difficulties • How to organise the project without a monorepo • How to effectively distribute the iOS framework
  75. We Are Developers - Mobile Day - @marcoGomier Start little

  76. We Are Developers - Mobile Day - @marcoGomier Start little

    Go bigger then
  77. We Are Developers - Mobile Day - @marcoGomier Start little

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

  78. 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/
  79. Marco Gomiero We Are Developers - Mobile Day Thank you!

    > Twitter: @marcoGomier 
 > Github: prof18 
 > Website: marcogomiero.com