Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

How can your team start sharing code using Kotlin Multiplatform?

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

Kotlin Multiplatform Integration Roadmap

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Organizational Buy In

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

“Let the problem define the solution” Tip #1

Slide 9

Slide 9 text

Our Problem “Analytics is a mess”

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

Rationalizing analytics at Premise Can we codify this?

Slide 12

Slide 12 text

shared code for shared thinking

Slide 13

Slide 13 text

Perfect use case How to pitch KMP to the team?

Slide 14

Slide 14 text

iOS team suggested KMP fi rst

Slide 15

Slide 15 text

“Find an iOS team that wants to use Kotlin Multiplatform” Fake Tip #1

Slide 16

Slide 16 text

“Evaluate both technical, and human factors” Tip #2

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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?

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Create the project Answer technical questions Ship…something

Slide 22

Slide 22 text

What to ship first?

Slide 23

Slide 23 text

“Start small” Tip #3

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

No content

Slide 26

Slide 26 text

Building Our Kotlin Multiplatform Solution

Slide 27

Slide 27 text

“Think like library developers” Tip #4

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

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?

Slide 30

Slide 30 text

Where will this new code live?

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Kotlin Multiplatform Targets iOS Javascript Linux JVM Android Mac

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

Configuring Build Targets kotlin { jvm() iosX64() iosArm64() sourceSets { val commonMain by getting val jvmMain by getting val iosX64Main by getting val iosArm64Main by getting } }

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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?

Slide 48

Slide 48 text

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?

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

Configuring a Composite Module Combine separate modules into 1 for iOS consumption // shared/build.gradle.kts plugins { kotlin(“multiplatform”) }

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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 -> } }

Slide 61

Slide 61 text

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 } } }

Slide 62

Slide 62 text

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 } } }

Slide 63

Slide 63 text

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 } } }

Slide 64

Slide 64 text

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

Slide 65

Slide 65 text

Configuring a Composite Module Combine separate modules into 1 for iOS consumption kotlin { . . . sourceSets { . . . iosMain by creating { dependencies { api(project(“:analytics”)) api(project(“:network”)) } } } }

Slide 66

Slide 66 text

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?

Slide 67

Slide 67 text

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?

Slide 68

Slide 68 text

Building Output Artifacts • Android / JVM • .jar / .aar • maven-publish plugin • iOS • XCFramework • Cocoapods • Swift Package Manager

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Building Android Output Artifacts // analytics/build.gradle.kts plugins { … } kotlin { android { publishAllLibraryVariants() } }

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Building iOS Swift Package // shared/build.gradle.kts plugins { . . . } multiplatformSwiftPackage { swiftToolsVersion(“5.3”) targetPlatforms { iOS { v(“13”) } } packageName(rootProject.name) distributionMode { local() } }

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

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?

Slide 79

Slide 79 text

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?

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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?

Slide 83

Slide 83 text

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?

Slide 84

Slide 84 text

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?

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

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

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . . . } / / hand-wavy pseudocode artifactory { setContextUrl(“”) }

Slide 90

Slide 90 text

Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . . . } / / hand-wavy pseudocode artifactory { setContextUrl(“”) }

Slide 91

Slide 91 text

Deploying Android Artifacts / / analytics/build.gradle.kts plugins { . . . } / / hand-wavy pseudocode artifactory { setContextUrl(“”) repository { setProperty(“repoKey”, “”) setProperty(“username”, “”) setProperty(“password”, “”) setProperty(“maven”, true) } }

Slide 92

Slide 92 text

Deploying Android Artifacts / / hand-wavy pseudocode artifactory { setContextUrl(“”) repository { setProperty(“repoKey”, “”) setProperty(“username”, “”) setProperty(“password”, “”) setProperty(“maven”, true) } }

Slide 93

Slide 93 text

Deploying Android Artifacts / / hand-wavy pseudocode artifactory { setContextUrl(“”) repository { setProperty(“repoKey”, “”) setProperty(“username”, “”) setProperty(“password”, “”) setProperty(“maven”, true) } }

Slide 94

Slide 94 text

Deploying Android Artifacts / / hand-wavy pseudocode artifactory { setContextUrl(“”) repository { setProperty(“repoKey”, “”) setProperty(“username”, “”) setProperty(“password”, “”) setProperty(“maven”, true) } defaults { invokeMethod(“publications”, arrayOf(“jvm”, “kotlinmultiplatform”)) } }

Slide 95

Slide 95 text

Consuming Android Artifact / / build.gradle.kts repositories { maven(url = “”) } / / app/build.gradle.kts dependencies { implementation(“com.yourpackage.analytics:1.0.0”) }

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

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

Slide 102

Slide 102 text

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

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

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?

Slide 105

Slide 105 text

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?

Slide 106

Slide 106 text

No content

Slide 107

Slide 107 text

How are we using Kotlin Multiplatform today?

Slide 108

Slide 108 text

Kotlin Multiplatform at Premise Today Analytics Deeplinking Recommendations Security Performance Networking

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

• 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

Slide 111

Slide 111 text

• 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

Slide 112

Slide 112 text

• 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

Slide 113

Slide 113 text

• 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

Slide 114

Slide 114 text

• 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

Slide 115

Slide 115 text

“Collaborate from the beginning” Tip #5

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

Takeaways

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

Thank You Nate Ebel @n8ebel