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

Kotlin Multiplatform Intro

Kotlin Multiplatform Intro

Introduction to Kotlin Multiplatform, at Oredev.

Kevin Galligan

November 06, 2019
Tweet

More Decks by Kevin Galligan

Other Decks in Programming

Transcript

  1. Kotlin Multiplatform Intro Kevin Galligan

  2. Touchlab

  3. None
  4. What is Kotlin Multiplatform?

  5. kot·lin mul·ti·plat·form /ˌkätˈlin məltiˈplatfôrm,ˌkätˈlin məltīˈplatfôrm/ noun noun: kotlin multiplatform 1.optional,

    natively-integrated, open-source, code sharing platform, based on the popular, modern language kotlin. facilitates non-ui logic availability on many platforms.
  6. kot·lin mul·ti·plat·form /ˌkätˈlin məltiˈplatfôrm,ˌkätˈlin məltīˈplatfôrm/ noun noun: kotlin multiplatform 1.optional,

    natively-integrated, open-source, code sharing platform, based on the popular, modern language kotlin. facilitates non-ui logic availability on many platforms. Oh, and JetBrains!
  7. Optional Sharing Low risk. No Big Decisions.

  8. Natively Integrated smooth interop

  9. open source

  10. Code Sharing not “cross platform”

  11. popular

  12. popular

  13. popular

  14. modern

  15. Not UI well, not necessarily UI

  16. -Kevin Galligan “Shared UI is a history of pain and

    failure. Shared logic is the history of computers.”
  17. Many Platforms

  18. Common JVM JS Native iOS Mac Linux Windows Android/NDK Wasm

    Others… Java-6 Java-8 Android Browser Node
  19. IDE and Tooling JetBrains makes it

  20. Why Kotlin? High Efficiency Low Risk Modern Language/Tools Highly Engaged

    Community
  21. mobile is easy

  22. mobile is easy

  23. Technically, Though what is it?

  24. Common JVM JS Native

  25. Common JVM JS Native iOS Mac Linux Windows Android/NDK Wasm

    Others… Java-6 Java-8 Android Browser Node
  26. Common JVM JS Native iOS Mac Linux Windows Android/NDK Wasm

    Others… Java-6 Java-8 Android Browser Node
  27. Common JVM JS Native iOS Mac Linux Windows Android/NDK Wasm

    Others… Java-6 Java-8 Android Browser Node
  28. Common JVM JS Native iOS Mac Linux Windows Android/NDK Wasm

    Others… Java-6 Java-8 Android Browser Node
  29. Common JVM JS Native iOS Mac Linux Windows Android/NDK Wasm

    Others… Java-6 Java-8 Android Browser Node
  30. Common JVM Native iOS Mac Linux Windows Android/NDK Wasm Others…

    Java-6 Java-8 Android
  31. Common JVM Native

  32. Common JVM Native

  33. Common JVM Native iOS

  34. Common JVM iOS

  35. Common JVM iOS Framework

  36. Common JVM iOS Framework Android

  37. Common Android iOS Framework

  38. Common Android iOS Framework Android Stuff iOS Stuff

  39. Status

  40. Q3 Q2 Q4 Q1 Q2 2018 2019 v0.7 v0.8 v0.8.2

    v0.9.3 IDE tooling! Coroutines? K/N 1.0, Kotlin 1.3 Gradle 4.10+ Android Studio Compiler plugins! Other samples/libraries Production deployments Paid license/debugger Reactive Library Big Production Apps Webassembly stuff? MT Coroutines!
  41. Q3 Q2 Q4 Q1 Q2 2019 2020 v1.3.30 v1.3.40 v1.3.50

    v1.3.60? MT Coroutines? Many Threading Libraries Many Threading Blog Posts Increased Production Adoption Improved Tooling Stability Compiler Plugins? Date/Time?
  42. Q3 Q2 Q4 Q1 Q2 2019 2020 v1.3.30 v1.3.40 v1.3.50

    v1.3.60? MT Coroutines? Mainstream Date/Time?
  43. Is This Ready? that’s more about your team

  44. Multithreaded Coroutines need to change my slides

  45. Sharing Code

  46. Kotlin Embraces “Native” the interop story is really good

  47. expect/actual

  48. //In common code expect val isMainThread: Boolean

  49. //In common code expect val isMainThread: Boolean //In Android/JVM actual

    val isMainThread: Boolean get() = Looper.getMainLooper() === Looper.myLooper()
  50. //In common code expect val isMainThread: Boolean //In Android/JVM actual

    val isMainThread: Boolean get() = Looper.getMainLooper() === Looper.myLooper() //In iOS/native code actual val isMainThread: Boolean get() = NSThread.isMainThread()
  51. //Value expect val isMainThread: Boolean

  52. //Value expect val isMainThread: Boolean //Function expect fun myFun():String

  53. //Value expect val isMainThread: Boolean //Function expect fun myFun():String //Class

    expect class MyClass { fun heyo(): String }
  54. //Value expect val isMainThread: Boolean //Function expect fun myFun():String //Class

    expect class MyClass { fun heyo(): String } //Object expect object MyObject { fun heyo(): String }
  55. //Value expect val isMainThread: Boolean //Function expect fun myFun():String //Class

    expect class MyClass { fun heyo(): String } //Object expect object MyObject { fun heyo(): String } //Annotation @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CONSTRUCTOR) @Retention(AnnotationRetention.SOURCE) expect annotation class Throws(vararg val exceptionClasses: KClass<out Throwable>)
  56. expect class Sample() { fun checkMe(): Int } expect object

    Platform { val name: String } fun hello(): String = "Hello from ${Platform.name}"
  57. actual class Sample { actual fun checkMe() = 44 }

    actual object Platform { actual val name: String = "Android" }
  58. actual class Sample { actual fun checkMe() = 7 }

    actual object Platform { actual val name: String = "iOS" }
  59. actual typealias with great power…

  60. /** * 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 }
  61. JVM Side?

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

  63. Actual needs to match because obviously it does

  64. 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) }
  65. 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) }
  66. library talk

  67. Prefer Interfaces for “service” objects

  68. public interface Settings { public fun clear() public fun remove(key:

    String) public fun hasKey(key: String): Boolean public fun putInt(key: String, value: Int) public fun getInt(key: String, defaultValue: Int = 0): Int public fun getIntOrNull(key: String): Int? public fun putLong(key: String, value: Long) public fun getLong(key: String, defaultValue: Long = 0): Long public fun getLongOrNull(key: String): Long? //Etc... } from https://github.com/russhwolf/multiplatform-settings
  69. expect fun platformSettings():Settings

  70. object ServiceRegistry { var sessionizeApi:SessionizeApi by ThreadLocalDelegate() var analyticsApi: AnalyticsApi

    by FrozenDelegate() var notificationsApi:NotificationsApi by FrozenDelegate() var dbDriver: SqlDriver by FrozenDelegate() var cd: CoroutineDispatcher by FrozenDelegate() var appSettings: Settings by FrozenDelegate() var concurrent: Concurrent by FrozenDelegate() var timeZone: String by FrozenDelegate() //Etc… from https://github.com/touchlab/DroidconKotlin/
  71. Just an Interface no expect/actual required

  72. class TestSettings:Settings { private val map = frozenHashMap<String, Any?>() override

    fun clear() { map.clear() } override fun getBoolean(key: String, defaultValue: Boolean): Bo return if(map.containsKey(key)){ map[key] as Boolean }else{ defaultValue } } //Etc… from https://github.com/touchlab/DroidconKotlin/
  73. object ServiceRegistry { var sessionizeApi:SessionizeApi by ThreadLocalDelegate() var analyticsApi: AnalyticsApi

    by FrozenDelegate() var notificationsApi:NotificationsApi by FrozenDelegate() var dbDriver: SqlDriver by FrozenDelegate() var cd: CoroutineDispatcher by FrozenDelegate() var appSettings: Settings by FrozenDelegate() var concurrent: Concurrent by FrozenDelegate() var timeZone: String by FrozenDelegate() //Etc… from https://github.com/touchlab/DroidconKotlin/
  74. koin multiplatform

  75. interface AnalyticsApi { fun logEvent(name: String, params: Map<String, Any>) }

  76. class AnalyticsApiImpl(val firebaseAnalytics: FirebaseAnalytics) : AnalyticsApi { override fun logEvent(name:

    String, params: Map<String, Any>) { val bundle = Bundle() params.keys.forEach { key -> when (val obj = params[key]) { is String -> bundle.putString(key, obj) is Int -> bundle.putInt(key, obj) else -> { throw IllegalArgumentException(“…”) } } } firebaseAnalytics.logEvent(name, bundle) } }
  77. class AnalyticsApiMock : AnalyticsApi { var logCalled = false override

    fun logEvent(name: String, params: Map<String, Any>) { logCalled = true } }
  78. class FirebaseAnalyticsApi: AnalyticsApi{ func logEvent(name: String, params: [String : Any])

    { Analytics.logEvent(name, parameters: params) } }
  79. serviceRegistry.doInitLambdas(staticFileLoader: loadAsset, clLogCallback: csLog, softExceptionCallback: softExceptionCallback) serviceRegistry.doInitServiceRegistry(sqlDriver: …, coroutineDispatcher: UI(),

    settings: FunctionsKt.defaultSettings(), concurrent: MainConcurrent(), sessionizeApi: SessionizeApiImpl(), analyticsApi: …, notificationsApi: NotificationsApiImpl(), timeZone: timeZone) AppContext().doInitAppContext(networkRepo: NetworkRepo(), fileRepo: FileRepo(), serviceRegistry: serviceRegistry, dbHelper: SessionizeDbHelper(), notificationsModel: NotificationsModel())
  80. Function args! swift friendly

  81. fun initLambdas( staticFileLoader: (filePrefix: String, fileType: String) -> String?, clLogCallback:

    (s: String) -> Unit, softExceptionCallback: (e:Throwable, message:String) - >Unit)
  82. func loadAsset(filePrefix:String, fileType:String) -> String?{ do{ let bundleFile = Bundle.main.path(forResource:

    filePrefix, ofType: fileType) return try String(contentsOfFile: bundleFile!) } catch { return nil } }
  83. Hard(er) to test tests are Kotlin exe

  84. go.touchlab.co/dcktsrc

  85. go.touchlab.co/dcktsrc

  86. So no expect/actual? um…

  87. Minimize expect/actual Droidcon App: 9 functions, 2 classes

  88. Minimize expect/actual? Firestore SDK: 100+ ‘expect’ instances

  89. Getting Started

  90. Droidcon is OK there’s a lot going on, though

  91. JetBrains Doc kotlinlang.org/docs/reference/building-mpp-with-gradle.html

  92. “5 minutes” go.touchlab.co/kmp5

  93. Kotlin Slack https://kotlinlang.org/community/

  94. KMP evaluation kit

  95. KaMP-Kit go.touchlab.co/KaMP-Kit

  96. Concurrency it’s complicated (with native)

  97. Two Rules mutable = 1 thread/immutable = many threads

  98. Frozen new state of state

  99. public fun <T> T.freeze(): T {...}

  100. public fun <T> T.freeze(): T {...}

  101. JetBrains Links • https://github.com/JetBrains/kotlin-native/blob/ master/IMMUTABILITY.md • https://github.com/JetBrains/kotlin-native/blob/ master/CONCURRENCY.md • https://www.youtube.com/watch?v=nw6YTfEyfO0

  102. go.touchlab.co/knthreads

  103. Kotlinconf

  104. Thanks! @kpgalligan touchlab.co

  105. Thanks! @kpgalligan touchlab.co Join the team !