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

Kotlin Multiplatform Architecture

Kotlin Multiplatform Architecture

Discussing building shared architecture apps with Kotlin, mostly native mobile (Android & iOS). Talk about libraries currently available, why Jetbrains has a different threading model, where the ecosystem is at and where I think it's going.

Kevin Galligan

October 26, 2018
Tweet

More Decks by Kevin Galligan

Other Decks in Technology

Transcript

  1. Kotlin Multiplatform Architecture
    Kevin Galligan

    View full-size slide

  2. Community
    community!

    View full-size slide

  3. SHARED ARHICTECTURE

    View full-size slide

  4. Mobile & Web
    Architecture, not UI

    View full-size slide

  5. Shared UI == Failure!

    View full-size slide

  6. Shared Loigc == Computers

    View full-size slide

  7. Web is more difficult
    No SQL :(

    View full-size slide

  8. Advocate for new standards
    I want SQL again

    View full-size slide

  9. https://hacks.mozilla.org/

    View full-size slide

  10. mobile is MUCH simpler

    View full-size slide

  11. Modern Language

    View full-size slide

  12. Community Excitement

    View full-size slide

  13. Smooth Interop

    View full-size slide

  14. Optional Sharing
    Low risk. No Big Decisions.

    View full-size slide

  15. “Everything Should Be Made as Simple as Possible, But Not
    Simpler”
    - Albert Einstein Maybe?

    View full-size slide

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

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

  18. 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
    MT Coroutines!
    Other samples/libraries
    Production deployments

    View full-size slide

  19. 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
    MT Coroutines!
    Other samples/libraries
    Production deployments
    Paid license/debugger
    Reactive Library
    Big Production Apps
    Webassembly stuff?

    View full-size slide

  20. SHARED CODE
    FOR
    ANDROID & IOS

    View full-size slide

  21. Common mainThread?

    View full-size slide

  22. expect val mainThread:Boolean

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

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

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

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

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

    View full-size slide

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

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

  32. JVM Native
    Common

    View full-size slide

  33. JVM Native
    Common
    Framework

    View full-size slide

  34. JVM Native
    Common
    Framework

    View full-size slide

  35. JVM Native
    Common
    Android
    Stuff
    Framework
    iOS Stuff

    View full-size slide

  36. JVM Native
    Common

    View full-size slide

  37. JVM Native
    Common
    Ktor-JVM Ktor-Native
    Ktor

    View full-size slide

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

    View full-size slide

  39. DROIDCON
    WITH
    KOTLIN MULTIPLATFORM

    View full-size slide

  40. Funky Code Testbed

    View full-size slide

  41. Funky Code Testbed
    Kotlin in 2014!

    View full-size slide

  42. Droidcon NYC & SF

    View full-size slide

  43. SQLite
    Knarch.db
    Android iOS

    View full-size slide

  44. SQLite
    Knarch.db
    SQLDelight
    Android iOS

    View full-size slide

  45. SQLite
    Knarch.db
    SQLDelight
    Logic!
    Reactive (LiveData)
    Android iOS

    View full-size slide

  46. SQLite
    Knarch.db
    SQLDelight
    Logic!
    Reactive (LiveData)
    Android iOS

    View full-size slide

  47. SQLite
    Knarch.db
    SQLDelight MP Settings
    Logic!
    Reactive (LiveData)
    Android iOS

    View full-size slide

  48. val evenLiveData:EventLiveData
    init {
    val query = goFreeze(AppContext.dbHelper.
    queryWrapper.sessionQueries.
    sessionById(sessionId))
    evenLiveData = EventLiveData(query)
    }
    fun shutDown(){
    evenLiveData.removeListener()
    }

    View full-size slide

  49. val evenLiveData:EventLiveData
    init {
    val query = goFreeze(AppContext.dbHelper.
    queryWrapper.sessionQueries.
    sessionById(sessionId))
    evenLiveData = EventLiveData(query)
    }
    fun shutDown(){
    evenLiveData.removeListener()
    }

    View full-size slide

  50. override fun onCreateView(inflater: LayoutInflater,
    container: ViewGroup?, savedInstanceState: Bundle?): View?
    {
    eventViewModel.eventModel.evenLiveData.
    observe(viewLifecycleOwner,
    Observer { dataRefresh(it) })
    return initView(inflater, container)
    }

    View full-size slide

  51. override fun onCreateView(inflater: LayoutInflater,
    container: ViewGroup?, savedInstanceState: Bundle?): View?
    {
    eventViewModel.eventModel.evenLiveData.
    observe(viewLifecycleOwner,
    Observer { dataRefresh(it) })
    return initView(inflater, container)
    }

    View full-size slide

  52. fun registerForChanges(proc:(sessionInfo:SessionInfo)->Unit){
    eventObserver = object : Observer{
    override fun onChanged(t: SessionInfo?){
    if(t != null)
    proc(t)
    }
    }
    eventModel.evenLiveData.observeForever(eventObserver!!)
    }

    View full-size slide

  53. viewModel = EventViewModel(sessionId: sessionId)
    viewModel.registerForChanges(proc: updateUi)

    View full-size slide

  54. viewModel = EventViewModel(sessionId: sessionId)
    viewModel.registerForChanges(proc: updateUi)
    func updateUi(sessionInfo:SessionInfo) -> KotlinUnit{
    self.sessionInfo = sessionInfo
    styleButton()
    updateAllUi()
    return KotlinUnit()
    }

    View full-size slide

  55. Droidcon App Kotlin Multiplatform
    https://www.youtube.com/watch?v=YAeDK3Ei0Lk
    https://github.com/touchlab/DroidconKotlin/
    Now
    with 0.9.3!

    View full-size slide

  56. KOTLINCONF
    WITH
    KOTLIN MULTIPLATFORM
    (OBV)

    View full-size slide

  57. Settings
    Android iOS
    Ktor

    View full-size slide

  58. Settings
    Android iOS
    Ktor
    DataRepository

    View full-size slide

  59. Settings
    Android iOS
    Ktor
    DataRepository
    SessionDetailsPresenter

    View full-size slide

  60. Settings
    Android iOS
    Ktor
    DataRepository
    SessionDetailsPresenter
    SessionDetailsView

    View full-size slide

  61. interface SessionDetailsView : BaseView {
    fun updateView(isFavorite: Boolean, session: SessionModel)
    fun setupRatingButtons(rating: SessionRating?)
    fun setRatingClickable(clickable: Boolean)
    }
    override fun updateView(isFavorite: Boolean, session: SessionModel) {
    collapsingToolbar.title = session.title
    speakersTextView.text = session.speakers.joinToString(separator = ", ")
    { it.fullName }
    timeTextView.text = session.timeString
    detailsTextView.text = listOfNotNull(session.roomText,
    session.category).joinToString(", ")
    descriptionTextView.text = session.descriptionText
    val online = context?.let { it.isConnected?.and(!it.isAirplaneModeOn) } ?:
    false
    for (button in listOf(votingButtonsLayout, favoriteButton)) {
    func updateView(isFavorite: Bool, session: SessionModel) {
    titleLabel.text = session.title
    let startsAt = session.startsAt
    let endsAt = session.endsAt
    if (startsAt != nil && endsAt != nil) {
    timeLabel.text = KotlinPair(first: startsAt, second:
    endsAt).toReadableString()
    }
    let image = UIImage(named: isFavorite ? "star_full" : "star_empty")!
    favoriteButton.image = image

    View full-size slide

  62. Settings
    Android iOS
    Ktor
    DataRepository
    SessionDetailsPresenter
    SessionDetailsView

    View full-size slide

  63. KotlinConf App
    https://github.com/JetBrains/kotlinconf-app

    View full-size slide

  64. 3 Ecosystems
    JVM, JS, and Native

    View full-size slide

  65. Kotlin/Native State Rules

    View full-size slide

  66. Rule #1
    Live state belongs to 1 thread

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  69. Runtime Safety
    Kotlin/Native can verify safe mutability

    View full-size slide

  70. JVM/JS?
    See Kotlinconf keynote

    View full-size slide

  71. Short term pain
    Tradeoff for future

    View full-size slide

  72. How does Kotlin know?!

    View full-size slide

  73. Runtime Designation
    AKA a flag

    View full-size slide

  74. all the code
    you’ve ever written
    not frozen
    frozen

    View full-size slide

  75. all the code
    you’ve ever written
    not frozen
    frozen

    View full-size slide

  76. One-way operation
    No unfreeze()

    View full-size slide

  77. Freezes everything

    View full-size slide

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

    View full-size slide

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

    View full-size slide

  80. Usually OK
    Data objects should be immutable

    View full-size slide

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

    View full-size slide

  82. Passing State

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

  86. Atomics!
    Mutable immutable

    View full-size slide

  87. AtomicInt/AtomicLong

    View full-size slide

  88. AtomicReference
    Update with frozen objects

    View full-size slide

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

  90. data class FrozenData(
    val someCount:AtomicInt,
    val someString:String,
    val someOtherState:AtomicReference
    )
    data class OtherState(
    val otherCount:Int,
    val otherString:String
    )

    View full-size slide

  91. val frozenData = FrozenData(
    AtomicInt(1),
    "asdf",
    AtomicReference(
    OtherState(
    1,
    "qwert")
    )
    ).freeze()

    View full-size slide

  92. val frozenData = FrozenData(
    AtomicInt(1),
    "asdf",
    AtomicReference(
    OtherState(
    1,
    "qwert")
    )
    ).freeze()

    View full-size slide

  93. fun update(frozenData: FrozenData){
    frozenData.someCount.increment()
    val otherState = frozenData.someOtherState.value
    val updated = otherState.copy(otherCount = otherState.otherCount+1)
    frozenData.someOtherState.value = updated.freeze()
    }

    View full-size slide

  94. Stately!
    v0.3.1~ish

    View full-size slide

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

    View full-size slide

  96. Multithreaded Collections
    Built on atomics

    View full-size slide

  97. val cowList = frozenCopyOnWriteList()
    val sharedList = frozenLinkedList()
    val coiSharedList = frozenLinkedList(stableIterator = true)
    val sharedMap = frozenHashMap()
    val lruRemovedCount = AtomicInt(0)
    val lruCache = frozenLruCache(5) {
    lruRemovedCount.increment()
    }

    View full-size slide

  98. Why?
    If you have to ask…

    View full-size slide

  99. Changing Architectures
    Coroutines, libraries, best practices

    View full-size slide

  100. Workers
    like Executor

    View full-size slide

  101. Passing State
    similar to DetachedObjectGraph

    View full-size slide

  102. Until Coroutines
    MT coroutines will largely replace Worker

    View full-size slide

  103. Coroutines and State?
    we’ll see

    View full-size slide

  104. Jetbrains
    • Ktor
    • Kotlinx.Coroutines
    • Kotlinx.io
    • Atomic-fu

    View full-size slide

  105. Community
    • Sqldelight
    • Knarch.db
    • Multiplatform Settings
    • Stately
    • OKIO2 (developing)

    View full-size slide

  106. GETTING STARTED

    View full-size slide

  107. Intellij EAP
    Sample project templates!

    View full-size slide

  108. Build Samples
    Conference apps, several others

    View full-size slide

  109. Kotlin/Native Docs
    Learn threads and state

    View full-size slide

  110. Join the Kotlin Slack

    View full-size slide

  111. Kotlinconf Videos
    list on KotlinMultiplatformStuff

    View full-size slide

  112. For Libraries?
    Check out Stately

    View full-size slide

  113. check out yours!

    View full-size slide

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

    View full-size slide