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

Kotlin Multiplatform in Production

Kotlin Multiplatform in Production

Update on the state of the Kotlin Multiplatform universe, current libraries, and how to get started.

Kevin Galligan

November 19, 2018
Tweet

More Decks by Kevin Galligan

Other Decks in Programming

Transcript

  1. Kotlin Multiplatform In Production
    Kevin Galligan

    View full-size slide

  2. Community
    community!

    View full-size slide

  3. What is Kotlin Multiplatform?

    View full-size slide

  4. 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.

    View full-size slide

  5. Optional Sharing
    Low risk. No Big Decisions.

    View full-size slide

  6. Natively Integrated
    smooth interop

    View full-size slide

  7. Code Sharing
    not “cross platform”

    View full-size slide

  8. language & tools

    View full-size slide

  9. Not UI
    well, not necessarily UI

    View full-size slide

  10. Shared UI == Failure!

    View full-size slide

  11. Shared Logic == Computers

    View full-size slide

  12. Many Platforms

    View full-size slide

  13. Kotlin
    JVM JS

    View full-size slide

  14. Kotlin
    JVM JS Native

    View full-size slide

  15. Kotlin
    JVM JS Native
    iOS Mac
    Linux Windows
    Android/NDK Others…

    View full-size slide

  16. Kotlin
    JVM JS Native
    iOS Mac
    Linux Windows
    Android/NDK Others…
    Webassembly

    View full-size slide

  17. Kotlin
    JVM JS Native
    iOS Mac
    Linux Windows
    Android/NDK Others…
    Webassembly
    Many Platforms

    View full-size slide

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

    View full-size slide

  19. mobile is easy

    View full-size slide

  20. mobile is easy

    View full-size slide

  21. Q3
    Q2 Q4 Q1 Q2
    2018 2019
    0 .6
    v0.7
    v0.8
    v0.8.2
    v0.9.3
    IDE tooling!
    Coroutines?

    View full-size slide

  22. Q3
    Q2 Q4 Q1 Q2
    2018 2019
    0 .6
    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

    View full-size slide

  23. Q3
    Q2 Q4 Q1 Q2
    2018 2019
    0 .6
    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
    Other samples/libraries
    Production deployments
    Compiler plugins!
    MT Coroutines!

    View full-size slide

  24. Q3
    Q2 Q4 Q1 Q2
    2018 2019
    0 .6
    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!

    View full-size slide

  25. TL;DR March
    start getting real end of Q1

    View full-size slide

  26. SHARED CODE
    FOR
    ANDROID & IOS

    View full-size slide

  27. Common mainThread?

    View full-size slide

  28. expect val mainThread:Boolean

    View full-size slide

  29. expect val mainThread:Boolean
    actual val mainThread: Boolean
    get() = Looper.myLooper() === Looper.getMainLooper()

    View full-size slide

  30. expect val mainThread:Boolean
    actual val mainThread: Boolean
    get() = Looper.myLooper() === Looper.getMainLooper()
    actual val mainThread: Boolean
    get() = NSThread.isMainThread()

    View full-size slide

  31. expect val mainThread:Boolean
    actual val mainThread: Boolean
    get() = Looper.myLooper() === Looper.getMainLooper()
    actual val mainThread: Boolean
    get() = NSThread.isMainThread()
    actual val mainThread: Boolean = true

    View full-size slide

  32. expect fun currentTimeMillis():Long
    expect fun backgroundTask(backJob:()-> B, mainJob:(B) -> Unit)
    expect fun backgroundTask(backJob:()->Unit)
    expect fun networkBackgroundTask(backJob:()->Unit)
    expect fun initContext():NativeOpenHelperFactory
    expect fun goFreeze(a:T):T
    expect fun T.freeze2(): T
    expect fun simpleGet(url:String):String
    expect fun logException(t:Throwable)
    expect fun settingsFactory(): Settings.Factory
    expect fun createUuid():String

    View full-size slide

  33. expect class Date {
    fun toLongMillis():Long
    }
    expect class DateFormatHelper(format:String){
    fun toDate(s:String):Date
    fun format(d:Date):String
    }

    View full-size slide

  34. actual class Date(val date:java.util.Date) {
    actual fun toLongMillis(): Long = date.time
    }
    actual class DateFormatHelper actual constructor(format: String) {
    val dateFormatter = object : ThreadLocal(){
    override fun initialValue(): DateFormat = SimpleDateFormat(format)
    }
    actual fun toDate(s: String): Date = Date(dateFormatter.get()!!.parse(s))
    actual fun format(d: Date): String = dateFormatter.get()!!.format(d.date)
    }

    View full-size slide

  35. fun initPlatformClient(
    staticFileLoader: (filePrefix: String, fileType: String) -> String?,
    analyticsCallback: (name: String, params: Map) -> Unit,
    clLogCallback: (s: String) -> Unit) {

    View full-size slide

  36. fun initPlatformClient(
    staticFileLoader: (filePrefix: String, fileType: String) -> String?,
    analyticsCallback: (name: String, params: Map) -> Unit,
    clLogCallback: (s: String) -> Unit) {
    AppContext.initPlatformClient ({filePrefix, fileType ->
    loadAsset("${filePrefix}.${fileType}")},
    {name: String, params: Map ->
    val event = CustomEvent(name)
    //Loop
    Answers.getInstance().logCustom(event)
    },
    { Log.w("MainApp", it) })

    View full-size slide

  37. let appContext = AppContext()
    appContext.doInitPlatformClient(staticFileLoader: loadAsset,
    analyticsCallback:
    analyticsCallback,
    clLogCallback: csLog)
    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
    }
    }

    View full-size slide

  38. JVM Native
    Common

    View full-size slide

  39. JVM Native
    Common
    Framework

    View full-size slide

  40. JVM Native
    Common
    Framework

    View full-size slide

  41. JVM Native
    Common
    Android
    Stuff
    Framework
    iOS Stuff

    View full-size slide

  42. Jetbrains
    • Ktor
    • Kotlinx.Coroutines
    • Kotlinx.io
    • Kotlinx.serialization
    • Atomic-fu (maybe?)

    View full-size slide

  43. Community
    • Sqldelight
    • Knarch.db
    • Multiplatform Settings
    • Stately
    • OKIO2 (developing)
    • Timber (sort of)

    View full-size slide

  44. Community
    • Sqldelight
    • Knarch.db
    • Multiplatform Settings
    • Stately
    • OKIO2 (developing)
    • Timber (sort of)

    View full-size slide

  45. SQLiter
    trim, lightly opinionated SQLite driver

    View full-size slide

  46. SQLit
    trim, lightly opinionated SQLite driver

    View full-size slide

  47. Targets
    Native SQLite

    View full-size slide

  48. Targets
    Native SQLite
    Native SQL
    Encrypted

    View full-size slide

  49. Targets
    Native SQLite
    Native SQL
    Encrypted
    Android SQLite

    View full-size slide

  50. Targets
    Native SQLite
    Native SQL
    Encrypted
    Android SQLite
    Android SQL
    Encrypted

    View full-size slide

  51. Targets
    Native SQLite
    Native SQL
    Encrypted
    Android SQLite
    Android SQL
    Encrypted
    JS?

    View full-size slide

  52. Public Service Announcement
    if you understand anything about native…

    View full-size slide

  53. 3 Ecosystems
    JVM, JS, and Native

    View full-size slide

  54. Kotlin/Native State Rules

    View full-size slide

  55. Rule #1
    Live state belongs to 1 thread

    View full-size slide

  56. Rule #2
    Frozen state can be shared by threads

    View full-size slide

  57. No threading primitives
    No “synchronized”, “volatile”, etc

    View full-size slide

  58. Runtime Safety
    Kotlin/Native can verify safe mutability

    View full-size slide

  59. JVM/JS?
    See Kotlinconf keynote

    View full-size slide

  60. Short term pain
    Tradeoff for future

    View full-size slide

  61. How does Kotlin know?!

    View full-size slide

  62. Runtime Designation
    AKA a flag

    View full-size slide

  63. Call freeze()

    View full-size slide

  64. One-way operation
    No unfreeze()

    View full-size slide

  65. class TalkExamples{
    var justCountingStuff:Int = 0
    init {
    backgroundCall {
    //do something
    justCountingStuff++
    }.freeze()
    }
    }

    View full-size slide

  66. class TalkExamples{
    var justCountingStuff:Int = 0
    init {
    backgroundCall {
    //do something
    justCountingStuff++
    }.freeze()
    }
    }

    View full-size slide

  67. Usually OK
    Data objects should be immutable

    View full-size slide

  68. Global state more difficult
    Service object, large memory state

    View full-size slide

  69. Passing State

    View full-size slide

  70. DetachedObjectGraph(TransferMode.SAFE) { ListData("asdf") }

    View full-size slide

  71. val data = ListData("asdf")
    DetachedObjectGraph(TransferMode.SAFE) { data }

    View full-size slide

  72. private val stateBox: AtomicReference> = AtomicReference(
    DetachedObjectGraph(mode = TransferMode.SAFE,
    producer = { mutableListOf() as Any })
    )
    private val lock = NSLock()
    internal fun withLockDetached(proc: (MutableList) -> MutableList) {
    lock.lock()
    try {
    stateBox.value = DetachedObjectGraph(mode = TransferMode.SAFE,
    producer = {
    val dataList = stateBox.value.attach() as MutableList
    proc(dataList) as Any
    })
    } finally {
    lock.unlock()
    }
    }

    View full-size slide

  73. Detaching Time
    must visit whole graph

    View full-size slide

  74. Atomics!
    Mutable immutable

    View full-size slide

  75. AtomicInt/AtomicLong

    View full-size slide

  76. AtomicReference
    Update with frozen objects

    View full-size slide

  77. val lambdas = AtomicReference(null)
    fun initPlatformClient(
    staticFileLoader: (filePrefix: String, fileType: String) -> String?,
    analyticsCallback: (name: String, params: Map) -> Unit,
    clLogCallback: (s: String) -> Unit) {
    lambdas.value = PlatformLambdas(
    staticFileLoader,
    analyticsCallback,
    clLogCallback).freeze()
    }

    View full-size slide

  78. Stately!
    v0.3.1~ish

    View full-size slide

  79. Multiplatform Definitions
    • freeze() method and frozen info
    • Atomics (Int, Long, Reference)
    • K/N state-related annotations (@ThreadLocal/
    @SharedImmutable)
    • Shared Collections!

    View full-size slide

  80. More Info and Tutorials
    https://github.com/touchlab/KotlinMultiplatformStuff

    View full-size slide

  81. Making a Library!
    a very small library

    View full-size slide

  82. Download Intellij EAP
    https://www.jetbrains.com/idea/nextversion/

    View full-size slide

  83. targets {
    fromPreset(presets.jvm, 'jvm')
    // This preset is for iPhone emulator
    // Switch here to presets.iosArm64 to build library for iPhone device
    fromPreset(presets.iosX64, 'ios') {
    compilations.main.outputKinds('FRAMEWORK')
    }
    }

    View full-size slide

  84. targets {
    fromPreset(presets.jvm, 'jvm')
    // This preset is for iPhone emulator
    // Switch here to presets.iosArm64 to build library for iPhone device
    fromPreset(presets.macosX64, 'ios') /*{
    compilations.main.outputKinds('FRAMEWORK')
    }*/
    }

    View full-size slide

  85. targets {
    fromPreset(presets.jvm, 'jvm')
    fromPreset(presets.js, 'js')
    fromPreset(presets.macosX64, 'macos')
    fromPreset(presets.iosX64, 'iosX64')
    fromPreset(presets.iosArm32, 'iosArm32')
    fromPreset(presets.iosArm64, 'iosArm64')
    }

    View full-size slide

  86. Live Coding is a Bad Idea
    don’t be a hero

    View full-size slide

  87. Go to Github
    https://github.com/kpgalligan/MyNewLibrary

    View full-size slide

  88. Much More
    obviously

    View full-size slide

  89. Libraries Needed
    • File Access
    • Date (310 port)
    • Mocking (and other test support)
    • Reactive
    • UI State

    View full-size slide

  90. Getting Started

    View full-size slide

  91. Join Kotlin Slack
    https://slack.kotlinlang.org

    View full-size slide

  92. Sample Apps
    the conference apps

    View full-size slide

  93. https://github.com/touchlab/DroidconKotlin/

    View full-size slide

  94. Sample Libraries
    Stately
    SQLDelight

    View full-size slide

  95. Office Hours - Kotlin Multiplatform
    2pm in Coblenz

    View full-size slide

  96. [email protected]
    @kpgalligan
    Join the team
    !

    View full-size slide