Slide 1

Slide 1 text

@marcoGomier Kotlin Dev Day Introducing Kotlin Multiplatform in an existing project Marco Gomiero πŸ‘¨πŸ’» Senior Android Engineer @ TIER 
 Google Developer Expert for Kotlin

Slide 2

Slide 2 text

Kotlin Dev Day - @marcoGomier Kotlin Multiplatform Share as much [NO UI] code as possible on different platforms

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

Kotlin Dev Day @marcoGomier New Project

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Kotlin Dev Day - @marcoGomier Kotlin Dev Day - @marcoGomier . └── kmm-project β”œβ”€β”€ androidApp β”œβ”€β”€ iosApp └── shared Same Repository

Slide 8

Slide 8 text

Kotlin Dev Day - @marcoGomier shared androidApp Gradle Module Android

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Kotlin Dev Day - @marcoGomier shared androidApp iosApp Gradle Module Framework iOS

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Kotlin Dev Day - @marcoGomier iOS Project: Podfile pod 'shared', :path => ' .. /shared'

Slide 15

Slide 15 text

Kotlin Dev Day @marcoGomier Existing Project

Slide 16

Slide 16 text

Kotlin Dev Day - @marcoGomier Photo by Ashkan Forouzani on Unsplash πŸ™… shared androidApp iosApp Gradle Module Framework Same Repository

Slide 17

Slide 17 text

Kotlin Dev Day - @marcoGomier Photo by Erwan Hesry on Unsplash πŸ’‘ Create a library!

Slide 18

Slide 18 text

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?

Slide 19

Slide 19 text

Kotlin Dev Day - @marcoGomier β€’ DTOs β€’ Common Models β€’ Utility methods, aka `object Utils {}` β€’ Analytics β€’ . . . Where to start?

Slide 20

Slide 20 text

Kotlin Dev Day - @marcoGomier Existing Projects: Intellij Wizard or Android Studio plugin

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Kotlin Dev Day @marcoGomier How to publish: Android

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

Kotlin Dev Day - @marcoGomier Android implementation(β€œcom.prof18.hn.foundation:hn-foundation-android:2.0.0")

Slide 28

Slide 28 text

Kotlin Dev Day @marcoGomier How to publish: iOS

Slide 29

Slide 29 text

Kotlin Dev Day - @marcoGomier ❌ Regular Framework ❌ CocoaPods dependency manager

Slide 30

Slide 30 text

Kotlin Dev Day - @marcoGomier XCFramework

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

Kotlin Dev Day - @marcoGomier XCFramework Official Support from Kotlin 1.5.30 https://kotlinlang.org/docs/whatsnew1530.html#support-for-xcframeworks

Slide 33

Slide 33 text

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().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() } }

Slide 34

Slide 34 text

Kotlin Dev Day - @marcoGomier XCFramework prior to Kotlin 1.5.30 https: / / www.marcogomiero.com/posts/2021/build-xcframework-kmp/

Slide 35

Slide 35 text

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) } } ... }

Slide 36

Slide 36 text

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) } } ... }

Slide 37

Slide 37 text

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) } } ... }

Slide 38

Slide 38 text

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) } } ... }

Slide 39

Slide 39 text

Kotlin Dev Day - @marcoGomier assemble${libName}XCFramework assemble${libName}DebugXCFramework assemble${libName}ReleaseXCFramework XCFramework: Gradle tasks

Slide 40

Slide 40 text

Kotlin Dev Day - @marcoGomier . β”œβ”€β”€ build β”œβ”€β”€ XCFrameworks β”œβ”€β”€ debug β”‚ └── LibraryName.xcframework └── release └── LibraryName.xcframework XCFramework

Slide 41

Slide 41 text

Kotlin Dev Day - @marcoGomier https://github.com/prof18/hn-foundation-cocoa-xcframework CocoaPod Repository https://guides.cocoapods.org/making/private-cocoapods.html

Slide 42

Slide 42 text

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'

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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())

Slide 45

Slide 45 text

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")

Slide 46

Slide 46 text

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 } }

Slide 47

Slide 47 text

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") } .. ..

Slide 48

Slide 48 text

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) {

Slide 49

Slide 49 text

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 } } } }

Slide 50

Slide 50 text

Kotlin Dev Day - @marcoGomier πŸŽ‰

Slide 51

Slide 51 text

Kotlin Dev Day @marcoGomier Conclusions

Slide 52

Slide 52 text

Kotlin Dev Day - @marcoGomier Faced [and resolved] difficulties β€’ How to organise the project without a monorepo β€’ How to effectively distribute the iOS framework

Slide 53

Slide 53 text

Kotlin Dev Day - @marcoGomier Start little

Slide 54

Slide 54 text

Kotlin Dev Day - @marcoGomier Start little Go bigger then

Slide 55

Slide 55 text

Kotlin Dev Day - @marcoGomier Start little then go bigger β€’ Validate the process with β€œlittle” effort β€’ Then you can go bigger and share more β€œfeatures” 


Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

@marcoGomier Kotlin Dev Day Thank you! > Twitter: @marcoGomier 
 > Github: prof18 
 > Website: marcogomiero.com Marco Gomiero πŸ‘¨πŸ’» Senior Android Engineer @ TIER 
 Google Developer Expert for Kotlin