SlideShare a Scribd company logo
KMM Survival Guide:
how to tackle everyday
struggles between Kotlin
and Swift
Emanuele Papa
Commit University November 2022
Working at Zest One in Chiasso, Switzerland
Who I am?
Emanuele Papa, Android Developer
Find me at www.emanuelepapa.dev
What is KMM?
Kotlin Multiplatform
Mobile is an SDK for iOS
and Android app
development. It offers all
the combined benefits of
creating cross-platform
and native apps.
Shared module: you write Kotlin code as you are used to do.
Android module: you see no difference and just use the shared module
code
iOS project: using Kotlin Native, the code is compiled into an
AppleFramework like it was written in Objective-C.
Then you can just use it in the same way as when you import an iOS
dependency.
How does it work?
Swift and Kotlin are both modern languages and have a lot of
syntax similarities
Swift is like Kotlin
Everything looks cool!
I can become an iOS developer!
(or I can become an Android developer!)
Expectation
Not everything on KMM works out of the box.
Unfortunately, most of the issues arise because the
Kotlin code is transformed into ObjectiveC code.
Reality
KMM directly supports Swift interoperability
(KT-49521)
Write shared code in a slightly different way to better
support iOS development (and iOS developers)
Solutions
Primitive types
fun getRandomInt(): Int = Random.nextInt(until = 10)
Shared code
Primitive types
val randomInt: Int = getRandomInt()
Android
Primitive types
let randomInt: Int32 = RandomNumberGeneratorKt.getRandomInt()
iOS
Sealed classes
sealed class KMMIntResult
data class SuccessKMMIntResult(
val value: Int
) : KMMIntResult()
data class ErrorKMMIntResult(
val throwable: Throwable
) : KMMIntResult()
Shared code
Sealed classes
fun getRandomIntWrappedInIntResult(): KMMIntResult {
val isSuccess = Random.nextBoolean()
return if(isSuccess) {
SuccessKMMIntResult(Random.nextInt(until = 10))
} else {
ErrorKMMIntResult(RuntimeException("There was an error, Int not generated"))
}
}
Shared code
Sealed classes
val randomInt: KMMIntResult = getRandomIntWrappedInIntResult()
val randomIntText: String = when (randomInt) {
is KMMIntResult.ErrorKMMIntResult -> {
"Error: ${randomInt.throwable.message}"
}
is KMMIntResult.SuccessKMMIntResult -> {
"Success: ${randomInt.value}"
}
}
Android
Sealed classes
let randomInt: KMMIntResult = RandomNumberGeneratorKt.getRandomIntWrappedInIntResult()
let randomIntText: String
switch randomInt {
case let error as KMMIntResult.ErrorKMMIntResult:
randomIntText = "Error: (error.throwable.message ?? error.throwable.description())"
case let success as KMMIntResult.SuccessKMMIntResult:
randomIntText = "Success: (success.value)"
default:
randomIntText = "This never happens"
}
iOS
Generic sealed class
sealed class KMMResult<out Value>
data class SuccessKMMResult<Value>(
val value: Value
): KMMResult<Value>()
data class ErrorKMMResult(
val throwable: Throwable
): KMMResult<Nothing>()
Shared code
Generic sealed class
fun getRandomIntWrappedInResult(): KMMResult<Int> {
val isSuccess = Random.nextBoolean()
return if(isSuccess) {
SuccessKMMResult(Random.nextInt(until = 10))
} else {
ErrorKMMResult(RuntimeException("There was an error, Int not generated"))
}
}
Shared code
Generic sealed class
Android
val randomInt: KMMResult<Int> = getRandomIntWrappedInResult()
val randomIntText: String = when (randomInt) {
is KMMResult.ErrorKMMResult -> {
"Error: ${randomInt.throwable.message}"
}
is KMMResult.SuccessKMMResult -> {
"Success: ${randomInt.value}"
}
}
Generic sealed class
iOS
let randomInt: KMMResult<KotlinInt> = RandomNumberGeneratorKt.getRandomIntWrappedInIntResult()
let randomIntText: String
switch randomInt {
case let error as KMMResultErrorKMMResult:
randomIntText = "Error: (error.throwable.message ?? error.throwable.description())"
case let success as KMMResultSuccessKMMResult<KotlinInt>:
randomIntText = "Success: (success.value)"
default:
randomIntText = "This never happens"
}
Generics sealed class
First solution
data class ErrorKMMResult(
val throwable: Throwable
): KMMResult<Nothing>()
data class ErrorKMMResult<Value>(
val throwable: Throwable
): KMMResult<Value>()
case let error as
KMMResultErrorKMMResult<KotlinInt>:
case let error as
KMMResultErrorKMMResult:
Generics sealed class
Second solution
enum SwiftResult<Value> {
case error(String)
case success(Value)
}
Generics sealed class
Second solution
func toSwiftResult<Value>(kmmResult: KMMResult<Value>) -> SwiftResult<Value> {
if let successResult = kmmResult as? KMMResultSuccessKMMResult<Value> {
return SwiftResult.success(successResult.value!)
}
if let errorResult = kmmResult as? KMMResultErrorKMMResult {
return SwiftResult.error(errorResult.throwable.message ?? errorResult.throwable.description())
}
return SwiftResult.error("Unexpected error converting to SwiftResult")
}
Use moko-kswift
https://github.com/icerockdev/moko-kswift
KSwift is gradle plugin to generate Swift-friendly API for Kotlin/Native framework.
Note: at the moment, all subclasses in a sealed class must be nested, otherwise the
generation fails.
Generics sealed classes
Definitive solution
public enum KMMResultKs<Value : AnyObject> {
case successKMMResult(KMMResultSuccessKMMResult<Value>)
case errorKMMResult(KMMResultErrorKMMResult<Value>)
public var sealed: KMMResult<Value> {
switch self {
case .successKMMResult(let obj):
return obj as shared.KMMResult<Value>
case .errorKMMResult(let obj):
return obj as shared.KMMResult<Value>
}
}
public init(_ obj: KMMResult<Value>) {
if let obj = obj as? shared.KMMResultSuccessKMMResult<Value> {
self = .successKMMResult(obj)
} else if let obj = obj as? shared.KMMResultErrorKMMResult<Value> {
self = .errorKMMResult(obj)
} else {
fatalError("KMMResultKs not synchronized with KMMResult class")
}
}
}
Generics sealed classes
Inline class
fun formatFirstAndLastName(firstName: String, lastName: String): String =
"$firstName $lastName"
formatFirstAndLastName("Emanuele", "Papa")
formatFirstAndLastName("Papa", "Emanuele")
Shared code
Inline class
fun formatFirstAndLastName(firstName: FirstName, lastName: LastName): String =
"${firstName.firstName} ${lastName.lastName}"
value class FirstName(val firstName: String)
value class LastName(val lastName: String)
formatFirstAndLastName(
FirstName("John"),
LastName("Doe")
)
Shared code
Inline class
ProfileFormatterKt.formatFirstAndLastName(firstName: <Any>, lastName: <Any>)
KT-32352
iOS
Inline class
fun formatFirstAndLastName(
firstName: FirstNameIos,
lastName: LastNameIos
): String {
return formatFirstAndLastName(FirstName(firstName.firstName), LastName(lastName.lastName))
}
data class FirstNameIos(
val firstName: String
)
data class LastNameIos(
val lastName: String
)
Shared iOS code
Inline class
ProfileFormatterKt.formatFirstAndLastName(
firstName: FirstNameIos(firstName: "John"),
lastName: LastNameIos(lastName: "Doe")
)
iOS code
A coroutine is a concurrency design pattern that you
can use on Android to simplify code that executes
asynchronously.
All of this doesn't exist in ObjectiveC
Coroutines
Coroutines
class CoroutinesProfileFormatter {
suspend fun formatFirstAndLastNameWithCoroutines(
firstName: String,
lastName: String
): String {
delay(1000)
return "$firstName $lastName"
}
}
Shared code
Coroutines
fun onShowMyNameClicked() {
viewModelScope.launch {
mutableState.value = mutableState.value.copy(
formattedName = coroutinesProfileFormatter.formatFirstAndLastNameWithCoroutines(
"John",
"Doe"
)
)
}
}
Coroutines
func onShowMyNameClicked() {
coroutinesProfileFormatter.formatFirstAndLastNameWithCoroutines(
firstName: "John",
lastName: "Doe"
) { formattedName, error in
DispatchQueue.main.async {
self.state.formattedName = formattedName!
}
}
}
Coroutines
https://github.com/rickclephas/KMP-NativeCoroutines
Solution
func onShowMyNameWithCombineClicked() {
let formatFirstAndLastNamePublisher = createPublisher(
for: coroutinesProfileFormatter.formatFirstAndLastNameWithCoroutinesNative(
firstName: "Async",
lastName: "Doe"
))
formatFirstAndLastNamePublisher
.subscribe(on: DispatchQueue.main)
.receive(on: DispatchQueue.main)
.sink { completion in
print("Received completion: (completion)")
} receiveValue: { value in
self.state.formattedNameWithAsync = value
}
}
Coroutines
The new memory manager is just being promoted to Beta and it's enabled by
default from Kotlin 1.7.20.
In the new memory manager (MM), we're lifting restrictions on object sharing:
there's no need to freeze objects to share them between threads anymore.
To enable it in Kotlin < 1.7.20
kotlin.native.binary.memoryModel=experimental
https://github.com/JetBrains/kotlin/blob/master/kotlin-native/NEW_MM.md
Result
fun getRandomDouble(): Result<Double> {
return if(Random.nextBoolean()) {
Result.success(Random.nextDouble())
} else {
Result.failure(Throwable("Can't generate a new double"))
}
}
Shared code
Result
fun onShowRandomDoubleClicked() {
val randomDouble = getRandomDouble()
randomDouble.fold(
onSuccess = { double ->
mutableState.value = mutableState.value.copy(formattedRandomDouble = double.toString())
},
onFailure = { throwable ->
mutableState.value = mutableState.value.copy(formattedRandomDouble = throwable.toString())
})
}
Android
Result
func onShowRandomDoubleClicked() {
let randomDouble = RandomDoubleGeneratorKt.getRandomDouble()
//randomDouble has type Any?
state.formattedRandomDouble = String(describing: randomDouble)
}
iOS
This is another thing which code generation might solve...
Default parameters
KT-38685
Exception handling
Kotlin -> Only unchecked exceptions
Swift -> Only checked errors
Exception specified with @Throws -> Propagated as NSError
Exception without @Throws -> iOS crash
Always catch Exceptions in the shared code and return a Result like class
Pure Kotlin -> ✅
KMM library -> ✅
Custom (expect interface, actual 1 native library for Android, 1 native
library for iOS) -> ✅❌
Third-party libraries
class
SystemInfoRetrieverImpl(
private val systemInfoRetriever: SystemInfoRetrieverNativeWrapper
): SystemInfoRetriever
interface
SystemInfoRetrieverNativeWrapper {
fun getSystemName(): String
}
class
SystemInfoRetrieverAndroid :
SystemInfoRetrieverNativeWrapper
class
SystemInfoRetrieverIOS:
SystemInfoRetrieverNativeWrapper
interface
SystemInfoRetriever {
fun getSystemName(): String
}
Third-party libraries
Use this Gradle plugin to create a Swift Package Manager manifest
and an XCFramework for iOS devs when you create a KMM library
SPM
https://github.com/ge-org/multiplatform-swiftpackage
iOS devs will like:
an Xcode plugin which allows debugging of Kotlin code
running in an iOS application, directly from Xcode.
https://github.com/touchlab/xcode-kotlin
IDEs
Android devs will like:
an IDE similar to IntelliJ IDEA/Android Studio but for
iOS: AppCode!
IDEs
Let's hope most of these issues will be
officially fixed soon...
but in the meanwhile, let's rock with
KMM!
The future
Thank you!
Drop a line to e.papa@zest.one
Get in touch with me
www.emanuelepapa.dev

More Related Content

What's hot (20)

PDF
The aggregate is dead! Long live the aggregate! - SpringIO.pdf
Sara Pellegrini
 
PPTX
Clean Pragmatic Architecture - Avoiding a Monolith
Victor Rentea
 
PDF
React for Dummies
Mitch Chen
 
PDF
Real Life Clean Architecture
Mattia Battiston
 
PPTX
SOLID principles
Jonathan Holloway
 
PPTX
Spring Framework
tola99
 
PPTX
React js - The Core Concepts
Divyang Bhambhani
 
PDF
Data Warehouses in Kubernetes Visualized: the ClickHouse Kubernetes Operator UI
Altinity Ltd
 
PDF
Asynchronous API in Java8, how to use CompletableFuture
José Paumard
 
PDF
Cours design pattern m youssfi partie 5 adapter
ENSET, Université Hassan II Casablanca
 
PPTX
Express JS Middleware Tutorial
Simplilearn
 
PPTX
Clean architecture
.NET Crowd
 
PDF
Clean code
Alvaro García Loaisa
 
PPT
Apache Ant
Vinod Kumar V H
 
PPTX
React js programming concept
Tariqul islam
 
PPTX
JavaScript Promises
L&T Technology Services Limited
 
PDF
Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
Mydbops
 
PPTX
Vertical Slicing Architectures
Victor Rentea
 
PDF
MongoDB performance
Mydbops
 
PPTX
LinkedList vs Arraylist- an in depth look at java.util.LinkedList
Marcus Biel
 
The aggregate is dead! Long live the aggregate! - SpringIO.pdf
Sara Pellegrini
 
Clean Pragmatic Architecture - Avoiding a Monolith
Victor Rentea
 
React for Dummies
Mitch Chen
 
Real Life Clean Architecture
Mattia Battiston
 
SOLID principles
Jonathan Holloway
 
Spring Framework
tola99
 
React js - The Core Concepts
Divyang Bhambhani
 
Data Warehouses in Kubernetes Visualized: the ClickHouse Kubernetes Operator UI
Altinity Ltd
 
Asynchronous API in Java8, how to use CompletableFuture
José Paumard
 
Cours design pattern m youssfi partie 5 adapter
ENSET, Université Hassan II Casablanca
 
Express JS Middleware Tutorial
Simplilearn
 
Clean architecture
.NET Crowd
 
Apache Ant
Vinod Kumar V H
 
React js programming concept
Tariqul islam
 
JavaScript Promises
L&T Technology Services Limited
 
Wars of MySQL Cluster ( InnoDB Cluster VS Galera )
Mydbops
 
Vertical Slicing Architectures
Victor Rentea
 
MongoDB performance
Mydbops
 
LinkedList vs Arraylist- an in depth look at java.util.LinkedList
Marcus Biel
 

Similar to KMM survival guide: how to tackle struggles between Kotlin and Swift (20)

PPTX
Kotlin Coroutines and Rx
Shaul Rosenzwieg
 
PDF
Kotlin for Android - Vali Iorgu - mRready
MobileAcademy
 
PDF
Kotlin wonderland
Jedsada Tiwongvokul
 
PDF
Swift - One step forward from Obj-C
Nissan Tsafrir
 
PDF
Promises - Asynchronous Control Flow
Henrique Barcelos
 
PDF
Meet Elcodi, the flexible e-commerce components built on Symfony2
Aldo Chiecchia
 
PDF
Architectures in the compose world
Fabio Collini
 
PPTX
Ultimate Node.js countdown: the coolest Application Express examples
Alan Arentsen
 
PDF
HTML5 for the Silverlight Guy
David Padbury
 
PPTX
From dot net_to_rails
pythonandchips
 
PDF
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017
Codemotion
 
PDF
Kotlin: forse è la volta buona (Trento)
Davide Cerbo
 
PPTX
Could Virtual Threads cast away the usage of Kotlin Coroutines - DevoxxUK2025
João Esperancinha
 
PDF
Compose로 Android:Desktop 멀티플랫폼 만들기.pdf
ssuserb6c2641
 
PDF
Kotlin advanced - language reference for android developers
Bartosz Kosarzycki
 
PDF
Kotlin Advanced - language reference for Android developers
STX Next
 
PPTX
Compose in Theory
Garth Gilmour
 
PDF
JS class slides (2016)
Yves-Emmanuel Jutard
 
PDF
JS Class 2016
Yves-Emmanuel Jutard
 
PPTX
The State of JavaScript (2015)
Domenic Denicola
 
Kotlin Coroutines and Rx
Shaul Rosenzwieg
 
Kotlin for Android - Vali Iorgu - mRready
MobileAcademy
 
Kotlin wonderland
Jedsada Tiwongvokul
 
Swift - One step forward from Obj-C
Nissan Tsafrir
 
Promises - Asynchronous Control Flow
Henrique Barcelos
 
Meet Elcodi, the flexible e-commerce components built on Symfony2
Aldo Chiecchia
 
Architectures in the compose world
Fabio Collini
 
Ultimate Node.js countdown: the coolest Application Express examples
Alan Arentsen
 
HTML5 for the Silverlight Guy
David Padbury
 
From dot net_to_rails
pythonandchips
 
Davide Cerbo - Kotlin: forse è la volta buona - Codemotion Milan 2017
Codemotion
 
Kotlin: forse è la volta buona (Trento)
Davide Cerbo
 
Could Virtual Threads cast away the usage of Kotlin Coroutines - DevoxxUK2025
João Esperancinha
 
Compose로 Android:Desktop 멀티플랫폼 만들기.pdf
ssuserb6c2641
 
Kotlin advanced - language reference for android developers
Bartosz Kosarzycki
 
Kotlin Advanced - language reference for Android developers
STX Next
 
Compose in Theory
Garth Gilmour
 
JS class slides (2016)
Yves-Emmanuel Jutard
 
JS Class 2016
Yves-Emmanuel Jutard
 
The State of JavaScript (2015)
Domenic Denicola
 
Ad

More from Commit University (20)

PDF
Accessibilità ed equità digitale: un impegno, non una scelta
Commit University
 
PDF
GitHub Copilot:vediamo chi comanda - Commit University.pdf
Commit University
 
PDF
Contract Driven Development - Branch 2024.pdf
Commit University
 
PPTX
Cybersecurity & AI: Illusioni e Speranze
Commit University
 
PDF
Migliorare la Developer Experience in un mondo Cloud Native
Commit University
 
PPTX
Scopri come sfruttare la potenza della Hybrid RAG
Commit University
 
PDF
Introduzione a AWS Forecast e SageMaker DeepAR: Prevedere la Domanda con il M...
Commit University
 
PDF
Oltre l'hype: vulnerabilità e limiti dell'intelligenza artificiale.pdf
Commit University
 
PPTX
Alla scoperta dei Vector Database e dei RAG
Commit University
 
PDF
Nell’iperspazio con Rocket: il Framework Web di Rust!
Commit University
 
PDF
Crea il tuo assistente AI con lo Stregatto (open source python framework)
Commit University
 
PDF
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Commit University
 
PDF
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Commit University
 
PDF
Slide-10years.pdf
Commit University
 
PDF
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Commit University
 
PDF
Vue.js slots.pdf
Commit University
 
PPTX
Commit - Qwik il framework che ti stupirà.pptx
Commit University
 
PPTX
Sviluppare da zero una Angular Web App per la PA
Commit University
 
PDF
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Commit University
 
PDF
Prisma the ORM that node was waiting for
Commit University
 
Accessibilità ed equità digitale: un impegno, non una scelta
Commit University
 
GitHub Copilot:vediamo chi comanda - Commit University.pdf
Commit University
 
Contract Driven Development - Branch 2024.pdf
Commit University
 
Cybersecurity & AI: Illusioni e Speranze
Commit University
 
Migliorare la Developer Experience in un mondo Cloud Native
Commit University
 
Scopri come sfruttare la potenza della Hybrid RAG
Commit University
 
Introduzione a AWS Forecast e SageMaker DeepAR: Prevedere la Domanda con il M...
Commit University
 
Oltre l'hype: vulnerabilità e limiti dell'intelligenza artificiale.pdf
Commit University
 
Alla scoperta dei Vector Database e dei RAG
Commit University
 
Nell’iperspazio con Rocket: il Framework Web di Rust!
Commit University
 
Crea il tuo assistente AI con lo Stregatto (open source python framework)
Commit University
 
Breaking REST Chains_ A Fastify & Mercurius Pathway to GraphQL Glory.pdf
Commit University
 
Accelerating API Development: A Pit Stop with Gin-Gonic in Golang-Slide.pdf
Commit University
 
Slide-10years.pdf
Commit University
 
Collaborazione, Decisionalità e Gestione della Complessità nel Tempo: cosa ...
Commit University
 
Vue.js slots.pdf
Commit University
 
Commit - Qwik il framework che ti stupirà.pptx
Commit University
 
Sviluppare da zero una Angular Web App per la PA
Commit University
 
Backstage l'Internal Developer Portal Open Source per una migliore Developer ...
Commit University
 
Prisma the ORM that node was waiting for
Commit University
 
Ad

Recently uploaded (20)

PPTX
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
PDF
Smart Trailers 2025 Update with History and Overview
Paul Menig
 
PDF
Exolore The Essential AI Tools in 2025.pdf
Srinivasan M
 
PDF
LLMs.txt: Easily Control How AI Crawls Your Site
Keploy
 
PDF
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
PDF
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
PDF
Presentation - Vibe Coding The Future of Tech
yanuarsinggih1
 
PDF
Transcript: New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
PDF
CIFDAQ Token Spotlight for 9th July 2025
CIFDAQ
 
PDF
Chris Elwell Woburn, MA - Passionate About IT Innovation
Chris Elwell Woburn, MA
 
PDF
DevBcn - Building 10x Organizations Using Modern Productivity Metrics
Justin Reock
 
PPTX
UiPath Academic Alliance Educator Panels: Session 2 - Business Analyst Content
DianaGray10
 
PDF
Timothy Rottach - Ramp up on AI Use Cases, from Vector Search to AI Agents wi...
AWS Chicago
 
PDF
HCIP-Data Center Facility Deployment V2.0 Training Material (Without Remarks ...
mcastillo49
 
PDF
From Code to Challenge: Crafting Skill-Based Games That Engage and Reward
aiyshauae
 
PPTX
WooCommerce Workshop: Bring Your Laptop
Laura Hartwig
 
PDF
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
PDF
[Newgen] NewgenONE Marvin Brochure 1.pdf
darshakparmar
 
PPTX
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
PDF
New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
AI Penetration Testing Essentials: A Cybersecurity Guide for 2025
defencerabbit Team
 
Smart Trailers 2025 Update with History and Overview
Paul Menig
 
Exolore The Essential AI Tools in 2025.pdf
Srinivasan M
 
LLMs.txt: Easily Control How AI Crawls Your Site
Keploy
 
"AI Transformation: Directions and Challenges", Pavlo Shaternik
Fwdays
 
Agentic AI lifecycle for Enterprise Hyper-Automation
Debmalya Biswas
 
Presentation - Vibe Coding The Future of Tech
yanuarsinggih1
 
Transcript: New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 
CIFDAQ Token Spotlight for 9th July 2025
CIFDAQ
 
Chris Elwell Woburn, MA - Passionate About IT Innovation
Chris Elwell Woburn, MA
 
DevBcn - Building 10x Organizations Using Modern Productivity Metrics
Justin Reock
 
UiPath Academic Alliance Educator Panels: Session 2 - Business Analyst Content
DianaGray10
 
Timothy Rottach - Ramp up on AI Use Cases, from Vector Search to AI Agents wi...
AWS Chicago
 
HCIP-Data Center Facility Deployment V2.0 Training Material (Without Remarks ...
mcastillo49
 
From Code to Challenge: Crafting Skill-Based Games That Engage and Reward
aiyshauae
 
WooCommerce Workshop: Bring Your Laptop
Laura Hartwig
 
Newgen 2022-Forrester Newgen TEI_13 05 2022-The-Total-Economic-Impact-Newgen-...
darshakparmar
 
[Newgen] NewgenONE Marvin Brochure 1.pdf
darshakparmar
 
Q2 FY26 Tableau User Group Leader Quarterly Call
lward7
 
New from BookNet Canada for 2025: BNC BiblioShare - Tech Forum 2025
BookNet Canada
 

KMM survival guide: how to tackle struggles between Kotlin and Swift

  • 1. KMM Survival Guide: how to tackle everyday struggles between Kotlin and Swift Emanuele Papa Commit University November 2022
  • 2. Working at Zest One in Chiasso, Switzerland Who I am? Emanuele Papa, Android Developer Find me at www.emanuelepapa.dev
  • 3. What is KMM? Kotlin Multiplatform Mobile is an SDK for iOS and Android app development. It offers all the combined benefits of creating cross-platform and native apps.
  • 4. Shared module: you write Kotlin code as you are used to do. Android module: you see no difference and just use the shared module code iOS project: using Kotlin Native, the code is compiled into an AppleFramework like it was written in Objective-C. Then you can just use it in the same way as when you import an iOS dependency. How does it work?
  • 5. Swift and Kotlin are both modern languages and have a lot of syntax similarities Swift is like Kotlin
  • 6. Everything looks cool! I can become an iOS developer! (or I can become an Android developer!) Expectation
  • 7. Not everything on KMM works out of the box. Unfortunately, most of the issues arise because the Kotlin code is transformed into ObjectiveC code. Reality
  • 8. KMM directly supports Swift interoperability (KT-49521) Write shared code in a slightly different way to better support iOS development (and iOS developers) Solutions
  • 9. Primitive types fun getRandomInt(): Int = Random.nextInt(until = 10) Shared code
  • 10. Primitive types val randomInt: Int = getRandomInt() Android
  • 11. Primitive types let randomInt: Int32 = RandomNumberGeneratorKt.getRandomInt() iOS
  • 12. Sealed classes sealed class KMMIntResult data class SuccessKMMIntResult( val value: Int ) : KMMIntResult() data class ErrorKMMIntResult( val throwable: Throwable ) : KMMIntResult() Shared code
  • 13. Sealed classes fun getRandomIntWrappedInIntResult(): KMMIntResult { val isSuccess = Random.nextBoolean() return if(isSuccess) { SuccessKMMIntResult(Random.nextInt(until = 10)) } else { ErrorKMMIntResult(RuntimeException("There was an error, Int not generated")) } } Shared code
  • 14. Sealed classes val randomInt: KMMIntResult = getRandomIntWrappedInIntResult() val randomIntText: String = when (randomInt) { is KMMIntResult.ErrorKMMIntResult -> { "Error: ${randomInt.throwable.message}" } is KMMIntResult.SuccessKMMIntResult -> { "Success: ${randomInt.value}" } } Android
  • 15. Sealed classes let randomInt: KMMIntResult = RandomNumberGeneratorKt.getRandomIntWrappedInIntResult() let randomIntText: String switch randomInt { case let error as KMMIntResult.ErrorKMMIntResult: randomIntText = "Error: (error.throwable.message ?? error.throwable.description())" case let success as KMMIntResult.SuccessKMMIntResult: randomIntText = "Success: (success.value)" default: randomIntText = "This never happens" } iOS
  • 16. Generic sealed class sealed class KMMResult<out Value> data class SuccessKMMResult<Value>( val value: Value ): KMMResult<Value>() data class ErrorKMMResult( val throwable: Throwable ): KMMResult<Nothing>() Shared code
  • 17. Generic sealed class fun getRandomIntWrappedInResult(): KMMResult<Int> { val isSuccess = Random.nextBoolean() return if(isSuccess) { SuccessKMMResult(Random.nextInt(until = 10)) } else { ErrorKMMResult(RuntimeException("There was an error, Int not generated")) } } Shared code
  • 18. Generic sealed class Android val randomInt: KMMResult<Int> = getRandomIntWrappedInResult() val randomIntText: String = when (randomInt) { is KMMResult.ErrorKMMResult -> { "Error: ${randomInt.throwable.message}" } is KMMResult.SuccessKMMResult -> { "Success: ${randomInt.value}" } }
  • 19. Generic sealed class iOS let randomInt: KMMResult<KotlinInt> = RandomNumberGeneratorKt.getRandomIntWrappedInIntResult() let randomIntText: String switch randomInt { case let error as KMMResultErrorKMMResult: randomIntText = "Error: (error.throwable.message ?? error.throwable.description())" case let success as KMMResultSuccessKMMResult<KotlinInt>: randomIntText = "Success: (success.value)" default: randomIntText = "This never happens" }
  • 20. Generics sealed class First solution data class ErrorKMMResult( val throwable: Throwable ): KMMResult<Nothing>() data class ErrorKMMResult<Value>( val throwable: Throwable ): KMMResult<Value>() case let error as KMMResultErrorKMMResult<KotlinInt>: case let error as KMMResultErrorKMMResult:
  • 21. Generics sealed class Second solution enum SwiftResult<Value> { case error(String) case success(Value) }
  • 22. Generics sealed class Second solution func toSwiftResult<Value>(kmmResult: KMMResult<Value>) -> SwiftResult<Value> { if let successResult = kmmResult as? KMMResultSuccessKMMResult<Value> { return SwiftResult.success(successResult.value!) } if let errorResult = kmmResult as? KMMResultErrorKMMResult { return SwiftResult.error(errorResult.throwable.message ?? errorResult.throwable.description()) } return SwiftResult.error("Unexpected error converting to SwiftResult") }
  • 23. Use moko-kswift https://github.com/icerockdev/moko-kswift KSwift is gradle plugin to generate Swift-friendly API for Kotlin/Native framework. Note: at the moment, all subclasses in a sealed class must be nested, otherwise the generation fails. Generics sealed classes Definitive solution
  • 24. public enum KMMResultKs<Value : AnyObject> { case successKMMResult(KMMResultSuccessKMMResult<Value>) case errorKMMResult(KMMResultErrorKMMResult<Value>) public var sealed: KMMResult<Value> { switch self { case .successKMMResult(let obj): return obj as shared.KMMResult<Value> case .errorKMMResult(let obj): return obj as shared.KMMResult<Value> } } public init(_ obj: KMMResult<Value>) { if let obj = obj as? shared.KMMResultSuccessKMMResult<Value> { self = .successKMMResult(obj) } else if let obj = obj as? shared.KMMResultErrorKMMResult<Value> { self = .errorKMMResult(obj) } else { fatalError("KMMResultKs not synchronized with KMMResult class") } } } Generics sealed classes
  • 25. Inline class fun formatFirstAndLastName(firstName: String, lastName: String): String = "$firstName $lastName" formatFirstAndLastName("Emanuele", "Papa") formatFirstAndLastName("Papa", "Emanuele") Shared code
  • 26. Inline class fun formatFirstAndLastName(firstName: FirstName, lastName: LastName): String = "${firstName.firstName} ${lastName.lastName}" value class FirstName(val firstName: String) value class LastName(val lastName: String) formatFirstAndLastName( FirstName("John"), LastName("Doe") ) Shared code
  • 28. Inline class fun formatFirstAndLastName( firstName: FirstNameIos, lastName: LastNameIos ): String { return formatFirstAndLastName(FirstName(firstName.firstName), LastName(lastName.lastName)) } data class FirstNameIos( val firstName: String ) data class LastNameIos( val lastName: String ) Shared iOS code
  • 29. Inline class ProfileFormatterKt.formatFirstAndLastName( firstName: FirstNameIos(firstName: "John"), lastName: LastNameIos(lastName: "Doe") ) iOS code
  • 30. A coroutine is a concurrency design pattern that you can use on Android to simplify code that executes asynchronously. All of this doesn't exist in ObjectiveC Coroutines
  • 31. Coroutines class CoroutinesProfileFormatter { suspend fun formatFirstAndLastNameWithCoroutines( firstName: String, lastName: String ): String { delay(1000) return "$firstName $lastName" } } Shared code
  • 32. Coroutines fun onShowMyNameClicked() { viewModelScope.launch { mutableState.value = mutableState.value.copy( formattedName = coroutinesProfileFormatter.formatFirstAndLastNameWithCoroutines( "John", "Doe" ) ) } }
  • 33. Coroutines func onShowMyNameClicked() { coroutinesProfileFormatter.formatFirstAndLastNameWithCoroutines( firstName: "John", lastName: "Doe" ) { formattedName, error in DispatchQueue.main.async { self.state.formattedName = formattedName! } } }
  • 34. Coroutines https://github.com/rickclephas/KMP-NativeCoroutines Solution func onShowMyNameWithCombineClicked() { let formatFirstAndLastNamePublisher = createPublisher( for: coroutinesProfileFormatter.formatFirstAndLastNameWithCoroutinesNative( firstName: "Async", lastName: "Doe" )) formatFirstAndLastNamePublisher .subscribe(on: DispatchQueue.main) .receive(on: DispatchQueue.main) .sink { completion in print("Received completion: (completion)") } receiveValue: { value in self.state.formattedNameWithAsync = value } }
  • 35. Coroutines The new memory manager is just being promoted to Beta and it's enabled by default from Kotlin 1.7.20. In the new memory manager (MM), we're lifting restrictions on object sharing: there's no need to freeze objects to share them between threads anymore. To enable it in Kotlin < 1.7.20 kotlin.native.binary.memoryModel=experimental https://github.com/JetBrains/kotlin/blob/master/kotlin-native/NEW_MM.md
  • 36. Result fun getRandomDouble(): Result<Double> { return if(Random.nextBoolean()) { Result.success(Random.nextDouble()) } else { Result.failure(Throwable("Can't generate a new double")) } } Shared code
  • 37. Result fun onShowRandomDoubleClicked() { val randomDouble = getRandomDouble() randomDouble.fold( onSuccess = { double -> mutableState.value = mutableState.value.copy(formattedRandomDouble = double.toString()) }, onFailure = { throwable -> mutableState.value = mutableState.value.copy(formattedRandomDouble = throwable.toString()) }) } Android
  • 38. Result func onShowRandomDoubleClicked() { let randomDouble = RandomDoubleGeneratorKt.getRandomDouble() //randomDouble has type Any? state.formattedRandomDouble = String(describing: randomDouble) } iOS
  • 39. This is another thing which code generation might solve... Default parameters KT-38685
  • 40. Exception handling Kotlin -> Only unchecked exceptions Swift -> Only checked errors Exception specified with @Throws -> Propagated as NSError Exception without @Throws -> iOS crash Always catch Exceptions in the shared code and return a Result like class
  • 41. Pure Kotlin -> ✅ KMM library -> ✅ Custom (expect interface, actual 1 native library for Android, 1 native library for iOS) -> ✅❌ Third-party libraries
  • 42. class SystemInfoRetrieverImpl( private val systemInfoRetriever: SystemInfoRetrieverNativeWrapper ): SystemInfoRetriever interface SystemInfoRetrieverNativeWrapper { fun getSystemName(): String } class SystemInfoRetrieverAndroid : SystemInfoRetrieverNativeWrapper class SystemInfoRetrieverIOS: SystemInfoRetrieverNativeWrapper interface SystemInfoRetriever { fun getSystemName(): String } Third-party libraries
  • 43. Use this Gradle plugin to create a Swift Package Manager manifest and an XCFramework for iOS devs when you create a KMM library SPM https://github.com/ge-org/multiplatform-swiftpackage
  • 44. iOS devs will like: an Xcode plugin which allows debugging of Kotlin code running in an iOS application, directly from Xcode. https://github.com/touchlab/xcode-kotlin IDEs
  • 45. Android devs will like: an IDE similar to IntelliJ IDEA/Android Studio but for iOS: AppCode! IDEs
  • 46. Let's hope most of these issues will be officially fixed soon... but in the meanwhile, let's rock with KMM! The future
  • 48. Drop a line to [email protected] Get in touch with me www.emanuelepapa.dev