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

Adopting Kotlin Multiplatform in Brownfield App...

Nate Ebel
September 06, 2022

Adopting Kotlin Multiplatform in Brownfield Applications - DCNYC22

Presented at Droidcon NYC 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

September 06, 2022
Tweet

More Decks by Nate Ebel

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. 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
  4. 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
  5. 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?
  6. 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
  7. 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?
  8. Where will this new code live? Greenfield KMP Project iOS

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

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

    our use case? iosMain androidMain commonMain
  13. 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
  14. Configuring Build Targets kotlin { jvm() iosX64() iosArm64() sourceSets {

    val commonMain by getting val jvmMain by getting val iosX64Main by getting val iosArm64Main by getting } }
  15. 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) } } }
  16. 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?
  17. 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?
  18. Greenfield KMP Project Shared What if we want multiple multiplatform

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

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

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

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

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

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

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

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

    iOS consumption // shared/build.gradle.kts plugins { kotlin(“multiplatform”) }
  27. 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) }
  28. 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 -> } }
  29. 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 } } }
  30. 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 } } }
  31. 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 } } }
  32. Configuring a Composite Module Combine separate modules into 1 for

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

    .aar • maven-publish plugin • iOS • XCFramework • Cocoapods • Swift Package Manager
  36. Building Android Output Artifacts // analytics/build.gradle.kts plugins { … }

    kotlin { android { publishAllLibraryVariants() } }
  37. Building iOS Swift Package // shared/build.gradle.kts plugins { . .

    . } multiplatformSwiftPackage { swiftToolsVersion(“5.3”) targetPlatforms { iOS { v(“13”) } } packageName(rootProject.name) distributionMode { local() } }
  38. 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?
  39. 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?
  40. Local Deployment for Android Consuming from mavenLocal() ./gradlew build publishToMavenLocal

    ~/.m2 build.gradle allProjects { repositories { mavenLocal() } } Android
  41. 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
  42. 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?
  43. 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?
  44. 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?
  45. Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . .

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

    . } / / hand-wavy pseudocode artifactory { setContextUrl(“<your server url>”) }
  47. 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) } }
  48. Deploying Android Artifacts / / hand-wavy pseudocode artifactory { setContextUrl(“<your

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

    server url>”) repository { setProperty(“repoKey”, “<repo>”) setProperty(“username”, “<username>”) setProperty(“password”, “<password>”) setProperty(“maven”, true) } }
  50. 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”)) } }
  51. 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”) }
  52. Building the Project with GitHub Actions checkout setup-java setup-xcode ./gradlew

    build ./gradlew createSwiftPackage ./gradlew publishToArtifactory
  53. 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
  54. 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
  55. Building the Project with GitHub Actions checkout setup-java setup-xcode ./gradlew

    build ./gradlew createSwiftPackage ./gradlew publishToArtifactory
  56. 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
  57. 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?
  58. 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?
  59. • 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
  60. • 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
  61. • 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
  62. • 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
  63. • 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
  64. 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
  65. 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