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

iOS Architecture with Multiplatform

iOS Architecture with Multiplatform

Kevin Galligan

October 05, 2018
Tweet

More Decks by Kevin Galligan

Other Decks in Programming

Transcript

  1. iOS Architecture with Multiplatform
    Kevin Galligan

    View full-size slide

  2. Community
    community!

    View full-size slide

  3. JRE

    (lang, io, util, etc)
    J2ObjC
    JRE
    JUnit Mockito

    View full-size slide

  4. JRE

    (lang, io, util, etc) Android

    (Context, SQLiteDatabase, Threading,
    Shared Preferences)
    J2ObjC
    Doppl
    JRE
    JUnit Mockito

    View full-size slide

  5. JRE

    (lang, io, util, etc) Android

    (Context, SQLiteDatabase, Threading,
    Shared Preferences)
    Gradle Plugin Library Format
    Testing Support Xcode Support
    J2ObjC
    Doppl
    JRE
    JUnit Mockito

    View full-size slide

  6. JRE

    (lang, io, util, etc) Android

    (Context, SQLiteDatabase, Threading,
    Shared Preferences)
    Gradle Plugin Library Format
    Retrofit
    RxJava
    RxAndroid
    Gson
    Dagger
    SQLDelight
    Room DB
    Android
    Architecture
    etc…
    Testing Support Xcode Support
    J2ObjC
    Doppl
    JRE
    JUnit Mockito

    View full-size slide

  7. Fractivities
    Architecture
    Components
    Looper,
    Handler,
    Message
    Queue
    Retrofit
    SQLite

    (Room, etc)
    java.io.File
    Android
    Android Native
    Stuff

    View full-size slide

  8. Fractivities
    Architecture
    Components
    Looper,
    Handler,
    Message
    Queue
    Retrofit
    SQLite

    (Room, etc)
    java.io.File
    Android
    Android Native
    Stuff
    ViewControllers
    Architecture
    Components
    Looper,
    Handler,
    Message
    Queue
    Retrofit
    SQLite

    (Room, etc)
    java.io.File
    iOS
    iOS Native Stuff

    View full-size slide

  9. https://medium.com/@kpgalligan/the-future-of-shared-code-is-kotlin-multiplatform-9aac94517f95

    View full-size slide

  10. still no thanks

    View full-size slide

  11. swift is life!!!

    View full-size slide

  12. the future?
    now

    View full-size slide

  13. reach out for repos

    View full-size slide

  14. chief hacking officer

    View full-size slide

  15. SHARED ARHICTECTURE

    View full-size slide

  16. Mobile & Web
    Architecture, not UI

    View full-size slide

  17. Shared UI == Failure!

    View full-size slide

  18. Shared Loigc == Computers

    View full-size slide

  19. Web is more difficult
    No SQL :(

    View full-size slide

  20. Advocate for new standards
    AKA The Long Game

    View full-size slide

  21. mobile is MUCH simpler

    View full-size slide

  22. DROIDCON
    WITH
    KOTLIN MULTIPLATFORM

    View full-size slide

  23. Funky Code Testbed

    View full-size slide

  24. Funky Code Testbed
    Kotlin in 2014!

    View full-size slide

  25. Droidcon NYC & SF

    View full-size slide

  26. Droidcon NYC & SF
    KotlinConf fork, but use the official :)

    View full-size slide

  27. SQLite
    Knarch.db
    Android iOS

    View full-size slide

  28. SQLite
    Knarch.db
    SQLDelight
    Android iOS

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

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

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

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

    View full-size slide

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

    View full-size slide

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

  40. KOTLINCONF
    WITH
    KOTLIN MULTIPLATFORM
    (OBV)

    View full-size slide

  41. Settings
    Android iOS
    Ktor

    View full-size slide

  42. Settings
    Android iOS
    Ktor
    DataRepository

    View full-size slide

  43. Settings
    Android iOS
    Ktor
    DataRepository
    SessionDetailsPresenter

    View full-size slide

  44. Settings
    Android iOS
    Ktor
    DataRepository
    SessionDetailsPresenter
    SessionDetailsView

    View full-size slide

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

  46. Settings
    Android iOS
    Ktor
    DataRepository
    SessionDetailsPresenter
    SessionDetailsView

    View full-size slide

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

    View full-size slide

  48. SHARED CODE
    FOR
    ANDROID & IOS

    View full-size slide

  49. Common mainThread?

    View full-size slide

  50. expect val mainThread:Boolean

    View full-size slide

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

    View full-size slide

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

    View full-size slide

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

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

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

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

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

    View full-size slide

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

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

  60. JVM Native
    Common

    View full-size slide

  61. JVM Native
    Common
    Framework

    View full-size slide

  62. JVM Native
    Common
    Framework

    View full-size slide

  63. JVM Native
    Common
    Android
    Stuff
    Framework
    iOS Stuff

    View full-size slide

  64. JVM Native
    Common

    View full-size slide

  65. JVM Native
    Common
    Ktor-JVM Ktor-Native
    Ktor

    View full-size slide

  66. iOS Dev Info

    View full-size slide

  67. Reference Counting
    but not your reference counting

    View full-size slide

  68. No Reference Cycles

    View full-size slide

  69. Can call from Swift
    although some complaints

    View full-size slide

  70. No bitcode support
    yet…

    View full-size slide

  71. Threading is Different
    that’s for everybody

    View full-size slide

  72. https://medium.com/@kpgalligan/kotlin-native-stranger-threads-c0cf0e0fb847

    View full-size slide

  73. Episode 2 soon!

    View full-size slide

  74. IDE TOOLS
    &
    GRADLE PLUGINS

    View full-size slide

  75. ¯\_(ツ)_/¯

    View full-size slide

  76. Multiplatform IDE
    Intellij community and Android Studio!

    View full-size slide

  77. Multiplatform Gradle
    new and changing

    View full-size slide

  78. Other Plugins?

    View full-size slide

  79. Stdlib
    not exactly a library, but…

    View full-size slide

  80. Kotlin/Native Runtime
    also not a library, still…
    https://github.com/JetBrains/kotlin-native

    View full-size slide

  81. • kotlin/native/concurrent/Freezing.kt
    • kotlin/native/Annotations.kt
    • kotlin/native/concurrent/Worker.kt (maybe)

    View full-size slide

  82. https://github.com/ktorio/ktor
    Ktor
    asynchronous server and client(s)

    View full-size slide

  83. Kotlinx.serialization
    cross-platform / multi-format reflectionless serialization
    https://github.com/Kotlin/kotlinx.serialization

    View full-size slide

  84. Kotlinx.coroutines
    makes coroutines usable
    https://github.com/Kotlin/kotlinx.coroutines

    View full-size slide

  85. KNArch.db
    Kotlin Native Architecture - Database
    https://github.com/touchlab/knarch.db

    View full-size slide

  86. Future Changes
    •Add multithreaded reads and WAL support
    •Coroutines aware api
    •CursorWindow?
    •Other stuff

    View full-size slide

  87. https://github.com/square/sqldelight
    Sqldelight
    A Multiplatform Delight

    View full-size slide

  88. CREATE TABLE session(
    id TEXT NOT NULL PRIMARY KEY,
    title TEXT NOT NULL,
    description TEXT NOT NULL,
    startsAt TEXT AS Date NOT NULL,
    endsAt TEXT AS Date NOT NULL,
    serviceSession INTEGER NOT NULL DEFAULT 0,
    rsvp INTEGER NOT NULL DEFAULT 0,
    roomId INTEGER,
    FOREIGN KEY (roomId) REFERENCES room(id)
    );
    insert:
    INSERT INTO session(id, title, description, startsAt, endsAt, serviceSession, roomId)
    VALUES (?,?,?,?,?,?,?)
    ;
    update:
    UPDATE session SET title = ?, description = ?, startsAt = ?,
    endsAt = ?, serviceSession = ?, roomId = ?, rsvp = ?
    WHERE id = ?;
    deleteById:
    DELETE FROM session WHERE id = ?;
    allSessions:
    SELECT * FROM session;
    sessionById:
    SELECT * FROM session WHERE id = ?;

    View full-size slide

  89. --Special query for schedule view
    sessionWithRoom:
    SELECT session.id, session.title, session.description, session.startsAt,
    session.endsAt,
    session.serviceSession, session.rsvp, session.roomId, room.name AS roomName,
    speakers.allNames
    FROM session
    LEFT JOIN (
    SELECT sessionId,group_concat(fullName, ', ') AS allNames
    FROM sessionSpeaker
    JOIN userAccount ON userAccount.id = sessionSpeaker.userAccountId
    GROUP BY sessionId
    ) AS speakers ON speakers.sessionId = session.id
    JOIN room ON session.roomId = room.id
    ;

    View full-size slide

  90. SQLDelight + KNArch.db

    View full-size slide

  91. KNArch.threads
    Kotlin Native Architecture - Threads
    https://github.com/touchlab/knarch.threads

    View full-size slide

  92. KNArch.threads
    • Temporary-ish until better tools emerge
    • Atomic support (deprecated)
    • ThreadLocal
    • LiveData

    View full-size slide

  93. Multiplatform Settings
    Really Shared Preferences
    https://github.com/russhwolf/multiplatform-settings

    View full-size slide

  94. public expect class PlatformSettings : Settings {
    /**
    * A factory that can produce [Settings] instances.
    */
    public class Factory : Settings.Factory {
    public override fun create(name: String?): Settings
    }
    public override fun clear()
    public override fun remove(key: String)
    public override fun hasKey(key: String): Boolean
    public override fun putInt(key: String, value: Int)
    public override fun getInt(key: String, defaultValue: Int): Int
    public override fun putLong(key: String, value: Long)
    public override fun getLong(key: String, defaultValue: Long): Long
    public override fun putString(key: String, value: String)
    public override fun getString(key: String, defaultValue: String): String
    public override fun putFloat(key: String, value: Float)
    public override fun getFloat(key: String, defaultValue: Float): Float
    public override fun putDouble(key: String, value: Double)
    public override fun getDouble(key: String, defaultValue: Double): Double
    public override fun putBoolean(key: String, value: Boolean)
    public override fun getBoolean(key: String, defaultValue: Boolean): Boolean
    }

    View full-size slide

  95. Timber
    Multiplatform logging
    https://github.com/JakeWharton/timber

    View full-size slide

  96. Atomic Fu
    Atomic operation support
    https://github.com/Kotlin/kotlinx.atomicfu

    View full-size slide

  97. Kotlinx.io
    multiplatform I/O library
    https://github.com/Kotlin/kotlinx-io

    View full-size slide

  98. OKIO2 MULTIPLATFORM

    View full-size slide

  99. MY WISH LIST

    View full-size slide

  100. Stable Gradle Plugins
    I know, but

    View full-size slide

  101. Significant Library Examples
    With publishing, for all targets

    View full-size slide

  102. Multithreaded Native Coroutines
    If I get 1 thing for Christmas…

    View full-size slide

  103. A Reactive Library
    Or maybe just coroutines?

    View full-size slide

  104. Xcode Debugging?
    Asking a lot, but still

    View full-size slide

  105. COMMUNITY WISH LIST

    View full-size slide

  106. Mocking Library
    See mockk repo

    View full-size slide

  107. Dependency Injection
    Or service locator I guess…

    View full-size slide

  108. Build Instrumentation
    kapt, compiler plugins

    View full-size slide

  109. Date Support
    JSR 310 or similar

    View full-size slide

  110. UI Stuff
    Here be dragons

    View full-size slide

  111. Getting Started

    View full-size slide

  112. Build Samples
    Conference apps, several others

    View full-size slide

  113. Kotlin/Native Docs
    Learn threads and state

    View full-size slide

  114. For Libraries?
    Multiplatform Settings (then the rest)

    View full-size slide

  115. Join the Kotlin Slack

    View full-size slide

  116. Q3
    Q2 Q4 Q1 Q2
    2018 2019
    0 .6
    v0.7
    v0.8
    v0.8.2
    v0.9
    Coroutines
    (and other libs)

    View full-size slide

  117. Q3
    Q2 Q4 Q1 Q2
    2018 2019
    0 .6
    v0.7
    v0.8
    v0.8.2
    v0.9.3
    Coroutines?

    View full-size slide

  118. Q3
    Q2 Q4 Q1 Q2
    2018 2019
    0 .6
    v0.7
    v0.8
    v0.8.2
    v0.9.3
    Coroutines?
    Other Libraries
    Docs/tutorials

    View full-size slide

  119. Thanks for the Images!
    Source Links at: https://bit.ly/2O0c469

    View full-size slide

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

    View full-size slide