$30 off During Our Annual Pro Sale. View Details »

Adopting Kotlin Multiplatform in Brownfield Applications - DCIT22

Nate Ebel
October 07, 2022

Adopting Kotlin Multiplatform in Brownfield Applications - DCIT22

Presented at Droidcon Italy 2022

How can your team start sharing code with Kotlin Multiplatform? This is a simple question for a complex, and nuanced challenge.

Over the past year, our team has invested into Kotlin Multiplatform for both our iOS and Android applications. In this talk, we'll discuss our lessons learned and highlight how to solve key challenges including:
- Getting organizational buy in
- Configuring a multi-artifact build
- Building a CI/CD pipeline
- Publishing .jar, .aar, and Swift Packages
- Integration into brownfield applications
- Optimizing developer experience

By the end of this talk, you should have a clear technical and product roadmap for integrating Kotlin Multiplatform into your apps.

Nate Ebel

October 07, 2022
Tweet

More Decks by Nate Ebel

Other Decks in Programming

Transcript

  1. Nate Ebel
    @n8ebel
    Adopting Kotlin Multiplatform
    In Brown
    fi
    eld Applications

    View Slide

  2. How can your team start sharing
    code using Kotlin Multiplatform?

    View Slide

  3. Where are we going?
    • How we got organizational buy in

    • How we solved key technical integration challenges

    • How we are using Kotlin Multiplatform today; and lessons learned

    View Slide

  4. Kotlin Multiplatform
    Integration Roadmap

    View Slide

  5. Kotlin Multiplatform at Premise
    Blog series
    • Kotlin Multiplatform at Premise

    • Kotlin Multiplatform Project Structure for
    Integration with Brown
    fi
    eld Applications

    • Building a CI Pipeline for Kotlin Multiplatform
    Mobile Using GitHub Actions

    View Slide

  6. Organizational Buy In

    View Slide

  7. About our team
    • 8 Android developers

    • 7 8 iOS developers

    • Android-
    fi
    rst feature development

    • Localized in 40 languages

    • Monthly active users in the hundreds of thousands

    View Slide

  8. “Let the problem define the solution”
    Tip #1

    View Slide

  9. Our Problem
    “Analytics is a mess”

    View Slide

  10. Rationalizing analytics at Premise
    • Enumerate standard types of analytics events

    • De
    fi
    ne rule patterns for each event type

    • Create shared documentation for Mobile team

    View Slide

  11. Rationalizing analytics at Premise
    Can we codify this?

    View Slide

  12. shared code for shared thinking

    View Slide

  13. Perfect use case
    How to pitch KMP to the
    team?

    View Slide

  14. iOS team suggested KMP
    fi
    rst

    View Slide

  15. “Find an iOS team that wants to use


    Kotlin Multiplatform”
    Fake Tip #1

    View Slide

  16. “Evaluate both technical, and


    human factors”
    Tip #2

    View Slide

  17. Why Kotlin Multiplatform?
    • Clear set of business rules to codify

    • Want to share that logic across iOS, Android (maybe backend)

    • Don’t need/want shared UI

    • Have engineers well versed in Kotlin

    • Have engineers comfortable with non-trivial Gradle builds

    • Want low-risk, medium/low-e
    ff
    ort integration into existing apps

    • Have engineers willing to prototype, learn, and educate

    View Slide

  18. What next?
    How do we validate this
    KMP solution for shared
    analytics?

    View Slide

  19. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  20. View Slide

  21. Create the project Answer technical questions Ship…something

    View Slide

  22. What to ship first?

    View Slide

  23. “Start small”
    Tip #3

    View Slide

  24. Validating KMP in production
    • Ship a single Kotlin Multiplatform String constant in production

    • Ensure no issues with development, CI/CD, or runtime in the wild

    View Slide

  25. View Slide

  26. Building Our
    Kotlin Multiplatform Solution

    View Slide

  27. “Think like library developers”
    Tip #4

    View Slide

  28. View Slide

  29. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  30. Where will this new code live?

    View Slide

  31. Where will this new code live?
    Greenfield KMP Project
    iOS
    Shared
    Android
    All projects live side by side in a
    single mono repo

    View Slide

  32. Where will this new code live?
    Brownfield iOS Project
    iOS
    Brownfield Android Project
    Android
    Greenfield KMP Project
    Shared
    Existing projects are untouched
    Shared code exists in a new, separate repo

    View Slide

  33. Where will this new code live?
    Brownfield iOS Project
    iOS
    Brownfield Android Project
    Android
    Greenfield KMP Project
    Shared
    Existing projects are untouched
    Shared code exists in a new, separate repo

    View Slide

  34. Greenfield KMP Project
    Shared
    How to structure this
    new KMP project?

    View Slide

  35. Greenfield KMP Project
    Shared
    How to structure this new KMP project?

    View Slide

  36. Greenfield KMP Project
    Shared
    How to structure this new KMP project?

    View Slide

  37. Greenfield KMP Project
    Shared
    How to structure this new KMP project?
    iosMain
    androidMain
    commonMain
    Are these the right
    targets for our use case?

    View Slide

  38. Kotlin Multiplatform Targets
    iOS Javascript
    Linux JVM
    Android
    Mac

    View Slide

  39. Greenfield KMP Project
    Shared
    Are these the right targets for our use case?
    iosMain
    androidMain
    commonMain

    View Slide

  40. Greenfield KMP Project
    Shared
    Are these the right targets for our use case?
    iosMain
    commonMain
    jvmMain
    Chose JVM over Android to
    support integration with JVM-
    based backend services

    View Slide

  41. Configuring Build Targets
    //
    analytics/build.gradle.kts

    plugins { kotlin(“multiplatform”) }

    View Slide

  42. Configuring Build Targets
    //
    analytics/build.gradle.kts

    plugins { kotlin(“multiplatform”) }

    kotlin {

    jvm()

    iosX64()

    iosArm64()

    }

    View Slide

  43. Configuring Build Targets
    kotlin {

    jvm()

    iosX64()

    iosArm64()

    }

    View Slide

  44. Configuring Build Targets
    kotlin {

    jvm()

    iosX64()

    iosArm64()

    }

    View Slide

  45. Configuring Build Targets
    kotlin {

    jvm()

    iosX64()

    iosArm64()

    sourceSets {

    val commonMain by getting

    val jvmMain by getting

    val iosX64Main by getting

    val iosArm64Main by getting



    }

    }

    View Slide

  46. Configuring Build Targets
    kotlin {

    jvm()

    iosX64()

    iosArm64()

    sourceSets {

    val commonMain by getting

    val jvmMain by getting

    val iosX64Main by getting

    val iosArm64Main by getting

    val iosMain by creating {

    dependsOn(commonMain)

    iosX64Main.dependsOn(this)

    iosArm64Main.dependsOn(this)

    }

    }

    }

    View Slide

  47. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  48. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  49. Greenfield KMP Project
    Shared
    What if we want multiple multiplatform artifacts?
    iosMain
    commonMain
    jvmMain
    Imagine the N+1 solution of
    additional multiplatform features

    View Slide

  50. Greenfield KMP Project
    What if we want multiple multiplatform artifacts?
    iosMain
    commonMain
    jvmMain
    Imagine the N+1 solution of
    additional multiplatform features

    View Slide

  51. Greenfield KMP Project
    Analytics
    What if we want multiple multiplatform artifacts?
    iosMain
    commonMain
    jvmMain
    Imagine the N+1 solution of
    additional multiplatform features

    View Slide

  52. Greenfield KMP Project
    Analytics
    What if we want multiple multiplatform artifacts?
    Imagine the N+1 solution of
    additional multiplatform features

    View Slide

  53. Greenfield KMP Project
    Analytics
    What if we want multiple multiplatform artifacts?
    DTOs
    Networking
    Recommendations

    View Slide

  54. Greenfield KMP Project
    Analytics
    What if we want multiple multiplatform artifacts?
    DTOs
    Networking
    Recommendations
    Android

    View Slide

  55. Greenfield KMP Project
    Analytics
    What if we want multiple multiplatform artifacts?
    DTOs
    Networking
    Recommendations
    iOS

    View Slide

  56. Greenfield KMP Project
    Analytics
    What if we want multiple multiplatform artifacts?
    DTOs
    Networking
    Recommendations
    iOS

    View Slide

  57. Greenfield KMP Project
    Analytics
    What if we want multiple multiplatform artifacts?
    DTOs
    Networking
    Recommendations
    iOS
    Shared

    View Slide

  58. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    //
    shared/build.gradle.kts

    plugins { kotlin(“multiplatform”) }

    View Slide

  59. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    //
    shared/build.gradle.kts

    plugins { kotlin(“multiplatform”) }

    kotlin {

    val xcf = XCFramework(rootProject.name)

    }

    View Slide

  60. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    //
    shared/build.gradle.kts

    plugins { kotlin(“multiplatform”) }

    kotlin {

    val xcf = XCFramework(rootProject.name)

    listOf(iosX64(), iosArm64(), iosSimulatorArm64())

    .forEach { target
    ->




    }

    }

    View Slide

  61. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    //
    shared/build.gradle.kts

    plugins { kotlin(“multiplatform”) }

    kotlin {

    val xcf = XCFramework(rootProject.name)

    listOf(iosX64(), iosArm64(), iosSimulatorArm64())

    .forEach { target
    ->


    target.binaries.framework {

    xcf.add(this)

    export(project(“:analytics”))

    export(project(“:network”))

    transitiveExport = true

    }



    }

    }

    View Slide

  62. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    kotlin {

    val xcf = XCFramework(rootProject.name)

    listOf(iosX64(), iosArm64(), iosSimulatorArm64())

    .forEach { target
    ->


    target.binaries.framework {

    xcf.add(this)

    export(project(“:analytics”))

    export(project(“:network”))

    transitiveExport = true

    }



    }

    }

    View Slide

  63. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    kotlin {

    val xcf = XCFramework(rootProject.name)

    listOf(iosX64(), iosArm64(), iosSimulatorArm64())

    .forEach { target
    ->


    target.binaries.framework {

    xcf.add(this)

    export(project(“:analytics”))

    export(project(“:network”))

    transitiveExport = true

    }



    }

    }

    View Slide

  64. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    kotlin {

    . . .

    }

    View Slide

  65. Configuring a Composite Module
    Combine separate modules into 1 for iOS consumption
    kotlin {

    . . .

    sourceSets {

    . . .

    iosMain by creating {

    dependencies {

    api(project(“:analytics”))

    api(project(“:network”))

    }

    }

    }

    }

    View Slide

  66. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  67. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  68. Building Output Artifacts
    • Android / JVM

    • .jar / .aar

    • maven-publish plugin

    • iOS

    • XCFramework

    • Cocoapods

    • Swift Package Manager

    View Slide

  69. Building JVM Output Artifacts
    / /
    analytics/build.gradle.kts

    plugins {

    kotlin(“multiplatform”)

    id(“maven-publish”)

    }

    View Slide

  70. Building Android Output Artifacts
    / /
    analytics/build.gradle.kts

    plugins {

    kotlin(“multiplatform”)

    id(“maven-publish”)

    id(“com.android.library”)

    }

    View Slide

  71. Building Android Output Artifacts
    / /
    analytics/build.gradle.kts

    plugins {

    kotlin(“multiplatform”)

    id(“maven-publish”)

    id(“com.android.library”)

    }

    View Slide

  72. Building Android Output Artifacts
    //
    analytics/build.gradle.kts

    plugins {



    }

    kotlin {

    android {

    publishAllLibraryVariants()

    }

    }

    View Slide

  73. Building JVM/Android Output Artifacts
    $
    > >
    ./gradlew build publishToMavenLocal

    View Slide

  74. Building iOS Swift Package
    / /
    shared/build.gradle.kts

    plugins {

    kotlin(“multiplatform”)

    id(“com.chromaticnoise.multiplatform-swiftpackage”)

    }

    View Slide

  75. Building iOS Swift Package
    //
    shared/build.gradle.kts

    plugins {

    . . .

    }

    View Slide

  76. Building iOS Swift Package
    //
    shared/build.gradle.kts

    plugins {

    . . .

    }

    multiplatformSwiftPackage {

    swiftToolsVersion(“5.3”)

    targetPlatforms { iOS { v(“13”) } }

    packageName(rootProject.name)

    distributionMode { local() }

    }

    View Slide

  77. $
    > >
    ./gradlew build createSwiftPakcage
    Building iOS Swift Package

    View Slide

  78. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  79. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  80. Local Deployment for Android
    Consuming from mavenLocal()
    ./gradlew build publishToMavenLocal
    ~/.m2
    build.gradle
    allProjects {
    repositories {
    mavenLocal()
    }
    }
    Android

    View Slide

  81. Local Deployment for iOS
    Consuming local Swift Package
    ./gradlew build createSwiftPackage
    shared/output/swiftpackage
    iOS
    1) add package dependency
    2) add local
    3) select shared/output/swiftpackage
    Xcode

    View Slide

  82. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  83. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  84. How do we ensure low cost deployment?
    Building a CI/CD pipeline
    • How to build the project in CI?

    • How to deploy the .jar and .aar
    fi
    les to internal Artifactory server?

    • How to deploy the Swift Package?

    View Slide

  85. Building the Project with GitHub Actions
    checkout setup-java
    setup-xcode
    ./gradlew build
    ./gradlew createSwiftPackage

    View Slide

  86. Deploying Android Artifacts
    / /
    analytics/build.gradle.kts

    plugins {

    id(“com.jfrog.artifactory”)

    }

    View Slide

  87. Deploying Android Artifacts
    / /
    analytics/build.gradle.kts

    plugins {

    . . .

    }

    View Slide

  88. Deploying Android Artifacts
    / /
    analytics/build.gradle.kts

    plugins { . . . }

    View Slide

  89. Deploying Android Artifacts
    / /
    analytics/build.gradle.kts

    plugins { . . . }

    / /
    hand-wavy pseudocode

    artifactory {

    setContextUrl(“”)

    }

    View Slide

  90. Deploying Android Artifacts
    / /
    analytics/build.gradle.kts

    plugins { . . . }

    / /
    hand-wavy pseudocode

    artifactory {

    setContextUrl(“”)

    }

    View Slide

  91. Deploying Android Artifacts
    / /
    analytics/build.gradle.kts

    plugins { . . . }

    / /
    hand-wavy pseudocode

    artifactory {

    setContextUrl(“”)

    repository {

    setProperty(“repoKey”, “”)

    setProperty(“username”, “”)

    setProperty(“password”, “”)

    setProperty(“maven”, true)

    }

    }

    View Slide

  92. Deploying Android Artifacts
    / /
    hand-wavy pseudocode

    artifactory {

    setContextUrl(“”)

    repository {

    setProperty(“repoKey”, “”)

    setProperty(“username”, “”)

    setProperty(“password”, “”)

    setProperty(“maven”, true)

    }

    }

    View Slide

  93. Deploying Android Artifacts
    / /
    hand-wavy pseudocode

    artifactory {

    setContextUrl(“”)

    repository {

    setProperty(“repoKey”, “”)

    setProperty(“username”, “”)

    setProperty(“password”, “”)

    setProperty(“maven”, true)

    }

    }

    View Slide

  94. Deploying Android Artifacts
    / /
    hand-wavy pseudocode

    artifactory {

    setContextUrl(“”)

    repository {

    setProperty(“repoKey”, “”)

    setProperty(“username”, “”)

    setProperty(“password”, “”)

    setProperty(“maven”, true)

    }

    defaults {

    invokeMethod(“publications”, arrayOf(“jvm”, “kotlinmultiplatform”))

    }

    }

    View Slide

  95. Consuming Android Artifact
    / /
    build.gradle.kts

    repositories {

    maven(url = “”)

    }

    / /
    app/build.gradle.kts

    dependencies {

    implementation(“com.yourpackage.analytics:1.0.0”)

    }

    View Slide

  96. Building the Project with GitHub Actions
    checkout setup-java
    setup-xcode
    ./gradlew build
    ./gradlew createSwiftPackage

    View Slide

  97. Building the Project with GitHub Actions
    checkout setup-java
    setup-xcode
    ./gradlew build
    ./gradlew createSwiftPackage

    View Slide

  98. Building the Project with GitHub Actions
    checkout setup-java
    setup-xcode
    ./gradlew build
    ./gradlew createSwiftPackage
    ./gradlew publishToArtifactory

    View Slide

  99. Deploying Swift Packages
    shared.xcframework
    Package.swift
    premise/mobile-shared-swift-package
    .gitignore

    View Slide

  100. Deploying Swift Packages
    shared.xcframework
    Package.swift
    premise/mobile-shared-swift-package
    .gitignore
    import PackageDescription

    let package = Package(

    name: “shared”,

    platforms: [ .iOS(.v13)],

    products: [

    .library(

    name: “shared”,

    targets: [“shared”]

    ),

    ],

    targets: [

    .binaryTarget(

    name: “shared”,

    path: “./shared.xcframework”

    )

    ]

    )
    / /
    Package.swift

    View Slide

  101. Consuming Swift Packages
    Xcode
    1) Select Add framework or library

    2) Select Add Package Dependency

    3) Enter package url into search bar

    4) Authenticate with GitHub

    5) Select Add Package

    View Slide

  102. Building the Project with GitHub Actions
    checkout setup-java
    setup-xcode
    ./gradlew build
    ./gradlew createSwiftPackage
    ./gradlew publishToArtifactory

    View Slide

  103. Building the Project with GitHub Actions
    checkout setup-java
    setup-xcode
    ./gradlew build
    ./gradlew createSwiftPackage
    ./gradlew publishToArtifactory
    clone

    mobile-shared-swift-package
    commit, push & tag

    swiftpackage

    View Slide

  104. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  105. Primary questions
    How do we build and scale a KMP solution?
    • Where will this new code be stored?

    • How to structure a non-monorepo KMP project?

    • What multiplatform targets to choose?

    • How to distribute a multi-module artifact to iOS?

    • How to build output artifacts?

    • How to build, deploy, test locally?

    • How to ensure low cost deployment?

    • How to integrate into our existing iOS and Android projects?

    View Slide

  106. View Slide

  107. How are we using
    Kotlin Multiplatform today?

    View Slide

  108. Kotlin Multiplatform at Premise Today
    Analytics Deeplinking
    Recommendations Security
    Performance
    Networking

    View Slide

  109. Challenges
    Build Times Swift Package Generation
    API Usability Shared Ownership
    Swift Package Sync Time

    View Slide

  110. • Building XCFramework takes a long time

    • Build only what you need

    • Skip iOS targets if developing/iterating on Android

    • Reduce iOS targets if developing/iterating on speci
    fi
    c iOS target
    Build Times

    View Slide

  111. • multiplatform-swiftpackage hasn’t been updated with M1 support

    • Community forks available

    • Rolled our own custom Gradle task

    • Less fully-featured

    • Very focused on our exact needs
    Swift Package Generation

    View Slide

  112. • Syncing our Swift Package in XCode can take 30+ min

    • Publishing XCFrameworks to GitHub increases download size of repo

    • XCode doesn’t support shallow clone of the Swift Package repo

    • Update Swift Package to use a remote URL for framework download

    • Keeps large
    fi
    les out of the repo

    • Keeps XCode package sync fast
    Swift Package Sync Time

    View Slide

  113. • Kotlin features don’t always translate well to Objective-C

    • Enums, Sealed Classes, suspend functions, default parameters

    • Write or consume wrappers to improve api usability

    • Collaborate with iOS team to
    fi
    nd opportunities for improvement

    • Community work into improved Swift API generation
    API Usability

    View Slide

  114. • Majority of development, maintenance, and understanding consolidated in
    1-2 members of the team

    • Encouraging shared ownership

    • Lunch-n-learns to build shared understanding

    • All Mobile developers can build both iOS and Android projects
    Shared Ownership

    View Slide

  115. “Collaborate from the beginning”
    Tip #5

    View Slide

  116. Collaborate From the Beginning
    • Get feedback from both iOS and Android teams early, and often

    • Validate new patterns across both platforms

    • Regularly build both projects to understand developer experience

    • Onboard new developers to shared project early on

    • Encourage contributions from all team members

    View Slide

  117. Takeaways

    View Slide

  118. Tips For Adopting Kotlin Multiplatform
    • “Let the problem de
    fi
    ne the solution”
    • “Evaluate both technical and human factors”
    • “Start small”
    • “Think like a library developer”
    • “Collaborate from the beginning”
    Tip #1
    Tip #2
    Tip #3
    Tip #4
    Tip #5

    View Slide

  119. Kotlin Multiplatform at Premise
    Blog series
    • Kotlin Multiplatform at Premise

    • Kotlin Multiplatform Project Structure for
    Integration with Brown
    fi
    eld Applications

    • Building a CI Pipeline for Kotlin Multiplatform
    Mobile Using GitHub Actions

    View Slide

  120. Thank You
    Nate Ebel
    @n8ebel

    View Slide