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

Adopting Kotlin Multiplatform in Brownfield Applications

Nate Ebel
September 06, 2022

Adopting Kotlin Multiplatform in Brownfield Applications

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

September 06, 2022
Tweet

More Decks by Nate Ebel

Other Decks in Programming

Transcript

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

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

  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
  4. Kotlin Multiplatform Integration Roadmap

  5. Organizational Buy In

  6. About our team • 8 Android developers • 7 iOS

    developers • Android- fi rst feature development • Localized in 40 languages • Monthly active users in the hundreds of thousands
  7. “Let the problem define the solution” Tip #1

  8. Our Problem “Analytics is a mess”

  9. 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
  10. Rationalizing analytics at Premise Can we codify this?

  11. shared code for shared thinking

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

  13. iOS team suggested KMP fi rst

  14. “Find an iOS team that wants to use Kotlin Multiplatform”

    Fake Tip #1
  15. “Evaluate both technical, and human factors” Tip #2

  16. 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
  17. What next? How do we validate this KMP solution for

    shared analytics?
  18. 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?
  19. None
  20. Create the project Answer technical questions Ship…something

  21. What to ship first?

  22. “Start small” Tip #3

  23. 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
  24. None
  25. Building Our Kotlin Multiplatform Solution

  26. “Think like library developers” Tip #4

  27. None
  28. 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?
  29. Where will this new code live?

  30. Where will this new code live? Greenfield KMP Project iOS

    Shared Android All projects live side by side in a single mono repo
  31. 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
  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
  33. Greenfield KMP Project Shared How to structure this new KMP

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

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

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

    project? iosMain androidMain commonMain Are these the right targets for our use case?
  37. Kotlin Multiplatform Targets iOS Javascript Linux JVM Android Mac

  38. Greenfield KMP Project Shared Are these the right targets for

    our use case? iosMain androidMain commonMain
  39. 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
  40. Configuring Build Targets // analytics/build.gradle.kts plugins { kotlin(“multiplatform”) }

  41. Configuring Build Targets // analytics/build.gradle.kts plugins { kotlin(“multiplatform”) } kotlin

    { jvm() iosX64() iosArm64() }
  42. Configuring Build Targets kotlin { jvm() iosX64() iosArm64() }

  43. Configuring Build Targets kotlin { jvm() iosX64() iosArm64() }

  44. Configuring Build Targets kotlin { jvm() iosX64() iosArm64() sourceSets {

    val commonMain by getting val jvmMain by getting val iosX64Main by getting val iosArm64Main by getting } }
  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 val iosMain by creating { dependsOn(commonMain) iosX64Main.dependsOn(this) iosArm64Main.dependsOn(this) } } }
  46. 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?
  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?
  48. Greenfield KMP Project Shared What if we want multiple multiplatform

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

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

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

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

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

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

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

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

    artifacts? DTOs Networking Recommendations iOS Shared
  57. Configuring a Composite Module Combine separate modules into 1 for

    iOS consumption // shared/build.gradle.kts plugins { kotlin(“multiplatform”) }
  58. 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) }
  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) listOf(iosX64(), iosArm64(), iosSimulatorArm64()) .forEach { target -> } }
  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 -> target.binaries.framework { xcf.add(this) export(project(“:analytics”)) export(project(“:network”)) transitiveExport = true } } }
  61. 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 } } }
  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 } } }
  63. Configuring a Composite Module Combine separate modules into 1 for

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

    iOS consumption kotlin { . . . sourceSets { . . . iosMain by creating { dependencies { api(project(“:analytics”)) api(project(“:network”)) } } } }
  65. 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?
  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?
  67. Building Output Artifacts • Android / JVM • .jar /

    .aar • maven-publish plugin • iOS • XCFramework • Cocoapods • Swift Package Manager
  68. Building JVM Output Artifacts / / analytics/build.gradle.kts plugins { kotlin(“multiplatform”)

    
 id(“maven-publish”) }
  69. Building Android Output Artifacts / / analytics/build.gradle.kts plugins { kotlin(“multiplatform”)

    id(“maven-publish”) id(“com.android.library”) }
  70. Building Android Output Artifacts / / analytics/build.gradle.kts plugins { kotlin(“multiplatform”)

    id(“maven-publish”) id(“com.android.library”) }
  71. Building Android Output Artifacts // analytics/build.gradle.kts plugins { … }

    kotlin { android { publishAllLibraryVariants() } }
  72. Building JVM/Android Output Artifacts $ > > ./gradlew build publishToMavenLocal

  73. Building iOS Swift Package / / shared/build.gradle.kts plugins { kotlin(“multiplatform”)

    id(“com.chromaticnoise.multiplatform-swiftpackage”) }
  74. Building iOS Swift Package // shared/build.gradle.kts plugins { . .

    . }
  75. Building iOS Swift Package // shared/build.gradle.kts plugins { . .

    . } multiplatformSwiftPackage { swiftToolsVersion(“5.3”) targetPlatforms { iOS { v(“13”) } } packageName(rootProject.name) distributionMode { local() } }
  76. $ > > ./gradlew build createSwiftPakcage Building iOS Swift Package

  77. 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?
  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?
  79. Local Deployment for Android Consuming from mavenLocal() ./gradlew build publishToMavenLocal

    ~/.m2 build.gradle allProjects { repositories { mavenLocal() } } Android
  80. 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
  81. 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?
  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?
  83. 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?
  84. Building the Project with GitHub Actions checkout setup-java setup-xcode ./gradlew

    build ./gradlew createSwiftPackage
  85. Deploying Android Artifacts / / analytics/build.gradle.kts plugins { id(“com.jfrog.artifactory”) }

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

    . }
  87. Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . .

    . }
  88. Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . .

    . } / / hand-wavy pseudocode artifactory { setContextUrl(“<your server url>”) }
  89. Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . .

    . } / / hand-wavy pseudocode artifactory { setContextUrl(“<your server url>”) }
  90. Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . .

    . } / / hand-wavy pseudocode artifactory { setContextUrl(“<your server url>”) repository { setProperty(“repoKey”, “<repo>”) setProperty(“username”, “<username>”) setProperty(“password”, “<password>”) setProperty(“maven”, true) } }
  91. Deploying Android Artifacts / / hand-wavy pseudocode artifactory { setContextUrl(“<your

    server url>”) repository { setProperty(“repoKey”, “<repo>”) setProperty(“username”, “<username>”) setProperty(“password”, “<password>”) setProperty(“maven”, true) } }
  92. Deploying Android Artifacts / / hand-wavy pseudocode artifactory { setContextUrl(“<your

    server url>”) repository { setProperty(“repoKey”, “<repo>”) setProperty(“username”, “<username>”) setProperty(“password”, “<password>”) setProperty(“maven”, true) } }
  93. Deploying Android Artifacts / / hand-wavy pseudocode artifactory { setContextUrl(“<your

    server url>”) repository { setProperty(“repoKey”, “<repo>”) setProperty(“username”, “<username>”) setProperty(“password”, “<password>”) setProperty(“maven”, true) } defaults { invokeMethod(“publications”, arrayOf(“jvm”, “kotlinmultiplatform”)) } }
  94. Consuming Android Artifact / / build.gradle.kts repositories { maven(url =

    “<your maven artifact server>”) } / / app/build.gradle.kts dependencies { implementation(“com.yourpackage.analytics:1.0.0”) }
  95. Building the Project with GitHub Actions checkout setup-java setup-xcode ./gradlew

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

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

    build ./gradlew createSwiftPackage ./gradlew publishToArtifactory
  98. Deploying Swift Packages shared.xcframework Package.swift premise/mobile-shared-swift-package .gitignore

  99. 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
  100. 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
  101. Building the Project with GitHub Actions checkout setup-java setup-xcode ./gradlew

    build ./gradlew createSwiftPackage ./gradlew publishToArtifactory
  102. 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
  103. 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?
  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?
  105. None
  106. How are we using Kotlin Multiplatform today?

  107. Kotlin Multiplatform at Premise Today Analytics Deeplinking Recommendations Security Performance

    Networking
  108. Challenges Build Times Swift Package Generation API Usability Shared Ownership

    Swift Package Sync Time
  109. • Building XCFramework takes a long time • Build only

    what you need • Skip iOS targets if developing/iterating on Android • Reduce iOS targets is developing/iterating on speci fi c iOS target Build Times
  110. • multiplatform-swiftpackage hasn’t been updated with M1 support • Rolled

    out own custom Gradle task • Less fully-featured • Very focused on our exact needs Swift Package Generation
  111. • 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
  112. • 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 API Usability
  113. • 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
  114. “Collaborate from the beginning” Tip #5

  115. 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
  116. Takeaways

  117. 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
  118. Thank You Nate Ebel @n8ebel