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

Introducing Kotlin Multiplatform in an existing...

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.

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 Kotlin Dev Day - @marcoGomier

    . └── kmm-project ├── androidApp ├── iosApp └── shared Same Repository
  6. 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
  7. Kotlin Dev Day - @marcoGomier Photo by Ashkan Forouzani on

    Unsplash 🙅 shared androidApp iosApp Gradle Module Framework Same Repository
  8. 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?
  9. Kotlin Dev Day - @marcoGomier • DTOs • Common Models

    • Utility methods, aka `object Utils {}` • Analytics • . . . Where to start?
  10. Kotlin Dev Day - @marcoGomier Common Kotlin Android App iOs

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

    App .aar Framework 
 Maven 
 Cocoa Repo Android App Repository KMP Repository iOs App Repository
  12. 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
  13. Kotlin Dev Day - @marcoGomier XCFramework Official Support from Kotlin

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

    https: / / www.marcogomiero.com/posts/2021/build-xcframework-kmp/
  16. Kotlin Dev Day - @marcoGomier . ├── build ├── XCFrameworks

    ├── debug │ └── LibraryName.xcframework └── release └── LibraryName.xcframework XCFramework
  17. Kotlin Dev Day - @marcoGomier iOs Project: Podfile # For

    develop releases: pod 'HNFoundation', :git => “[email protected]:prof18/hn-foundation-cocoa-xcframework.git", :branch = > 'develop' # For stable releases pod 'HNFoundation', :git => “[email protected]:prof18/hn-foundation-cocoa-xcframework.git", :tag => ‘2.0.0'
  18. 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
  19. 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())
  20. 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")
  21. 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 } }
  22. 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") } .. ..
  23. 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) {
  24. 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 } } } }
  25. Kotlin Dev Day - @marcoGomier Faced [and resolved] difficulties •

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

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

  27. 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
  28. @marcoGomier Kotlin Dev Day Thank you! > Twitter: @marcoGomier 


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