You are on page 1of 118

Adopting Kotlin Multiplatform

In Brown eld Applications

Nate Ebel
@n8ebel
fi
How can your team start sharing
code using Kotlin Multiplatform?
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


Kotlin Multiplatform
Integration Roadmap
Organizational Buy In
About our team

• 8 Android developers

• 7 iOS developers

• Android- rst feature development

• Localized in 40 languages

• Monthly active users in the hundreds of thousands


fi
Tip #1

“Let the problem define the solution”


Our Problem
“Analytics is a mess”
Rationalizing analytics at Premise

• Enumerate standard types of analytics events

• De ne rule patterns for each event type

• Create shared documentation for Mobile team


fi
Rationalizing analytics at Premise

Can we codify this?


shared code for shared thinking
Perfect use case
How to pitch KMP to the
team?
iOS team suggested KMP
rst
fi
Fake Tip #1

“Find an iOS team that wants to use


Kotlin Multiplatform”

Tip #2

“Evaluate both technical, and


human factors”

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 ort integration into existing apps

• Have engineers willing to prototype, learn, and educate


ff
What next?
How do we validate this
KMP solution for shared
analytics?
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?


Create the project Answer technical questions Ship…something
What to ship first?
Tip #3

“Start small”
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


Building Our
Kotlin Multiplatform Solution
Tip #4

“Think like library developers”


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?


Where will this new code live?
Where will this new code live?
Greenfield KMP Project

iOS

All projects live side by side in a


Android single mono repo

Shared
Where will this new code live?
Brownfield iOS Project Greenfield KMP Project

iOS Shared

Brownfield Android Project


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

iOS Shared

Brownfield Android Project


Existing projects are untouched
Shared code exists in a new, separate repo
Android
Greenfield KMP Project

How to structure this Shared


new KMP project?
How to structure this new KMP project?

Greenfield KMP Project

Shared
How to structure this new KMP project?

Greenfield KMP Project

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

Shared

iosMain

Are these the right


targets for our use case?
androidMain

commonMain
Kotlin Multiplatform Targets

iOS Javascript Android

Linux JVM Mac


Are these the right targets for our use case?
Greenfield KMP Project

Shared

iosMain

androidMain

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

Shared

iosMain

Chose JVM over Android to


support integration with JVM-
based backend services
jvmMain

commonMain
Configuring Build Targets

analytics/build.gradle.kts

plugins { kotlin(“multiplatform”) }
/
/
Configuring Build Targets

analytics/build.gradle.kts

plugins { kotlin(“multiplatform”) }

kotlin {

jvm()

iosX64()

iosArm64()

}
/
/
Configuring Build Targets

kotlin {

jvm()

iosX64()

iosArm64()

}
Configuring Build Targets

kotlin {

jvm()

iosX64()

iosArm64()

}
Configuring Build Targets

kotlin {

jvm()

iosX64()

iosArm64()

sourceSets {

val commonMain by getting

val jvmMain by getting

val iosX64Main by getting

val iosArm64Main by getting

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

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


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?


What if we want multiple multiplatform artifacts?
Greenfield KMP Project

Shared

iosMain

Imagine the N+1 solution of


additional multiplatform features
jvmMain

commonMain
What if we want multiple multiplatform artifacts?
Greenfield KMP Project

iosMain

Imagine the N+1 solution of


additional multiplatform features
jvmMain

commonMain
What if we want multiple multiplatform artifacts?
Greenfield KMP Project

Analytics

iosMain

Imagine the N+1 solution of


additional multiplatform features
jvmMain

commonMain
What if we want multiple multiplatform artifacts?
Greenfield KMP Project

Analytics

Imagine the N+1 solution of


additional multiplatform features
What if we want multiple multiplatform artifacts?
Greenfield KMP Project

Analytics

DTOs

Networking

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

Analytics

DTOs

Android

Networking

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

Analytics

DTOs

iOS

Networking

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

Analytics

DTOs

iOS

Networking

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

Analytics

DTOs

Shared iOS

Networking

Recommendations
Configuring a Composite Module
Combine separate modules into 1 for iOS consumption

shared/build.gradle.kts

plugins { kotlin(“multiplatform”) }
/
/
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)

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

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

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

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

}
-
>
Configuring a Composite Module
Combine separate modules into 1 for iOS consumption

kotlin {

. . .

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

kotlin {

. . .

sourceSets {

. . .

iosMain by creating {

dependencies {

api(project(“:analytics”))

api(project(“:network”))

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?


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?


Building Output Artifacts

• Android / JVM

• .jar / .aar

• maven-publish plugin

• iOS

• XCFramework

• Cocoapods

• Swift Package Manager


Building JVM Output Artifacts

analytics/build.gradle.kts

plugins {

kotlin(“multiplatform”)
id(“maven-publish”)

}
/
/

Building Android Output Artifacts

analytics/build.gradle.kts

plugins {

kotlin(“multiplatform”)

id(“maven-publish”)

id(“com.android.library”)

/
/
Building Android Output Artifacts

analytics/build.gradle.kts

plugins {

kotlin(“multiplatform”)

id(“maven-publish”)

id(“com.android.library”)

/
/
Building Android Output Artifacts

analytics/build.gradle.kts

plugins {

kotlin {

android {

publishAllLibraryVariants()

/
/
Building JVM/Android Output Artifacts

$ ./gradlew build publishToMavenLocal


>
>
Building iOS Swift Package

shared/build.gradle.kts

plugins {

kotlin(“multiplatform”)

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

}
/
/
Building iOS Swift Package

shared/build.gradle.kts

plugins {

. . .

}
/
/
Building iOS Swift Package

shared/build.gradle.kts

plugins {

. . .

multiplatformSwiftPackage {

swiftToolsVersion(“5.3”)

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

packageName(rootProject.name)

distributionMode { local() }

}
/
/
Building iOS Swift Package

$ ./gradlew build createSwiftPakcage


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


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?


Local Deployment for Android
Consuming from mavenLocal()

./gradlew build publishToMavenLocal

Android

allProjects {
repositories {
mavenLocal()
}
~/.m2 }

build.gradle
Local Deployment for iOS
Consuming local Swift Package

./gradlew build createSwiftPackage

iOS

1) add package dependency


2) add local
3) select shared/output/swiftpackage

Xcode

shared/output/swiftpackage
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?


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?


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 les to internal Artifactory server?

• How to deploy the Swift Package?


fi
Building the Project with GitHub Actions

checkout setup-xcode setup-java

./gradlew createSwiftPackage ./gradlew build


Deploying Android Artifacts

analytics/build.gradle.kts

plugins {

id(“com.jfrog.artifactory”)

/
/
Deploying Android Artifacts

analytics/build.gradle.kts

plugins {

. . .

/
/
Deploying Android Artifacts

analytics/build.gradle.kts

plugins { . . . }

/
/
Deploying Android Artifacts

analytics/build.gradle.kts

plugins { . . . }

hand-wavy pseudocode

artifactory {

setContextUrl(“<your server url>”)

}
/
/
/
/
Deploying Android Artifacts

analytics/build.gradle.kts

plugins { . . . }

hand-wavy pseudocode

artifactory {

setContextUrl(“<your server url>”)

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

}
/
/
/
/
Deploying Android Artifacts

hand-wavy pseudocode

artifactory {

setContextUrl(“<your server url>”)

repository {

setProperty(“repoKey”, “<repo>”)

setProperty(“username”, “<username>”)

setProperty(“password”, “<password>”)

setProperty(“maven”, true)

}
/
/
Deploying Android Artifacts

hand-wavy pseudocode

artifactory {

setContextUrl(“<your server url>”)

repository {

setProperty(“repoKey”, “<repo>”)

setProperty(“username”, “<username>”)

setProperty(“password”, “<password>”)

setProperty(“maven”, true)

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

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

/
/
/
/
Building the Project with GitHub Actions

checkout setup-xcode setup-java

./gradlew createSwiftPackage ./gradlew build


Building the Project with GitHub Actions

checkout setup-xcode setup-java

./gradlew createSwiftPackage ./gradlew build


Building the Project with GitHub Actions

checkout setup-xcode setup-java

./gradlew createSwiftPackage ./gradlew build

./gradlew publishToArtifactory
Deploying Swift Packages

premise/mobile-shared-swift-package

shared.xcframework

Package.swift

.gitignore
Deploying Swift Packages

import PackageDescription

premise/mobile-shared-swift-package let package = Package(

name: “shared”,

platforms: [ .iOS(.v13)],

products: [

shared.xcframework .library(

name: “shared”,

targets: [“shared”]

),

Package.swift ],

targets: [

.binaryTarget(

.gitignore name: “shared”,

path: “./shared.xcframework”

) Package.swift
/
/
Consuming Swift Packages

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

Xcode
Building the Project with GitHub Actions

checkout setup-xcode setup-java

./gradlew createSwiftPackage ./gradlew build

./gradlew publishToArtifactory
Building the Project with GitHub Actions

checkout setup-xcode setup-java

./gradlew createSwiftPackage ./gradlew build

clone
commit, push & tag

./gradlew publishToArtifactory
mobile-shared-swift-package swiftpackage
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?


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?


How are we using
Kotlin Multiplatform today?
Kotlin Multiplatform at Premise Today

Analytics Deeplinking Performance

Recommendations Security Networking


Challenges

Build Times Swift Package Generation Swift Package Sync Time

API Usability Shared Ownership


Build Times

• 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 c iOS target

fi
Swift Package Generation

• 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 Sync Time

• 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 les out of the repo

• Keeps XCode package sync fast


fi
API Usability

• 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 nd opportunities for improvement


fi
Shared Ownership

• 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
Tip #5

“Collaborate from the beginning”


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


Takeaways
Tips For Adopting Kotlin Multiplatform

Tip #1 • “Let the problem de ne the solution”


Tip #2 • “Evaluate both technical and human factors”
Tip #3 • “Start small”
Tip #4 • “Think like a library developer”
Tip #5 • “Collaborate from the beginning”
fi
Thank You

Nate Ebel
@n8ebel

You might also like