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

The Hitchhikers Guide Through Kotlin Multiplatform Android Makers

cmota
April 20, 2020

The Hitchhikers Guide Through Kotlin Multiplatform Android Makers

Since the early days of mobile that we keep seeing new frameworks being designed to overcome one of the biggest challenges:
- How can I develop for both Android and iOS?

Although it’s initial promises, when we talk about performance, maintainability or even customization we keep discarding these solutions and we always choose native.

Fast forward to the present, and now we have two new languages: Android is Kotlin first and iOS, Swift. And if you put them side by side you can see a lot of similarities between both what will ease switching between one to the other if you have to develop for both platforms.

But what I told that you could just develop in Kotlin and run it seamlessly on all devices? Here comes Kotlin Multiplatform!

cmota

April 20, 2020
Tweet

More Decks by cmota

Other Decks in Programming

Transcript

  1. Android Makers: Virtual Edition
    The Hitchhikers Guide Through
    Kotlin Multiplatform
    @cafonsomota

    View full-size slide

  2. hello .
    @cafonsomota

    View full-size slide

  3. Questions?
    bit.ly/kmp-androidmakers
    @cafonsomota

    View full-size slide

  4. speakerdeck.com/cmota/
    the-hitchhikers-guide-through-kotlin-multiplatform-androidmakers
    @cafonsomota

    View full-size slide

  5. a brief history of time
    “In the beginning the Universe was created. This had made many people
    angry and has been widely regarded as a bad move.”

    View full-size slide

  6. Photo by Fabian Grohs on Unsplash

    View full-size slide

  7. Photo by Fabian Grohs on Unsplash
    a wild application idea
    appears!
    develop it for Android and iOS.

    View full-size slide

  8. -hire a team of specialised developers
    -define requirements
    -design mockups for both platforms
    -plan features development
    -start development/ write unit tests
    -write t-specs for SQA validation
    -cross-checking validation between both platforms
    (or jacks)
    Steps

    View full-size slide

  9. ~2xthe team
    the cost
    the status meetings required
    the time spent on development
    the time spent on testing
    native
    the time spent on bug fixing

    View full-size slide

  10. Photo by Fabian Grohs on Unsplash
    a wild application idea
    appears!
    develop it for Android and iOS and web.

    View full-size slide

  11. ~3xthe team
    the cost
    the status meetings required
    the time spent on development
    the time spent on testing
    the time spent on bug fixing

    View full-size slide

  12. let’s find a solution
    that works on all
    platforms.

    View full-size slide

  13. 2008 2012 2018
    2013
    2011 2017
    2006 2010
    2009 2015
    * Kotlin Multiplatform
    *

    View full-size slide

  14. - smaller team
    - typically half of the cost needed on native
    - small learning curve for web developers
    (advantages)
    Cross-platform

    View full-size slide

  15. - chained to the framework implementation of UI
    - new updates from the OS will take time to adopt
    - performance is not the same
    - some native code might need to be written
    - OS/ device features are dependent on the fw support
    - dart (flutter) is not widely used language (for now)
    - committed to one framework/ language
    (disadvantages)
    Cross-platform

    View full-size slide

  16. how can we have
    the best of both
    worlds?

    View full-size slide

  17. how can we have
    the best of both
    worlds?

    View full-size slide

  18. kotlin
    “42.”

    View full-size slide

  19. - developed by JetBrains
    - open source
    - concise, safe, interoperable, tool-friendly
    - supported/ used by Google for Android Development
    - it’s more than “just for Android”
    Kotlin
    Server-side Android Kotlin JS Native

    View full-size slide

  20. Mutability
    etc.
    Null safety
    Default
    values
    Type
    inference
    String
    interpolation
    when
    Collections
    Easy to learn

    View full-size slide

  21. Easy to learn
    - small learning curve involved
    - easily to go from JavaScript/ Swift into Kotlin and back
    (from a developers’ point of view)

    View full-size slide

  22. var variable = 42
    variable = 1
    let value = 42
    var variable = 42
    variable = 1
    val value = 42
    *adapted from http://nilhcem.com/swift-is-like-kotlin/
    (variables and constants)
    Kotlin vs Swift
    Kotlin
    Swift

    View full-size slide

  23. fun greet(name: String, day: String): String {
    return "Hello $name, today is $day."
    }
    greet(“Paris”, “Monday”)
    *updated from http://nilhcem.com/swift-is-like-kotlin/
    func greet(_ name: String,_ day: String) -> String {
    return "Hello \(name), today is \(day)."
    }
    greet(“Paris”, “Monday”)
    Kotlin
    Swift
    (functions)
    Kotlin vs Swift

    View full-size slide

  24. class Shape {
    var numberOfSides = 0
    func simpleDescription() -> String {
    return "A shape with \(numberOfSides) sides."
    }
    }
    var shape = Shape()
    shape.numberOfSides = 7
    var shapeDescription = shape.simpleDescription()
    class Shape {
    var numberOfSides = 0
    fun simpleDescription() = "A shape with $numberOfSides sides."
    }
    var shape = Shape()
    shape.numberOfSides = 7
    var shapeDescription = shape.simpleDescription()
    (variables and constants)
    Kotlin vs Swift
    Kotlin
    Swift

    View full-size slide

  25. kotlin multiplatform
    “‘Resistance is useless!’ How can anyone maintain a positive mental
    attitude if you’re saying things like that?”

    View full-size slide

  26. K
    otlin multiplatform
    using kotlin in projects
    that target more than
    one platform

    View full-size slide

  27. view view view view
    model
    parser
    network
    presentation presentation presentation presentation
    network network network
    parser parser parser
    model model model
    presentation presentation presentation
    desktop
    web
    iOS
    android

    View full-size slide

  28. business logic business logic business logic
    model
    parser
    network
    presentation
    model
    parser
    network
    presentation
    model
    parser
    network
    presentation
    model
    parser
    network
    presentation
    business logic
    view view view view
    desktop
    web
    iOS
    android

    View full-size slide

  29. android iOS web desktop
    model
    parser
    network
    presentation
    common
    view view view view
    java/kotlin objective-c/ swift (kotlin) JS supported in jvm

    View full-size slide

  30. shares application logic
    doesn’t share the application UI
    kotlin
    multiplatform

    View full-size slide

  31. (advantages)
    - language features
    - kotlin first!
    - low risk
    - you decide what’s worth to share across platforms
    - interoperability
    - consistency across platforms
    - strong community support
    Kotlin Multiplatform

    View full-size slide

  32. first reaction

    View full-size slide

  33. but how can they expect to communicate?

    View full-size slide

  34. declared at common module
    expect
    declared at android module
    actual
    declared at iOS module
    declared at …

    View full-size slide

  35. we want a platform-specific value for name

    View full-size slide

  36. expect object Platform {
    val name: String
    }
    commonMain
    src/commonMain/sample/Platform.kt

    View full-size slide

  37. commonMain
    src/commonMain/sample/Platform.kt
    - define actual on android
    - define actual on iOS
    targets: android and iOS
    expect object Platform {
    val name: String
    }

    View full-size slide

  38. src/iOSMain/sample/Platform.kt
    actual object Platform {
    actual val name: String = "Android"
    }
    actual object Platform {
    actual val name: String = "iOS"
    }
    src/androidMain/sample/Platform.kt
    platform-dependent code

    View full-size slide

  39. IntelliJ will ask you declare the implementations for actual
    tip: just press alt+enter

    View full-size slide

  40. *.kt
    common
    expect
    JVM
    actual
    *.kt, *.java, *.jar
    Native
    actual
    *.kt, *C, Swift, Framework
    JS
    actual
    *.kt, *.js, NPM

    View full-size slide

  41. medium.com/@cafonsomota

    View full-size slide

  42. show me the code!

    View full-size slide

  43. demo
    "For a moment, nothing happened. Then, after a second or so, nothing
    continued to happen.”

    View full-size slide

  44. (optional)
    (or AppCode)
    IDE’s required

    View full-size slide

  45. src/
    commonMain/
    commonTest/
    app/
    project structure

    View full-size slide

  46. src/
    commonMain/
    commonTest/
    app/
    project structure

    View full-size slide

  47. androidMain
    androidTest
    project structure

    src/
    commonMain/
    commonTest/
    app/

    View full-size slide

  48. project structure


    src/
    commonMain/
    commonTest/
    app/
    androidMain
    androidTest

    View full-size slide

  49. project structure


    src/
    commonMain/
    commonTest/
    app/
    androidMain
    androidTest
    iosMain/
    iosTest/
    jsMain/
    jsTest/


    View full-size slide

  50. github.com/cmota/androidmakers

    View full-size slide

  51. - user interface
    - RecyclerView and more (Android)
    - UITableViewController and more (iOS)
    - multiple network request
    - parse response objects
    - store on local database
    - notify the UI
    - that there’s new content available to be draw
    - reload list
    project structure

    View full-size slide

  52. android iOS
    Room CoreData
    Retrofit Alamofire
    GSON/ Moshi JSONSerialization
    MVP, MVVM, MVI MVVM, ELM
    RxJava RxSwift
    Tests Tests
    Activity UIViewController
    RecyclerView UITableView

    View full-size slide

  53. Room CoreData
    Retrofit Alamofire
    GSON/ Moshi JSONSerialization
    MVP, MVVM, MVI MVVM, ELM
    RxJava RxSwift
    Tests Tests
    Activity UIViewController
    RecyclerView UITableView
    android iOS

    View full-size slide

  54. Room CoreData
    Retrofit Alamofire
    GSON/ Moshi JSONSerialization
    MVP, MVVM, MVI MVVM, ELM
    RxJava RxSwift
    Tests Tests
    Activity UIViewController
    RecyclerView UITableView
    android iOS
    Activity UIViewController
    RecyclerView UITableView
    unnecessary duplication

    View full-size slide

  55. SQLDelight
    ktor
    kotlinx.serialization
    MVP
    kotlinx.coroutines
    kotlin.test
    android iOS
    Activity UIViewController
    RecyclerView UITableView

    View full-size slide

  56. multiplatform
    httpClient
    ktor
    SQLDelight
    android
    kotlinx.serialization
    SQLDriver dispatcher
    view
    presenter
    presenter
    model
    GetSchedule GetSpeakers

    View full-size slide

  57. multiplatform
    httpClient
    ktor
    SQLDelight
    iOS
    model
    kotlinx.serialization
    GetSchedule GetSpeakers
    SQLDriver dispatcher
    view
    presenter
    presenter

    View full-size slide

  58. multiplatform
    network
    database
    android
    parser
    presenter
    presenter
    view
    platform
    domain
    specific
    model

    View full-size slide

  59. class SessionizeAPI(engine: HttpClientEngine) {
    private val client = HttpClient(engine) {
    install(JsonFeature) {
    serializer = KotlinxSerializer()
    }
    }
    suspend fun fetchSpeakers(): List {
    val response = client.get{ url ("$URL")}
    val json = response.readText()
    return Json.parse(SpeakerEntity.serializer().list, json)
    }
    }
    network
    src/commonMain/data/SessionizeAPI.kt
    ktor
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    common (shared) code

    View full-size slide

  60. kotlinx.serialization
    network
    src/commonMain/data/SessionizeAPI.kt
    class SessionizeAPI(engine: HttpClientEngine) {
    private val client = HttpClient(engine) {
    install(JsonFeature) {
    serializer = KotlinxSerializer()
    }
    }
    suspend fun fetchSpeakers(): List {
    val response = client.get{ url ("$URL")}
    val json = response.readText()
    return Json.parse(SpeakerEntity.serializer().list, json)
    }
    }
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  61. json response
    [{
    "id": "2bc1e95f-1243-4ad0-8a11-75541fee10e0",
    "firstName": "Carlos",
    "lastName": "Mota",
    "fullName": "Carlos Mota",
    "bio": "… GDG Coimbra organizer and Kotlin evangelist, he also has a
    huge passion for travel, photography, space and the occasional run.",
    "tagLine": "Lead Software Engineer at WIT Software",
    "profilePicture": “https://sessionize.com/imag...7ef8707ae.jpg”,
    "sessions": [{
    "id": 131513,
    "name": "The Hitchhikers Guide Through Kotlin Multiplatform"
    }],

    },…
    https://sessionize.com/api/v2/3hvwlgcc/view/speakers

    View full-size slide

  62. json response
    [{
    "id": "2bc1e95f-1243-4ad0-8a11-75541fee10e0",
    "firstName": "Carlos",
    "lastName": "Mota",
    "fullName": "Carlos Mota",
    "bio": "… GDG Coimbra organizer and Kotlin evangelist, he also has a
    huge passion for travel, photography, space and the occasional run.",
    "tagLine": "Lead Software Engineer at WIT Software",
    "profilePicture": “https://sessionize.com/imag...7ef8707ae.jpg”,
    "sessions": [{
    "id": 131513,
    "name": "The Hitchhikers Guide Through Kotlin Multiplatform"
    }],

    },…
    https://sessionize.com/api/v2/3hvwlgcc/view/speakers

    View full-size slide

  63. kotlinx.serialization
    src/commonMain/data/entities/SpeakerEntity.kt multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    parser
    @Serializable
    data class SpeakerEntity (
    val id: String,
    val firstName: String,
    val lastName: String,
    val fullName: String,
    val bio: String,
    val tagLine: String,
    val profilePicture: String,
    val sessions: List,
    val isTopSpeaker: Boolean,
    val links: List,
    val questionAnswers: List,
    val categories: List)

    View full-size slide

  64. database
    CREATE TABLE SpeakerModel (
    id TEXT NOT NULL PRIMARY KEY,
    speaker TEXT as Speaker NOT NULL
    );
    insertOrReplaceSpeaker:
    INSERT OR REPLACE INTO SpeakerModel(id, speaker) VALUES (?, ?);
    selectAllSpeakers:
    SELECT *
    FROM SpeakerModel;
    SQLDelight
    src/commonMain/sqldelight/data/model/SpeakerModel.sq multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  65. database
    SQLite
    SQLDelight
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  66. database
    SQLite Compiler Generated code
    SQLDelight
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  67. database
    Generated code
    SQLDelight
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  68. app/build/sqldelight/ScheduleDb/data/app/ScheduleDbImpl.kt
    database
    class SpeakerModelQueriesImpl(private val db: ScheduleDbImpl,
    private val driver: SqlDriver) :
    TransacterImpl(driver), SpeakerModelQueries {

    override fun insertOrReplaceSpeaker(id: String, speaker: Speaker) {
    driver.execute(2113668020,
    """INSERT OR REPLACE INTO SpeakerModel(id, speaker)
    |VALUES (?1, ?2)""", 2) {
    bindString(1, id)
    bindString(2, db.SpeakerModelAdapter.speakerAdapter.encode(speaker))
    }
    notifyQueries(2113668020, {db.speakerModelQueries.selectAllSpeakers})
    }
    }
    generated class
    SQLDelight
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  69. model
    class SpeakerDao(database: ScheduleDb) {
    private val db = database.speakerModelQueries
    internal fun insertOrReplace(speaker: Speaker) {
    db.insertOrReplaceSpeaker(
    id = speaker.id,
    speaker = speaker)
    }
    internal fun getAllSpeakers(): List {
    val data = db.selectAllSpeakers().executeAsList()

    src/commonMain/domain/dao/SpeakerDao.kt multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  70. model
    @Serializable
    data class Speaker (
    val id: String,
    val fullName: String,
    val bio: String,
    val tagLine: String,
    val profilePicture: String,
    val sessions: List,
    val categories: List)
    fun SpeakerEntity.toSpeaker() = Speaker(
    id = id,
    fullName = fullName,
    bio = bio,
    …)
    src/commonMain/domain/model/Speaker.kt multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  71. domain
    class GetSpeakers(val api: SessionizeAPI, val dao: SpeakerDao) {
    suspend operator fun invoke(onSuccess: (List) -> Unit,
    onFailure: (Exception) -> Unit) {
    try {
    val result = api.fetchSpeakers()
    val speakers = Speaker.toSpeaker(result)
    dao.insertOrReplace(speakers)
    coroutineScope {
    onSuccess(speakers)
    }
    } catch (e: Exception) {
    onFailure(e)
    }
    src/commonMain/domain/GetSpeakers.kt multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  72. domain
    class GetSpeakers(val api: SessionizeAPI, val dao: SpeakerDao) {
    suspend operator fun invoke(onSuccess: (List) -> Unit,
    onFailure: (Exception) -> Unit) {
    try {
    val result = api.fetchSpeakers()
    val speakers = Speaker.toSpeaker(result)
    dao.insertOrReplace(speakers)
    coroutineScope {
    onSuccess(speakers)
    }
    } catch (e: Exception) {
    onFailure(e)
    }
    src/commonMain/domain/GetSpeakers.kt multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  73. domain
    class GetSpeakers(val api: SessionizeAPI, val dao: SpeakerDao) {
    suspend operator fun invoke(onSuccess: (List) -> Unit,
    onFailure: (Exception) -> Unit) {
    try {
    val result = api.fetchSpeakers()
    val speakers = Speaker.toSpeaker(result)
    dao.insertOrReplace(speakers)
    coroutineScope {
    onSuccess(speakers)
    }
    } catch (e: Exception) {
    onFailure(e)
    }
    src/commonMain/domain/GetSpeakers.kt multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  74. domain
    class GetSpeakers(val api: SessionizeAPI, val dao: SpeakerDao) {
    suspend operator fun invoke(onSuccess: (List) -> Unit,
    onFailure: (Exception) -> Unit) {
    try {
    val result = api.fetchSpeakers()
    val speakers = Speaker.toSpeaker(result)
    dao.insertOrReplace(speakers)
    coroutineScope {
    onSuccess(speakers)
    }
    } catch (e: Exception) {
    onFailure(e)
    }
    src/commonMain/domain/GetSpeakers.kt multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  75. class SpeakersListPresenter(val speakers: GetSpeakers,
    val coroutineContext: CoroutineContext) {
    lateinit var view: ISpeakersData
    fun attachView(currView: ISpeakersData) {
    view = currView
    fetchSpeakersList()
    }

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/presentation/SpeakersListPresenter.kt

    View full-size slide

  76. class SpeakersListPresenter(val speakers: GetSpeakers,
    val coroutineContext: CoroutineContext) {
    lateinit var view: ISpeakersData
    fun attachView(currView: ISpeakersData) {
    view = currView
    fetchSpeakersList()
    }

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/presentation/SpeakersListPresenter.kt
    onCreate()/ viewDidLoad()

    View full-size slide

  77. class SpeakersListPresenter(val speakers: GetSpeakers,
    val coroutineContext: CoroutineContext) {
    lateinit var view: ISpeakersData
    fun attachView(currView: ISpeakersData) {
    view = currView
    fetchSpeakersList()
    }

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/presentation/SpeakersListPresenter.kt

    View full-size slide

  78. presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/presentation/cb/ISpeakersData.kt
    interface ISpeakersData {
    fun onSpeakersDataFetched(speakers: List)
    fun onSpeakersDataFailed(e: Exception)
    }

    View full-size slide

  79. class SpeakersListPresenter(val speakers: GetSpeakers,
    val coroutineContext: CoroutineContext) {
    lateinit var view: ISpeakersData
    fun attachView(currView: ISpeakersData) {
    view = currView
    fetchSpeakersList()
    }
    private fun fetchSpeakersList() {
    PresenterCoroutineScope(coroutineContext).launch {
    speakers(
    onSuccess = { view?.onSpeakersDataFetched(it) },
    onFailure = { view?.onSpeakersDataFailed(it) })

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/presentation/SpeakersListPresenter.kt

    View full-size slide

  80. class SpeakersListPresenter(val speakers: GetSpeakers,
    val coroutineContext: CoroutineContext) {
    lateinit var view: ISpeakersData
    fun attachView(currView: ISpeakersData) {
    view = currView
    fetchSpeakersList()
    }
    private fun fetchSpeakersList() {
    PresenterCoroutineScope(coroutineContext).launch {
    speakers(
    onSuccess = { view?.onSpeakersDataFetched(it) },
    onFailure = { view?.onSpeakersDataFailed(it) })

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/presentation/SpeakersListPresenter.kt

    View full-size slide

  81. object ServiceLocator {
    private val sessionizeAPI by lazy {
    SessionizeAPI(PlatformServiceLocator.httpClientEngine)
    }
    private val speakerDao by lazy {
    SpeakerDao(PlatformServiceLocator.databaseEngine)
    }
    private val getSpeakers: GetSpeakers
    get() = GetSpeakers(sessionizeAPI, speakerDao)
    val getSpeakersPresenter: SpeakersListPresenter
    get() = SpeakersPresenter(getSpeakers)

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/ServiceLocator.kt

    View full-size slide

  82. object ServiceLocator {
    private val sessionizeAPI by lazy {
    SessionizeAPI(PlatformServiceLocator.httpClientEngine)
    }
    private val speakerDao by lazy {
    SpeakerDao(PlatformServiceLocator.databaseEngine)
    }
    private val getSpeakers: GetSpeakers
    get() = GetSpeakers(sessionizeAPI, speakerDao)
    val getSpeakersPresenter: SpeakersListPresenter
    get() = SpeakersPresenter(getSpeakers)

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/ServiceLocator.kt
    called by the UI

    View full-size slide

  83. object ServiceLocator {
    private val sessionizeAPI by lazy {
    SessionizeAPI(PlatformServiceLocator.httpClientEngine)
    }
    private val speakerDao by lazy {
    SpeakerDao(PlatformServiceLocator.databaseEngine)
    }
    private val getSpeakers: GetSpeakers
    get() = GetSpeakers(sessionizeAPI, speakerDao)
    val getSpeakersPresenter: SpeakersListPresenter
    get() = SpeakersPresenter(getSpeakers)

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/ServiceLocator.kt

    View full-size slide

  84. object ServiceLocator {
    private val sessionizeAPI by lazy {
    SessionizeAPI(PlatformServiceLocator.httpClientEngine)
    }
    private val speakerDao by lazy {
    SpeakerDao(PlatformServiceLocator.databaseEngine)
    }
    private val getSpeakers: GetSpeakers
    get() = GetSpeakers(sessionizeAPI, speakerDao)
    val getSpeakersPresenter: SpeakersListPresenter
    get() = SpeakersPresenter(getSpeakers)

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/ServiceLocator.kt

    View full-size slide

  85. object ServiceLocator {
    private val sessionizeAPI by lazy {
    SessionizeAPI(PlatformServiceLocator.httpClientEngine)
    }
    private val speakerDao by lazy {
    SpeakerDao(PlatformServiceLocator.databaseEngine)
    }
    private val getSpeakers: GetSpeakers
    get() = GetSpeakers(sessionizeAPI, speakerDao)
    val getSpeakersPresenter: SpeakersListPresenter
    get() = SpeakersPresenter(getSpeakers)

    presenter
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/ServiceLocator.kt

    View full-size slide

  86. platform specific
    expect object PlatformServiceLocator {
    val httpClientEngine: HttpClientEngine
    val databaseEngine: ScheduleDb
    }
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/PlatformServiceLocator.kt

    View full-size slide

  87. platform specific
    expect object PlatformServiceLocator {
    val httpClientEngine: HttpClientEngine
    val databaseEngine: ScheduleDb
    }
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    src/commonMain/PlatformServiceLocator.kt

    View full-size slide

  88. platform specific
    expect object PlatformServiceLocator {
    val httpClientEngine: HttpClientEngine
    val databaseEngine: ScheduleDb
    }
    commonMain

    View full-size slide

  89. platform specific
    expect object PlatformServiceLocator {
    val httpClientEngine: HttpClientEngine
    val databaseEngine: ScheduleDb
    }
    commonMain
    actual object PlatformServiceLocator {
    actual val httpClientEngine: HttpClientEngine by lazy {
    OkHttp.create()
    }
    actual val databaseEngine: SqlDriver by lazy {
    AndroidSqliteDriver(ScheduleDb.Schema, ctx, “app.db")

    androidMain

    View full-size slide

  90. platform specific
    expect object PlatformServiceLocator {
    val httpClientEngine: HttpClientEngine
    val databaseEngine: ScheduleDb
    }
    commonMain
    actual object PlatformServiceLocator {
    actual val httpClientEngine: HttpClientEngine by lazy {
    OkHttp.create()
    }
    actual val databaseEngine: SqlDriver by lazy {
    AndroidSqliteDriver(ScheduleDb.Schema, ctx, “app.db")

    androidMain
    actual object PlatformServiceLocator {
    actual val httpClientEngine: HttpClientEngine by lazy {
    Ios.create()
    }
    actual val databaseEngine: SqlDriver by lazy {
    NativeSqliteDriver(ScheduleDb.Schema, “app.db")

    iOSMain

    View full-size slide

  91. platform specific
    expect object PlatformServiceLocator {
    val httpClientEngine: HttpClientEngine
    val databaseEngine: ScheduleDb
    }
    commonMain
    actual object PlatformServiceLocator {
    actual val httpClientEngine: HttpClientEngine by lazy {
    OkHttp.create()
    }
    actual val databaseEngine: SqlDriver by lazy {
    AndroidSqliteDriver(ScheduleDb.Schema, ctx, “app.db")

    androidMain
    actual object PlatformServiceLocator {
    actual val httpClientEngine: HttpClientEngine by lazy {
    Ios.create()
    }
    actual val databaseEngine: SqlDriver by lazy {
    NativeSqliteDriver(ScheduleDb.Schema, “app.db")

    iOSMain

    View full-size slide

  92. platform specific
    actual object PlatformServiceLocator {
    actual val httpClientEngine: HttpClientEngine by lazy {
    OkHttp.create()
    }
    actual val databaseEngine: SqlDriver by lazy {
    AndroidSqliteDriver(ScheduleDb.Schema, ctx, “app.db")

    androidMain
    actual object PlatformServiceLocator {
    actual val httpClientEngine: HttpClientEngine by lazy {
    Ios.create()
    }
    actual val databaseEngine: SqlDriver by lazy {
    NativeSqliteDriver(ScheduleDb.Schema, “app.db")

    iOSMain
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  93. src/commonAndroid/presenter/activities/MainActivity.kt
    android
    class MainActivity : AppCompatActivity(), ISpeakersData {
    val presenter by lazy { ServiceLocator.getSpeakersPresenter }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    presenter.attachView(this)
    }
    override fun onSpeakersDataFetched(list: List){
    setup(list)

    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  94. src/commonAndroid/presenter/activities/MainActivity.kt
    android
    class MainActivity : AppCompatActivity(), ISpeakersData {
    val presenter by lazy { ServiceLocator.getSpeakersPresenter }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    presenter.attachView(this)
    }
    override fun onSpeakersDataFetched(list: List){
    setup(list)

    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  95. src/commonAndroid/presenter/adapters/SpeakersListAdapter.kt
    android
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    override fun onBindViewHolder(viewHolder: SpeakerViewHolder,
    position: Int) {
    val speaker = speakers[position]
    Glide.with(viewHolder.speakerPhoto)
    .load(speaker.profilePicture)
    .apply(RequestOptions.circleCropTransform())
    .into(viewHolder.speakerPhoto)
    viewHolder.speakerName.text = speaker.fullName
    viewHolder.talkTitle.text = speaker.talkTitle
    viewHolder.container.setOnClickListener {
    action.onUserClickAction(speaker, it)
    }
    }

    View full-size slide

  96. src/commonAndroid/presenter/adapters/SpeakersListAdapter.kt
    android
    override fun onBindViewHolder(viewHolder: SpeakerViewHolder,
    position: Int) {
    val speaker = speakers[position]
    Glide.with(viewHolder.speakerPhoto)
    .load(speaker.profilePicture)
    .apply(RequestOptions.circleCropTransform())
    .into(viewHolder.speakerPhoto)
    viewHolder.speakerName.text = speaker.fullName
    viewHolder.talkTitle.text = speaker.talkTitle
    viewHolder.container.setOnClickListener {
    action.onUserClickAction(speaker, it)
    }
    }
    you can keep using your android libraries
    multiplatform
    network
    database
    android
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model

    View full-size slide

  97. iosApp/iosApp/SpeakerTableViewController.swift
    iOS
    multiplatform
    network
    database
    iOS
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    class SpeakerTableViewController:
    UITableViewController, ISpeakersData
    lazy var presenter = ServiceLocator.init().getSpeakerPresenter
    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    presenter.attachView(view: self)
    }
    func onSpeakersDataFetched(speakers: [Speaker]) {
    setup(speakers)

    View full-size slide

  98. iosApp/iosApp/SpeakerTableViewController.swift
    iOS
    multiplatform
    network
    database
    iOS
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    class SpeakerTableViewController:
    UITableViewController, ISpeakersData {
    lazy var presenter = ServiceLocator.init().getSpeakersPresenter
    override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    presenter.attachView(view: self)
    }
    func onSpeakersDataFetched(speakers: [Speaker]) {
    setup(speakers)

    View full-size slide

  99. iosApp/iosApp/SpeakerTableViewController.swift
    iOS
    multiplatform
    network
    database
    iOS
    parser
    view
    platform
    specific
    presenter
    presenter
    domain
    model
    override func tableView(_ tableView: UITableView, cellForRowAt
    indexPath: IndexPath) -> UITableViewCell {
    let cellIdentifier = "SpeakerTableViewCell"
    let cell = tableView.dequeueReusableCell(withIdentifier:
    cellIdentifier, for: indexPath) as SpeakerTableViewCell
    let speaker = speakers[indexPath.row]
    cell.talkName.text = speaker.talkTitle
    cell.speakerName.text = speaker.fullName
    cell.speakerImage.image = speaker.profilePicture
    return cell
    }

    View full-size slide

  100. how much sharing?
    what makes sense for your app
    kotlin
    multiplatform

    View full-size slide

  101. conclusions
    don’t forget to bring your own towel.

    View full-size slide

  102. team structure
    Kotlin Multiplatform
    android iOS

    View full-size slide

  103. team structure
    Kotlin Multiplatform
    android iOS
    mobile backend
    documentation
    tests
    clean API

    View full-size slide

  104. (impressions)
    Kotlin Multiplatform
    I could just focus on doing what I know best - UI. I have
    no idea how things are being done in the backend, yet I
    know that when I ask for data I receive it.
    - iOS Developer

    View full-size slide

  105. - it’s in experimental state
    - although we keep seeing new projects in production
    - Space was announced during Kotlin Conf 19 (web, desktop and mobile)
    (apps in production)
    Kotlin Multiplatform
    Space
    Adapted from: KotlinConf 2019: Opening Keynote by Andrey Breslav
    Full KMP
    Cash App
    Shares:
    business logic
    Yandex Maps
    Shares:
    business logic,
    wrappers for C++
    libraries
    PlanGrid Planboard
    Workspace
    Shares:
    business logic,
    sync logic,
    mgmnt offline data
    Shares:
    business logic,
    networking,
    offline caching lyrs
    Shares:
    business logic

    View full-size slide

  106. - start small, don’t try to reach 100% of shared logic
    - perhaps by writing common tests to gain familiarity
    - one module at a time (network, database, image processing, etc.)
    - you can spend some time resolving compilation issues
    - specially if you try to target all platforms
    (how to start?)
    Kotlin Multiplatform
    - it’s in experimental state
    - experimental means that one simple update can break your builds
    - lookout for plugins/ libraries updates

    View full-size slide

  107. - evangelize the iOS team
    - select one module where something is more complex on iOS than on Android
    - KMP is not intended to share the UI
    - iOS developers can focus on Swift and frontend features
    - On cross platform like Flutter or React Native they need to use Dart or JS
    - not possible to debug kotlin from Xcode
    - plugin for Xcode from TouchLab/ support mentioned on Kotlin Conf 19
    (how to start?)
    Kotlin Multiplatform

    View full-size slide

  108. - strong community
    - a lot of people are using kotlin nowadays
    - Google and JetBrains are pushing Kotlin in
    - you can ask questions directly on https://kotlinlang.slack.com
    - 2x faster to develop your features business logic
    - 2x faster writing unit tests
    - one tech stack
    - consistency across platforms
    (conclusions)
    Kotlin Multiplatform

    View full-size slide

  109. - Ktor (networking)
    - Kotlinx.Coroutines
    - Kotlin.Serialization
    - SqlDelight (database)
    - Multiplatform Settings
    - Stately (state management)
    (libraries)
    more information

    View full-size slide

  110. - Kotlinconf App
    - Droidcon Kotlin
    - KaMP Kit
    - Sudoku Playground
    - PeopleInSpace
    - Kotlin Game Of Life
    - WorkoutMPP
    (cool projects to follow)
    more information

    View full-size slide

  111. - Kotlin Slack
    - Kotlin Official
    - Kotlin by:
    https://jakewharton.com/presentations/
    https://touchlab.co/kamp-kit-touchlab/
    - Kotlin Conf 2019 videos
    https://www.youtube.com/watch?v=0xKTM0A8gdI
    - Android Makers Paris
    Intégrer plusieurs libraires Kotlin Native dans vos apps Android/iOS (April 21st)
    (useful links)
    more information

    View full-size slide

  112. @cafonsomota
    so long and thanks for all the fish
    medium.com/@cafonsomota
    github.com/cmota/AndroidMakers

    View full-size slide