Slide 1

Slide 1 text

Troubled Waters: Bridging platform-native SDKs with Kotlin Multiplatform Kevin Galligan

Slide 2

Slide 2 text

Partner at Kotlin GDE

Slide 3

Slide 3 text

Touchlab

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

Common JVM JS Native

Slide 6

Slide 6 text

Common JVM JS Native Firebase/JVM Firebase/JS Firebase/iOS

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Common JVM JS Native Firebase/JVM Firebase/JS Firebase/iOS

Slide 9

Slide 9 text

Common Android iOS Firebase/JVM Firebase/iOS

Slide 10

Slide 10 text

JNI

Slide 11

Slide 11 text

Basic Topics • Tell Kotlin about the native code • Link native code into Kotlin Xcode Framework • API Design

Slide 12

Slide 12 text

Simple First pass it in

Slide 13

Slide 13 text

Interface Driven • 💰 Don’t need cinterop • 💰 No weird linking • 💰 No extra binary size • 🗑 Not easily testable • 🗑 Impractical for complex situations • 🗑 Bad for libraries

Slide 14

Slide 14 text

interface KotlinAnalytics { fun logEvent(name: String, parameters: Map) }

Slide 15

Slide 15 text

interface KotlinAnalytics { fun logEvent(name: String, parameters: Map) } fun initKoinIos( userDefaults: NSUserDefaults, appInfo: AppInfo, analytics: KotlinAnalytics, doOnStartup: () -> Unit ) ( // Etc )

Slide 16

Slide 16 text

class IosAnalytics: KotlinAnalytics { func logEvent(name: String, parameters: [String : Any]) { Analytics.logEvent(name, parameters: parameters) } }

Slide 17

Slide 17 text

class IosAnalytics: KotlinAnalytics { func logEvent(name: String, parameters: [String : Any]) { Analytics.logEvent(name, parameters: parameters) } } KoinIOSKt.doInitKoinIos( userDefaults: userDefaults, appInfo: iosAppInfo, analytics: IosAnalytics(), doOnStartup: doOnStartup )

Slide 18

Slide 18 text

fun withCallback(callback: (Breed) -> Unit) { }

Slide 19

Slide 19 text

viewModel.withCallback(callback: {[weak self] (breed:Breed) -> Void in self?.callMe(breed: breed) })

Slide 20

Slide 20 text

cinterop kotlin calling c/objc

Slide 21

Slide 21 text

cinterop kotlin calling c/objc

Slide 22

Slide 22 text

Call C/Obj-C some swift, some C++

Slide 23

Slide 23 text

*.h cinterop Kotlin/iOS

Slide 24

Slide 24 text

*.h cinterop Kotlin/iOS Binary ? ? ? ? ? ? ? ? ?? ? ?

Slide 25

Slide 25 text

package = co.touchlab.whatever language = Objective-C headers = whatever.h whatever.def

Slide 26

Slide 26 text

other config

Slide 27

Slide 27 text

package = co.touchlab.whatever language = Objective-C headers = whatever.h whatever.def

Slide 28

Slide 28 text

extraOpts = listOf("-mode", "sourcecode")

Slide 29

Slide 29 text

*.h cinterop Kotlin/iOS Binary ? ? ? ? ? ? ? ? ?? ? ?

Slide 30

Slide 30 text

Where are these *.h files? multiple options

Slide 31

Slide 31 text

In Your Repo copy/paste

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

Script Downloads grab headers

Slide 34

Slide 34 text

Truncated Headers feels like cheating a bit :)

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Cocoapods Dependency the hammer approach

Slide 37

Slide 37 text

kotlin { //etc cocoapods { summary = "Common library for the KaMP starter kit" homepage = "https://github.com/touchlab/KaMPKit" ios.deploymentTarget = "12.4" podfile = project.file("../ios/Podfile") pod("FirebaseAnalytics") } }

Slide 38

Slide 38 text

Docs on Cocoapods/Kotlin https://kotlinlang.org/docs/native-cocoapods-libraries.html

Slide 39

Slide 39 text

cocoapods.FirebaseAnalytics .FIRAnalytics.logEventWithName("someevent", mapOf(/*data*/))

Slide 40

Slide 40 text

Makes project weird can’t run tests, dependency in podspec

Slide 41

Slide 41 text

Carthage ❤ Kotlin https://github.com/wireapp/carthage-gradle-plugin

Slide 42

Slide 42 text

SPM? not yet

Slide 43

Slide 43 text

Linking where is the binary?

Slide 44

Slide 44 text

*.h cinterop Kotlin/iOS Binary ? ? ? ? ? ? ? ? ?? ? ?

Slide 45

Slide 45 text

complex-ish

Slide 46

Slide 46 text

No content

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

Linking Basics Binary Framework

Slide 49

Slide 49 text

Static Linking Binary Static Framework

Slide 50

Slide 50 text

Dynamic Linking Binary Dynamic Framework

Slide 51

Slide 51 text

Assemble Static Static Framework *.h cinterop Kotlin/iOS Binary ? ? ? ? ? ? ? ??? ? ?

Slide 52

Slide 52 text

Assemble Dynamic *.h cinterop Kotlin/iOS Binary ? ? ? ? ? ? ? ??? ? ? Dynamic Framework

Slide 53

Slide 53 text

Libraries == More Complex recency bias

Slide 54

Slide 54 text

Kermit Crashlytics Firebase.h Firebase

Slide 55

Slide 55 text

Kermit Crashlytics Firebase.h Firebase Dynamic

Slide 56

Slide 56 text

Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_FIRStackFrame", referenced from: objc-class-ref in libco.touchlab:kermit-crashlytics-cache.a(result.o) "_OBJC_CLASS_$_FIRExceptionModel", referenced from: objc-class-ref in libco.touchlab:kermit-crashlytics-cache.a(result.o) "_OBJC_CLASS_$_FIRCrashlytics", referenced from: objc-class-ref in libco.touchlab:kermit-crashlytics-cache.a(result.o) ld: symbol(s) not found for architecture x86_64

Slide 57

Slide 57 text

Undefined symbols for architecture x86_64: "_OBJC_CLASS_$_FIRStackFrame", referenced from: objc-class-ref in libco.touchlab:kermit-crashlytics-cache.a(result.o) "_OBJC_CLASS_$_FIRExceptionModel", referenced from: objc-class-ref in libco.touchlab:kermit-crashlytics-cache.a(result.o) "_OBJC_CLASS_$_FIRCrashlytics", referenced from: objc-class-ref in libco.touchlab:kermit-crashlytics-cache.a(result.o) ld: symbol(s) not found for architecture x86_64

Slide 58

Slide 58 text

stay one lesson ahead…

Slide 59

Slide 59 text

Kotlin/iOS Cocoapods *.h Binary

Slide 60

Slide 60 text

Kotlin/iOS Some Code *.h *.c/*.m

Slide 61

Slide 61 text

Kotlin/iOS *.h *.c/*.m cinterop clang

Slide 62

Slide 62 text

Kotlin/iOS *.h *.c/*.m cinterop

Slide 63

Slide 63 text

Kotlin/iOS *.h *.c/*.m cinterop cklib

Slide 64

Slide 64 text

cklib Links • https://github.com/touchlab/cklib • https://github.com/cashapp/zipline/blob/trunk/ zipline/build.gradle.kts#L157 • https://github.com/touchlab/Kermit/blob/main/ kermit-crashlytics-test/build.gradle.kts#L69

Slide 65

Slide 65 text

Linking Links • https://bpoplauschi.github.io/2021/10/24/Intro-to-static-and-dynamic- libraries-frameworks.html • https://bpoplauschi.github.io/2021/10/25/Advanced-static-vs-dynamic- libraries-and-frameworks.html

Slide 66

Slide 66 text

Testing Binary Framework

Slide 67

Slide 67 text

Kotlin/iOS test.kexe

Slide 68

Slide 68 text

Kotlin/iOS test.kexe Cocoapods *.h Binary Some Code *.h *.c/*.m

Slide 69

Slide 69 text

Kotlin/iOS test.kexe *.h Binary Firebase.h Firebase

Slide 70

Slide 70 text

Kotlin/iOS test.kexe *.h Binary Firebase.h Firebase Stub Binary

Slide 71

Slide 71 text

No content

Slide 72

Slide 72 text

Kotlin/iOS test.kexe *.h Binary Firebase.h Firebase Stub Binary

Slide 73

Slide 73 text

stay one lesson ahead…

Slide 74

Slide 74 text

API Design thoughts on the common language

Slide 75

Slide 75 text

Uncanny Valley they do the same thing, but…

Slide 76

Slide 76 text

Common Android iOS Firebase/JVM Firebase/iOS

Slide 77

Slide 77 text

Common Android iOS Firebase/JVM Firebase/iOS

Slide 78

Slide 78 text

Common Android iOS C code

Slide 79

Slide 79 text

Common Android iOS C code JNI cinterop bridge

Slide 80

Slide 80 text

Common Android iOS C code JNI cinterop bridge

Slide 81

Slide 81 text

Rules are difficult “it depends” is everything

Slide 82

Slide 82 text

Don’t default expect/actual can be good, can be frustrating

Slide 83

Slide 83 text

expect/actual is less flexible harder to test, etc

Slide 84

Slide 84 text

expect class KotlinAnalytics() { fun logEvent(name: String, parameters: Map) }

Slide 85

Slide 85 text

interface KotlinAnalytics { fun logEvent(name: String, parameters: Map) } expect fun platformAnalyticsFactory(): KotlinAnalytics

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

expect fun platformLogWriter(): LogWriter

Slide 88

Slide 88 text

expect class CrashlyticsLogWriter( minSeverity: Severity = Severity.Info, minCrashSeverity: Severity = Severity.Warn, printTag: Boolean = true ) : LogWriter

Slide 89

Slide 89 text

expect fun crashlyticsLogWriter( minSeverity: Severity = Severity.Info, minCrashSeverity: Severity = Severity.Warn, printTag: Boolean = true ) : LogWriter

Slide 90

Slide 90 text

Service Objects/Small Graph interface and delegates work well

Slide 91

Slide 91 text

Parent<->Child/Larger Graph interface-with-delegates starts looking ugly

Slide 92

Slide 92 text

Order Big Graph/Data Objects getProducts() Product Product Product Supplier

Slide 93

Slide 93 text

Order Big Graph/Data Objects getProducts() Product Product Product Supplier

Slide 94

Slide 94 text

actual typealias with great power…

Slide 95

Slide 95 text

/** * Multiplatform AtomicInt implementation */ expect class AtomicInt(initialValue: Int) { fun get(): Int fun set(newValue: Int) fun incrementAndGet(): Int fun decrementAndGet(): Int fun addAndGet(delta: Int): Int fun compareAndSet(expected: Int, new: Int): Boolean }

Slide 96

Slide 96 text

JVM Side?

Slide 97

Slide 97 text

import java.util.concurrent.atomic.AtomicInteger actual typealias AtomicInt = AtomicInteger

Slide 98

Slide 98 text

Actual needs to match because obviously it does

Slide 99

Slide 99 text

import kotlin.native.concurrent.AtomicInt actual class AtomicInt actual constructor(initialValue:Int){ private val atom = AtomicInt(initialValue) actual fun get(): Int = atom.value actual fun set(newValue: Int) { atom.value = newValue } actual fun incrementAndGet(): Int = atom.addAndGet(1) actual fun decrementAndGet(): Int = atom.addAndGet(-1) actual fun addAndGet(delta: Int): Int = atom.addAndGet(delta) actual fun compareAndSet(expected: Int, new: Int): Boolean = atom.compareAndSet(expected, new) }

Slide 100

Slide 100 text

import kotlin.native.concurrent.AtomicInt actual class AtomicInt actual constructor(initialValue:Int){ private val atom = AtomicInt(initialValue) actual fun get(): Int = atom.value actual fun set(newValue: Int) { atom.value = newValue } actual fun incrementAndGet(): Int = atom.addAndGet(1) actual fun decrementAndGet(): Int = atom.addAndGet(-1) actual fun addAndGet(delta: Int): Int = atom.addAndGet(delta) actual fun compareAndSet(expected: Int, new: Int): Boolean = atom.compareAndSet(expected, new) }

Slide 101

Slide 101 text

Platform Affinity android iOS ?

Slide 102

Slide 102 text

Platform Affinity android iOS android-y

Slide 103

Slide 103 text

typealias can be brittle

Slide 104

Slide 104 text

typealias can be brittle

Slide 105

Slide 105 text

Platform Affinity android iOS android-y JS

Slide 106

Slide 106 text

Parallel Delegates android iOS android-y

Slide 107

Slide 107 text

Empty Typealias everything is extensions

Slide 108

Slide 108 text

expect class QuerySnapshot expect val QuerySnapshot.documentChanges_:List expect fun QuerySnapshot.getDocumentChanges_(…):List expect val QuerySnapshot.documents_:List expect val QuerySnapshot.metadata: SnapshotMetadata expect val QuerySnapshot.query: Query expect val QuerySnapshot.empty: Boolean expect val QuerySnapshot.size: Int

Slide 109

Slide 109 text

expect class QuerySnapshot expect val QuerySnapshot.documentChanges_:List expect fun QuerySnapshot.getDocumentChanges_(…):List expect val QuerySnapshot.documents_:List expect val QuerySnapshot.metadata: SnapshotMetadata expect val QuerySnapshot.query: Query expect val QuerySnapshot.empty: Boolean expect val QuerySnapshot.size: Int

Slide 110

Slide 110 text

actual typealias QuerySnapshot = FIRQuerySnapshot actual val QuerySnapshot.documentChanges_: List get() = documentChanges as List actual val QuerySnapshot.documents_: List get() = documents as List actual fun QuerySnapshot.getDocumentChanges_(metadataChanges: Metadata documentChangesWithIncludeMetadataChanges(metadataChanges == Metad iOS

Slide 111

Slide 111 text

actual typealias QuerySnapshot = FIRQuerySnapshot actual val QuerySnapshot.documentChanges_: List get() = documentChanges as List actual val QuerySnapshot.documents_: List get() = documents as List actual fun QuerySnapshot.getDocumentChanges_(metadataChanges: Metadata documentChangesWithIncludeMetadataChanges(metadataChanges == Metad iOS

Slide 112

Slide 112 text

actual typealias QuerySnapshot = FIRQuerySnapshot actual val QuerySnapshot.documentChanges_: List get() = documentChanges as List actual val QuerySnapshot.documents_: List get() = documents as List actual fun QuerySnapshot.getDocumentChanges_(metadataChanges: Metadata documentChangesWithIncludeMetadataChanges(metadataChanges == Metad iOS

Slide 113

Slide 113 text

expect open class DocumentSnapshot DocumentSnapshot actual typealias DocumentSnapshot = FIRDocumentSnapshot iOS common actual typealias DocumentSnapshot = com.google.firebase.firestore.DocumentSnapshot Android

Slide 114

Slide 114 text

actual typealias QuerySnapshot = com.google.firebase.firestore.QuerySn actual val QuerySnapshot.documentChanges_: List get() = documentChanges actual val QuerySnapshot.documents_: List get() = documents actual fun QuerySnapshot.getDocumentChanges_(metadataChanges: Metadata getDocumentChanges(metadataChanges.toJvm()) Android

Slide 115

Slide 115 text

Platform Affinity

Slide 116

Slide 116 text

Platform Agnostic

Slide 117

Slide 117 text

Order Big Graph/Data Objects getProducts() Product Product Product Supplier

Slide 118

Slide 118 text

Order Big Graph/Data Objects getProducts() Product Product Product Supplier

Slide 119

Slide 119 text

Again, that’s rare mostly just use interfaces and delegates!

Slide 120

Slide 120 text

Empty Typealias • https://vimeo.com/371460823 • https://github.com/touchlab-lab/FirestoreKMP

Slide 121

Slide 121 text

No content

Slide 122

Slide 122 text

API *almost* designs itself you’re mapping existing APIs

Slide 123

Slide 123 text

Iterative Design tweak for platform specifics

Slide 124

Slide 124 text

Platform-Specific Init then common

Slide 125

Slide 125 text

Whatever/ Common Whatever/ Android Whatever/ iOS Whatever AAR Whatever.h Init Init

Slide 126

Slide 126 text

Whatever/ Common Whatever/ Android Whatever/ iOS Whatever AAR Whatever.h Init Init Other Calls

Slide 127

Slide 127 text

Simple First pass it in

Slide 128

Slide 128 text

That was a lot! it’s not all that bad

Slide 129

Slide 129 text

At Touchlab • Big team build tools • Better iOS Dev ex • Swift code generation • Other things

Slide 130

Slide 130 text

If you’re facing these issues… @kpgalligan Kotlin Slack

Slide 131

Slide 131 text

Thanks! @kpgalligan

Slide 132

Slide 132 text

Thanks! @kpgalligan Join the team !