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.

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  5. Kotlin Dev Day @marcoGomier
    New Project

    View Slide

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

    View Slide

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


    └── kmm-project


    ├── androidApp


    ├── iosApp


    └── shared
    Same Repository

    View Slide

  8. Kotlin Dev Day - @marcoGomier
    shared
    androidApp
    Gradle Module
    Android

    View Slide

  9. Kotlin Dev Day - @marcoGomier
    //
    settings.gradle.kts


    include(":shared")


    //
    build.gradle.kts


    implementation(project(":shared"))
    Android

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

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

    View Slide

  15. Kotlin Dev Day @marcoGomier
    Existing Project

    View Slide

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


    🙅
    shared
    androidApp iosApp
    Gradle
    Module
    Framework
    Same Repository

    View Slide

  17. Kotlin Dev Day - @marcoGomier
    Photo by Erwan Hesry on Unsplash
    💡 Create a library!

    View Slide

  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?

    View Slide

  19. Kotlin Dev Day - @marcoGomier
    • DTOs


    • Common Models


    • Utility methods, aka `object Utils {}`


    • Analytics



    . . .
    Where to start?

    View Slide

  20. Kotlin Dev Day - @marcoGomier
    Existing Projects:


    Intellij Wizard or Android Studio plugin

    View Slide

  21. Kotlin Dev Day - @marcoGomier
    Common Kotlin
    Android App iOs App
    .aar Framework

    Maven

    Cocoa Repo

    View Slide

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

    Maven

    Cocoa Repo
    Android App Repository
    KMP Repository
    iOs App Repository

    View Slide

  23. Kotlin Dev Day @marcoGomier
    How to publish: Android

    View Slide

  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

    View Slide

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


    View Slide

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


    ./gradlew publishToMavenLocal


    View Slide

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


    View Slide

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

    View Slide

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

    View Slide

  30. Kotlin Dev Day - @marcoGomier
    XCFramework

    View Slide

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

    View Slide

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

    View Slide

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


    }


    }

    View Slide

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

    View Slide

  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)


    }


    }


    ...

    }

    View Slide

  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)


    }


    }


    ...

    }

    View Slide

  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)


    }


    }


    ...

    }

    View Slide

  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)


    }


    }


    ...

    }

    View Slide

  39. Kotlin Dev Day - @marcoGomier
    assemble${libName}XCFramework


    assemble${libName}DebugXCFramework


    assemble${libName}ReleaseXCFramework
    XCFramework: Gradle tasks

    View Slide

  40. Kotlin Dev Day - @marcoGomier
    .


    ├── build


    ├── XCFrameworks


    ├── debug


    │ └── LibraryName.xcframework


    └── release


    └── LibraryName.xcframework


    XCFramework

    View Slide

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

    View Slide

  42. 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'


    View Slide

  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

    View Slide

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


    View Slide

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


    View Slide

  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


    }


    }




    View Slide

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


    }

    .. ..

    View Slide

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



    View Slide

  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


    }


    }


    }


    }


    View Slide

  50. Kotlin Dev Day - @marcoGomier
    🎉

    View Slide

  51. Kotlin Dev Day @marcoGomier
    Conclusions

    View Slide

  52. Kotlin Dev Day - @marcoGomier
    Faced [and resolved] difficulties
    • How to organise the project without a monorepo


    • How to effectively distribute the iOS framework

    View Slide

  53. Kotlin Dev Day - @marcoGomier
    Start little

    View Slide

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

    View Slide

  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”

    View Slide

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

    View Slide

  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


    View Slide

  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

    View Slide