Slide 1

Slide 1 text

Marco Gomiero Kotlin Budapest Meetup Introducing Kotlin Multiplatform in an existing project

Slide 2

Slide 2 text

Kotlin Budapest Meetup - @marcoGomier Marco Gomiero ๐Ÿ‘จ๐Ÿ’ป Android Engineer @ TIER ๐Ÿ›ด ๐Ÿ‡ฉ๐Ÿ‡ช ๐Ÿ‡ฎ๐Ÿ‡น โ€จ Google Developer Expert for Kotlin > Twitter: @marcoGomier โ€จ > Github: prof18 โ€จ > Website: marcogomiero.com

Slide 3

Slide 3 text

Kotlin Budapest Meetup - @marcoGomier Kotlin Multiplatform

Slide 4

Slide 4 text

Kotlin Budapest Meetup - @marcoGomier Kotlin Multiplatform โ€ข Not about compiling all code for all platforms โ€ข Share as much [NO UI] code as possible

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

Kotlin Budapest Meetup - @marcoGomier https://kotlinlang.org/lp/mobile/

Slide 8

Slide 8 text

Kotlin Budapest Meetup @marcoGomier New Project

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Kotlin Budapest Meetup - @marcoGomier Kotlin Budapest Meetup - @marcoGomier . โ””โ”€โ”€ kmm-project โ”œโ”€โ”€ androidApp โ”œโ”€โ”€ iosApp โ””โ”€โ”€ shared Same Repository

Slide 12

Slide 12 text

Kotlin Budapest Meetup - @marcoGomier shared androidApp Gradle Module Android

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

Kotlin Budapest Meetup - @marcoGomier shared androidApp iosApp Gradle Module Framework iOS

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

Kotlin Budapest Meetup - @marcoGomier

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Kotlin Budapest Meetup @marcoGomier Existing Project

Slide 21

Slide 21 text

Kotlin Budapest Meetup - @marcoGomier Photo by Ashkan Forouzani on Unsplash ๐Ÿ™… shared androidApp iosApp Gradle Module Framework Same Repository

Slide 22

Slide 22 text

Kotlin Budapest Meetup - @marcoGomier Photo by Erwan Hesry on Unsplash ๐Ÿ’ก Create a library!

Slide 23

Slide 23 text

Kotlin Budapest Meetup - @marcoGomier Photo by Erwan Hesry on Unsplash A little piece of tech stack to start with

Slide 24

Slide 24 text

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?

Slide 25

Slide 25 text

Kotlin Budapest Meetup - @marcoGomier โ€ข DTOs โ€ข Common Models โ€ข Utility methods, aka `object Utils {}` โ€ข Analytics โ€ข . . . Where to start?

Slide 26

Slide 26 text

Kotlin Budapest Meetup - @marcoGomier Existing Projects: Intellij Wizard

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Kotlin Budapest Meetup - @marcoGomier Common Kotlin Android App iOs App .aar Framework โ€จ Maven โ€จ Cocoa Repo Android App Repository KMP Repository iOs App Repository

Slide 29

Slide 29 text

Kotlin Budapest Meetup @marcoGomier How to publish: Android

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

Kotlin Budapest Meetup - @marcoGomier Android implementation(โ€œcom.prof18.hn.foundation:hn-foundation-android:2.0.0")

Slide 35

Slide 35 text

Kotlin Budapest Meetup @marcoGomier How to publish: iOS

Slide 36

Slide 36 text

Kotlin Budapest Meetup - @marcoGomier ๐Ÿฅต

Slide 37

Slide 37 text

Kotlin Budapest Meetup - @marcoGomier embedAndSignAppleFrameworkForXcode packForXcode CocoaPods integration

Slide 38

Slide 38 text

Kotlin Budapest Meetup - @marcoGomier ๐Ÿค”

Slide 39

Slide 39 text

Kotlin Budapest Meetup - @marcoGomier XCFramework

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

Kotlin Budapest Meetup - @marcoGomier

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 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 46

Slide 46 text

Kotlin Budapest Meetup - @marcoGomier XCFramework prior to Kotlin 1.5.30

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

Kotlin Budapest Meetup - @marcoGomier assemble${libName}XCFramework assemble${libName}DebugXCFramework assemble${libName}ReleaseXCFramework XCFramework: Gradle tasks

Slide 53

Slide 53 text

Kotlin Budapest Meetup - @marcoGomier . โ”œโ”€โ”€ build โ”œโ”€โ”€ XCFrameworks โ”œโ”€โ”€ debug โ”‚ โ””โ”€โ”€ LibraryName.xcframework โ””โ”€โ”€ release โ””โ”€โ”€ LibraryName.xcframework XCFramework

Slide 54

Slide 54 text

Kotlin Budapest Meetup - @marcoGomier https://github.com/prof18/hn-foundation-cocoa-xcframework CocoaPod Repository

Slide 55

Slide 55 text

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" = > "[email protected]" } s.vendored_frameworks = 'HNFoundation.xcframework' s.source = { :git => "[email protected]:prof18/hn-foundation-cocoa-xcframework", :tag => " #{ s.version}" } s.exclude_files = "Classes/Exclude" end

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

https: // guides.cocoapods.org/making/private-cocoapods.html

Slide 58

Slide 58 text

Kotlin Budapest Meetup - @marcoGomier iOs Project: Podfile # For develop releases: pod 'HNFoundation', :git => โ€œ[email protected]:prof18/hn-foundation-cocoa-xcframework.git", :branch = > 'develop'

Slide 59

Slide 59 text

Kotlin Budapest Meetup - @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 60

Slide 60 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 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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

Slide 64

Slide 64 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 65

Slide 65 text

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

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

Kotlin Budapest Meetup - @marcoGomier ๐ŸŽ‰

Slide 68

Slide 68 text

Kotlin Budapest Meetup @marcoGomier Concurrency

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

Uncaught Kotlin exception: kotlin.native.IncorrectDereferenceException: illegal attempt to access non-shared @c2ddb8 from other thread

Slide 71

Slide 71 text

Kotlin Budapest Meetup - @marcoGomier ๐Ÿฅถ Freeze the object to share it between threads

Slide 72

Slide 72 text

Kotlin Budapest Meetup - @marcoGomier override fun deserialize(jsonString: String): NewsDTO { val newsDTO: NewsDTO = json.decodeFromString(jsonString) newsDTO.freeze() return newsDTO }

Slide 73

Slide 73 text

Kotlin Budapest Meetup - @marcoGomier https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/

Slide 74

Slide 74 text

Kotlin Budapest Meetup @marcoGomier Conclusions

Slide 75

Slide 75 text

Kotlin Budapest Meetup - @marcoGomier Faced [and resolved] difficulties โ€ข How to organise the project without a monorepo โ€ข How to effectively distribute the iOS framework

Slide 76

Slide 76 text

Kotlin Budapest Meetup - @marcoGomier Start little

Slide 77

Slide 77 text

Kotlin Budapest Meetup - @marcoGomier Start little Go bigger then

Slide 78

Slide 78 text

Kotlin Budapest Meetup - @marcoGomier Start little then go bigger โ€ข Validate the process with โ€œlittleโ€ effort โ€ข Then you can go bigger and share more โ€œfeaturesโ€ โ€จ

Slide 79

Slide 79 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: / / 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/

Slide 80

Slide 80 text

Marco Gomiero Kotlin Budapest Meetup Thank you! > Twitter: @marcoGomier โ€จ > Github: prof18 โ€จ > Website: marcogomiero.com