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

Introducing Kotlin Multiplatform in an existing project | Kotlin Dev Day Amsterdam

Introducing Kotlin Multiplatform in an existing project | Kotlin Dev Day Amsterdam

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

May 19, 2022
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. @marcoGomier Kotlin Dev Day Introducing Kotlin Multiplatform in an existing

    project Marco Gomiero 👨💻 Senior Android Engineer @ TIER 
 Google Developer Expert for Kotlin
  2. Kotlin Dev Day - @marcoGomier Kotlin Multiplatform Share as much

    [NO UI] code as possible on different platforms
  3. Kotlin Dev Day - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOS macOS watchOS tvOS Linux Windows
  4. Kotlin Dev Day - @marcoGomier Common Kotlin Kotlin/JVM Kotlin/JS Kotlin/Native

    Java Android Browser NodeJS Android NDK iOS macOS watchOS tvOS Linux Windows Mobile App
  5. Kotlin Dev Day @marcoGomier New Project

  6. Kotlin Dev Day - @marcoGomier New Projects: Android Studio plugin

  7. Kotlin Dev Day - @marcoGomier Kotlin Dev Day - @marcoGomier

    . └── kmm-project ├── androidApp ├── iosApp └── shared Same Repository
  8. Kotlin Dev Day - @marcoGomier shared androidApp Gradle Module Android

  9. Kotlin Dev Day - @marcoGomier // settings.gradle.kts include(":shared") // build.gradle.kts

    implementation(project(":shared")) Android
  10. Kotlin Dev Day - @marcoGomier shared androidApp iosApp Gradle Module

    Framework iOS
  11. Kotlin Dev Day - @marcoGomier New Projects: Android Studio plugin

  12. Kotlin Dev Day - @marcoGomier embedAndSignAppleFrameworkForXcode https://blog.jetbrains.com/kotlin/2021/07/multiplatform-gradle-plugin-improved-for-connecting-kmm-modules/ Regular Framework

  13. Kotlin Dev Day - @marcoGomier CocoaPods dependency manager 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
  14. Kotlin Dev Day - @marcoGomier iOS Project: Podfile pod 'shared',

    :path => ' .. /shared'
  15. Kotlin Dev Day @marcoGomier Existing Project

  16. Kotlin Dev Day - @marcoGomier Photo by Ashkan Forouzani on

    Unsplash 🙅 shared androidApp iosApp Gradle Module Framework Same Repository
  17. Kotlin Dev Day - @marcoGomier Photo by Erwan Hesry on

    Unsplash 💡 Create a library!
  18. Kotlin Dev 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?
  19. Kotlin Dev Day - @marcoGomier • DTOs • Common Models

    • Utility methods, aka `object Utils {}` • Analytics • . . . Where to start?
  20. Kotlin Dev Day - @marcoGomier Existing Projects: Intellij Wizard or

    Android Studio plugin
  21. Kotlin Dev Day - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework 
 Maven 
 Cocoa Repo
  22. Kotlin Dev Day - @marcoGomier Common Kotlin Android App iOs

    App .aar Framework 
 Maven 
 Cocoa Repo Android App Repository KMP Repository iOs App Repository
  23. Kotlin Dev Day @marcoGomier How to publish: Android

  24. Kotlin Dev 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
  25. Kotlin Dev Day - @marcoGomier Publish the artifacts ./gradlew publish

  26. Kotlin Dev Day - @marcoGomier Publish the artifacts ./gradlew publish

    ./gradlew publishToMavenLocal
  27. Kotlin Dev Day - @marcoGomier Android implementation(“com.prof18.hn.foundation:hn-foundation-android:2.0.0")

  28. Kotlin Dev Day @marcoGomier How to publish: iOS

  29. Kotlin Dev Day - @marcoGomier ❌ Regular Framework ❌ CocoaPods

    dependency manager
  30. Kotlin Dev Day - @marcoGomier XCFramework

  31. Kotlin Dev Day - @marcoGomier NEW https://developer.apple.com/videos/play/wwdc2019/416/ NEW iOS mac

    OS watch OS tvOS
  32. Kotlin Dev Day - @marcoGomier XCFramework Official Support from Kotlin

    1.5.30 https://kotlinlang.org/docs/whatsnew1530.html#support-for-xcframeworks
  33. 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() } }
  34. Kotlin Dev Day - @marcoGomier XCFramework prior to Kotlin 1.5.30

    https: / / www.marcogomiero.com/posts/2021/build-xcframework-kmp/
  35. Kotlin Dev 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) } } ... }
  36. Kotlin Dev 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) } } ... }
  37. Kotlin Dev 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) } } ... }
  38. Kotlin Dev 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) } } ... }
  39. Kotlin Dev Day - @marcoGomier assemble${libName}XCFramework assemble${libName}DebugXCFramework assemble${libName}ReleaseXCFramework XCFramework: Gradle

    tasks
  40. Kotlin Dev Day - @marcoGomier . ├── build ├── XCFrameworks

    ├── debug │ └── LibraryName.xcframework └── release └── LibraryName.xcframework XCFramework
  41. Kotlin Dev Day - @marcoGomier https://github.com/prof18/hn-foundation-cocoa-xcframework CocoaPod Repository https://guides.cocoapods.org/making/private-cocoapods.html

  42. Kotlin Dev 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'
  43. 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
  44. Kotlin Dev 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())
  45. Kotlin Dev 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")
  46. Kotlin Dev 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 } }
  47. 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") } .. ..
  48. Kotlin Dev 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) {
  49. Kotlin Dev 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 } } } }
  50. Kotlin Dev Day - @marcoGomier 🎉

  51. Kotlin Dev Day @marcoGomier Conclusions

  52. Kotlin Dev Day - @marcoGomier Faced [and resolved] difficulties •

    How to organise the project without a monorepo • How to effectively distribute the iOS framework
  53. Kotlin Dev Day - @marcoGomier Start little

  54. Kotlin Dev Day - @marcoGomier Start little Go bigger then

  55. Kotlin Dev Day - @marcoGomier Start little then go bigger

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

  56. Kotlin Dev Day - @marcoGomier https://github.com/prof18/shared-hn-android-ios-backend * based on a

    true story project
  57. 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: // developer.apple.com/videos/play/wwdc2019/416/ • https: // guides.cocoapods.org/making/private-cocoapods.html
  58. @marcoGomier Kotlin Dev Day Thank you! > Twitter: @marcoGomier 


    > Github: prof18 
 > Website: marcogomiero.com Marco Gomiero 👨💻 Senior Android Engineer @ TIER 
 Google Developer Expert for Kotlin