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

And that, folks, is how we shared code between Android, iOS and the Backend - droidcon Italy

And that, folks, is how we shared code between Android, iOS and the Backend - droidcon Italy

Kotlin Multiplatform is an alpha feature that you can use to share code between different platforms. Even if it is in alpha stage, it is already possible to start using it in production applications.

In this talk, I will share the discussion that led us to Kotlin Multiplatform, and the following processes we put in place to start using it in production for an Android, iOS, and backend project. I will show you what parts of the code you can (gradually) start to share and how to integrate with existing standalone projects.

9da5d5cc4b6a9f28058152e28364b02a?s=128

Marco Gomiero

November 11, 2021
Tweet

More Decks by Marco Gomiero

Other Decks in Programming

Transcript

  1. droidcon Italy Marco Gomiero And that, folks, is how we

    shared code between Android, iOS and the Backend
  2. droidcon Italy - @marcoGomier Marco Gomiero 👨💻 Android Engineer @

    TIER 🛴 🇩🇪 🇮🇹 
 Google Developer Expert for Kotlin 
 🍻 Co-Lead @ GDG Venezia > Twitter: @marcoGomier 
 > Github: prof18 
 > Website: marcogomiero.com
  3. droidcon Italy @marcoGomier Story #1

  4. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer

  5. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer data class

    NewsDTO( val id: Long, val author: String )
  6. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer data class

    NewsDTO( val id: Long, val author: String ) data class NewsDTO( val id: Long, val author: String? ) OR
  7. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer Bob Backend

    Developer 👨💻
  8. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 👨💻 Bob

    Backend Developer Is author nullable? Mmm, the doc says its not!
  9. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 👨💻 Bob

    Backend Developer Well, its not working as expected Let me check
  10. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 👨💻 Bob

    Backend Developer ...
  11. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 👨💻 Bob

    Backend Developer Oh, you’re right! Sorry! I’ve changed the value last day!
  12. droidcon Italy @marcoGomier Story #2

  13. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 🧑💻 Charlie

    Product Manager
  14. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 🧑💻 Charlie

    Product Manager Can you now implement this logic on iOs as well? Sure!
  15. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 🧑💻 Charlie

    Product Manager ...
  16. droidcon Italy - @marcoGomier 👩💻 Alice Mobile Developer 🧑💻 Charlie

    Product Manager I’m just converting Kotlin into Swift!
  17. droidcon Italy - @marcoGomier 🤔

  18. droidcon Italy - @marcoGomier 🚀

  19. droidcon Italy - @marcoGomier 🚀 Execution speed

  20. droidcon Italy @marcoGomier How to tackle

  21. droidcon Italy - @marcoGomier How to tackle • Cross Platform

    solutions (React Native, Flutter, .. )
  22. droidcon Italy - @marcoGomier How to tackle • Cross Platform

    solutions (React Native, Flutter, .. )
  23. droidcon Italy - @marcoGomier Cross Platform solutions (React Native, Flutter,

    .. ) Unifying UI declaration between platform is complicated
  24. droidcon Italy - @marcoGomier Cross Platform solutions (React Native, Flutter,

    .. ) Unifying UI declaration between platform is complicated Different platforms have different patterns
  25. droidcon Italy - @marcoGomier How to tackle • Cross Platform

    solutions (React Native, Flutter, .. ) • Kotlin Multiplatform
  26. droidcon Italy - @marcoGomier Not about compiling all code for

    all platforms Kotlin Multiplatform
  27. droidcon Italy - @marcoGomier Not about compiling all code for

    all platforms Kotlin Multiplatform Share as much [NO UI] code as possible https://kotlinlang.org/docs/reference/multiplatform.html
  28. droidcon Italy @marcoGomier How to approach

  29. droidcon Italy - @marcoGomier In most of the cases you

    start from an existing project Photo by Jonas Jacobsson on Unsplash
  30. droidcon Italy - @marcoGomier You don’t have enough time for

    a big rewrite Photo by Ashkan Forouzani on Unsplash
  31. droidcon Italy - @marcoGomier droidcon Italy - @marcoGomier . └──

    kmm-project ├── androidApp ├── iosApp └── shared Photo by Ashkan Forouzani on Unsplash 🙅
  32. droidcon Italy - @marcoGomier Photo by Erwan Hesry on Unsplash

    💡 Create a library!
  33. droidcon Italy - @marcoGomier Photo by Erwan Hesry on Unsplash

    You can choose a little piece of tech stack to start with
  34. droidcon Italy - @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?
  35. droidcon Italy - @marcoGomier • DTOs • Common Models •

    Utility methods, aka `object Utils {}` • Analytics • . . . Where to start?
  36. droidcon Italy - @marcoGomier Where to start? • DTOs •

    Common Models • Utility methods, aka `object Utils {}` • Analytics • . . .
  37. droidcon Italy @marcoGomier Let’s start building

  38. droidcon Italy - @marcoGomier Common Kotlin .aar Framework Maven Repo

    .jar iOs App iOs App Repository Android App Android App Repository Backend Backend Repository KMP Repository Cocoa Repo Architecture
  39. droidcon Italy - @marcoGomier Let’s start building https://github.com/prof18/shared-hn-android-ios-backend * based

    on a true story project
  40. droidcon Italy - @marcoGomier • Create a new project Let’s

    start building
  41. droidcon Italy - @marcoGomier Create a new project

  42. droidcon Italy - @marcoGomier • Create a new project •

    Add the JVM target [if needed] Let’s start building
  43. droidcon Italy - @marcoGomier Add the JVM target [if needed]

  44. droidcon Italy - @marcoGomier Let’s start building • Create a

    new project • Add the JVM target [if needed] • Setup a Maven repository to share the artifacts
  45. droidcon Italy - @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
  46. droidcon Italy - @marcoGomier • Create a new project •

    Add the JVM target [if needed] • Setup a Maven repository to share the artifacts • Write some code! Let’s start building
  47. droidcon Italy - @marcoGomier NewsDTO.kt data class NewsDTO( val author:

    String, val id: Long, val score: Int, val timestamp: Long, val title: String, val type: String, val url: String )
  48. droidcon Italy - @marcoGomier Publish the artifacts ./gradlew publish

  49. droidcon Italy - @marcoGomier Publish the artifacts ./gradlew publish ./gradlew

    publishToMavenLocal
  50. droidcon Italy - @marcoGomier • Create a new project •

    Add the JVM target [if needed] • Setup a Maven repository to share the artifacts • Write some code! Let’s start building • Add the new library to the backend and the clients
  51. droidcon Italy - @marcoGomier Android implementation(“com.prof18.hn.foundation:hn-foundation-android:2.0.0")

  52. droidcon Italy - @marcoGomier Android interface HNApiService { @GET("hn/topStories") suspend

    fun getTopStories(): NewsListDTO }
  53. droidcon Italy - @marcoGomier Backend implementation(“com.prof18.hn.foundation:hn-foundation-jvm:2.0.0")

  54. droidcon Italy - @marcoGomier routing { get("/hn/topStories") { call.respond(getTopStories()) }

    } fun getTopStories(): NewsListDTO { ... } Backend
  55. droidcon Italy - @marcoGomier 🎉

  56. droidcon Italy - @marcoGomier  And iOS?

  57. droidcon Italy - @marcoGomier 🥵

  58. droidcon Italy - @marcoGomier Framework

  59. droidcon Italy - @marcoGomier packForXcode Auto generated when creating a

    Kotlin Multiplatform mobile app
  60. droidcon Italy - @marcoGomier embedAndSignAppleFrameworkForXcode https://blog.jetbrains.com/kotlin/2021/07/multiplatform-gradle-plugin-improved-for-connecting-kmm-modules/ packForXcode From Kotlin 1.5.20

  61. droidcon Italy - @marcoGomier packForXcode Auto generated when creating a

    Kotlin Multiplatform mobile app val packForXcode by tasks.creating(Sync :: class) { group = "build" val mode = System.getenv("CONFIGURATION") ?: "DEBUG" val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator" val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64" val framework = kotlin.targets.getByName<KotlinNativeTarget>(targetName).binaries.getFramework(mode) inputs.property("mode", mode) dependsOn(framework.linkTask) val targetDir = File(buildDir, "xcode-frameworks") from({ framework.outputDirectory }) into(targetDir) } tasks.getByName("build").dependsOn(packForXcode)
  62. droidcon Italy - @marcoGomier CocoaPods plugin plugins { kotlin("multiplatform") version

    "1.4.10" kotlin("native.cocoapods") version "1.4.10" } // CocoaPods requires the podspec to have a version. version = "1.0" kotlin { cocoapods { // Configure fields required by CocoaPods. summary = "Some description for a Kotlin/Native module" homepage = "Link to a Kotlin/Native module homepage" // You can change the name of the produced framework. // By default, it is the name of the Gradle project. frameworkName = "my_framework" } } https://kotlinlang.org/docs/reference/native/cocoapods.html
  63. droidcon Italy - @marcoGomier CocoaPods plugin: shared.podspec https://kotlinlang.org/docs/reference/native/cocoapods.html 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 } ]
  64. droidcon Italy - @marcoGomier CocoaPods plugin: shared.podspec https://kotlinlang.org/docs/reference/native/cocoapods.html 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 } ]
  65. droidcon Italy - @marcoGomier 🤔

  66. droidcon Italy - @marcoGomier XCFramework

  67. droidcon Italy - @marcoGomier NEW https://devstreaming-cdn.apple.com/videos/wwdc/2019/416h8485aty341c2/416/416_binary_frameworks_in_swift.pdf

  68. droidcon Italy - @marcoGomier https://devstreaming-cdn.apple.com/videos/wwdc/2019/416h8485aty341c2/416/416_binary_frameworks_in_swift.pdf NEW iOS mac OS watch

    OS tvOS
  69. droidcon Italy - @marcoGomier

  70. droidcon Italy - @marcoGomier https: // developer.apple.com/videos/play/wwdc2019/416/

  71. droidcon Italy - @marcoGomier XCFramework Official Support from Kotlin 1.5.30

    https://kotlinlang.org/docs/whatsnew1530.html#support-for-xcframeworks
  72. 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() } }
  73. droidcon Italy - @marcoGomier XCFramework prior to Kotlin 1.5.30

  74. droidcon Italy - @marcoGomier XCFramework prior to Kotlin 1.5.30 https:

    / / www.marcogomiero.com/posts/2021/build-xcframework-kmp/
  75. droidcon Italy - @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) } } ... }
  76. droidcon Italy - @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) } } ... }
  77. droidcon Italy - @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) } } ... }
  78. droidcon Italy - @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) } } ... }
  79. droidcon Italy - @marcoGomier assemble${libName}XCFramework assemble${libName}DebugXCFramework assemble${libName}ReleaseXCFramework XCFramework: Gradle tasks

  80. droidcon Italy - @marcoGomier . ├── build ├── XCFrameworks ├──

    debug │ └── LibraryName.xcframework └── release └── LibraryName.xcframework XCFramework
  81. droidcon Italy - @marcoGomier https://github.com/prof18/hn-foundation-cocoa-xcframework CocoaPod Repository

  82. droidcon Italy - @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
  83. None
  84. https: // guides.cocoapods.org/making/private-cocoapods.html

  85. droidcon Italy - @marcoGomier iOs Project: Podfile # For develop

    releases: pod 'HNFoundation', :git => “git@github.com:prof18/hn-foundation-cocoa-xcframework.git", :branch = > 'develop'
  86. droidcon Italy - @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'
  87. https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/build.gradle.kts#L110 Publish Dev Framework Task 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") } .. ..
  88. droidcon Italy - @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())
  89. droidcon Italy - @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")
  90. droidcon Italy - @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 } }
  91. 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") } .. ..
  92. droidcon Italy - @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) {
  93. droidcon Italy - @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 } } } }
  94. droidcon Italy - @marcoGomier 👩💻👨💻

  95. droidcon Italy - @marcoGomier 😩

  96. droidcon Italy - @marcoGomier How to handle deserialization? 😩

  97. droidcon Italy - @marcoGomier How to handle deserialization? • Probably

    you are using Alamofire for net handling
  98. droidcon Italy - @marcoGomier How to handle deserialization? • Probably

    you are using Alamofire for net handling • [De]Serialization probable handled with Codable 
 (or other solutions)
  99. droidcon Italy - @marcoGomier How to handle deserialization? • Probably

    you are using Alamofire for net handling • [De]Serialization probable handled with Codable 
 (or other solutions) • Need to comply to the Decodable protocol • Not yet available on Kotlin/Native: KT-48081
  100. droidcon Italy - @marcoGomier How to handle deserialization? • Need

    to comply to the Decodable protocol • Use kotlinx.serialization • Write a custom serializer for Alamofire
  101. droidcon Italy - @marcoGomier import Foundation import Alamofire import HNFoundation

    struct CustomSerializer<T: BaseDTO> : ResponseSerializer { func serialize(request: URLRequest?, response: HTTPURLResponse?, data: Data?, error: Error?) throws -> T { guard error == nil else { throw error! } guard let data = data, !data.isEmpty else { guard emptyResponseAllowed(forRequest: request, response: response) else { throw AFError.responseSerializationFailed(reason: .inputDataNilOrZeroLength) } guard let emptyResponseType = T.self as? EmptyResponse.Type, let emptyValue = emptyResponseType.emptyValue() as? T else { throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)")) } return emptyValue CustomSerializer.swift https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-ios-client/HN%20Client/HN%20Client/data/CustomSerializer.swift
  102. droidcon Italy - @marcoGomier https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-ios-client/HN%20Client/HN%20Client/data/CustomSerializer.swift throw AFError.responseSerializationFailed(reason: .invalidEmptyResponse(type: "\(T.self)")) }

    return emptyValue } do { let jsonString = try StringResponseSerializer().serialize(request: request, response: response, data: data, error: error) let deserializedObject = try T().deserialize(jsonString: jsonString) as! T deserializedObject.makeFrozen() return deserializedObject } catch { throw AFError.responseSerializationFailed(reason: .decodingFailed(error: error)) } } }
  103. droidcon Italy - @marcoGomier BaseDTO.kt https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/src/commonMain/kotlin/com/prof18/hn/dto/BaseDTO.kt abstract class BaseDTO {

    @Throws(Exception :: class) abstract fun deserialize(jsonString: String): BaseDTO protected val json = Json { ignoreUnknownKeys = true } fun makeFrozen() { freeze() } }
  104. droidcon Italy - @marcoGomier NewsDTO.kt https://github.com/prof18/shared-hn-android-ios-backend/blob/main/hn-foundation/src/commonMain/kotlin/com/prof18/hn/dto/NewsDTO.kt @Serializable data class NewsDTO(

    val author: String, val id: Long, val score: Int, val timestamp: Long, val title: String, val type: String, val url: String ) : BaseDTO() { override fun deserialize(jsonString: String): NewsDTO { val newsDTO: NewsDTO = json.decodeFromString(jsonString) newsDTO.makeFrozen() return newsDTO } }
  105. droidcon Italy - @marcoGomier How to handle deserialization?

  106. droidcon Italy - @marcoGomier How to handle deserialization? https: /

    / www.marcogomiero.com/posts/2020/kotlin-serialization-alamofire/
  107. droidcon Italy - @marcoGomier 🎉

  108. droidcon Italy @marcoGomier Conclusions

  109. droidcon Italy - @marcoGomier Faced [and resolved] difficulties • How

    to organise the project without a monorepo • Gradle • How to effectively distribute the iOs framework • How to handle deserialization on iOs
  110. droidcon Italy - @marcoGomier Start little

  111. droidcon Italy - @marcoGomier Start little Go bigger then

  112. droidcon Italy - @marcoGomier Start little then go bigger •

    We have validated the process with “little” effort • Now we can go bigger and share more “features” 
 
 For example the data layer → write SQL once for all
  113. 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/
  114. droidcon Italy Marco Gomiero Thank you! > Twitter: @marcoGomier 


    > Github: prof18 
 > Website: marcogomiero.com