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

A Multiplatform Triathlon

D8a3623b157508fecdae1f8e756f362f?s=47 cmota
October 17, 2020

A Multiplatform Triathlon

For several years now, that we’ve been trying to find new solutions on how we could develop a single project and run it on all platforms. We’ve been seeing all types of solutions: some are web-based, others require you to learn a new language, and others even may require that you pay a monthly fee to use. It also comes with the drawback that all your UI depends on the framework implementation. So, if there’s a native update you’ll need to wait until someone rewrites the widgets for you to update your app.

Kotlin multiplatform gives us a new solution. It focuses on sharing your business logic across all platforms, leaving the UI to be implemented natively. Since it’s Kotlin, you can take full advantage of its language features - concise, safe, etc. Moreover, if you’re coming from android you might already be familiarised with Kotlin and even if you’re an iOS developer you’ll see that it’s quite similar to Swift.

Join me on this triathlon and let’s go through Android, iOS, and Web in under 30 minutes.

D8a3623b157508fecdae1f8e756f362f?s=128

cmota

October 17, 2020
Tweet

Transcript

  1. @cafonsomota Kotlin through Android, iOS and Web A Multiplatform Triathlon

  2. Android Dev Lead @WITSoftware Founder @GDGCoimbra and co-founder @Kotlin_Knights ✍

    Author @rwenderlich and @opinioesonline Podcaster wannabe Loves travel, photography and running @cafonsomota
  3. MATERIALS speakerdeck.com/cmota/a-multiplatform-triathlon github.com/cmota/kmp-a-multiplatform-triathlon cmota.github.io/kmp-codelabs @cafonsomota

  4. @cafonsomota dev-reactions/commitstrip/unrealist-mobile-dev-dream

  5. 2008 2012 2018 2013 2011 2017 2006 2010 2009 2015

    * Kotlin Multiplatform *
  6. - Small learning curve for web developers - Smaller team

    - Typically half of the cost needed on native - Less time (working hours) to built (advantages) Cross-platform to test to fix issues (hopefully not framework specific )
  7. - 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
  8. None
  9. what about Kotlin Multiplatform?

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

  11. - 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
  12. - 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 Cross-multiplatform UI is developed natively nope, UI is developed natively
  13. - 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 Cross-multiplatform nope, UI is developed natively UI is developed natively always arguable, but you’re leaving UI to native, so…
  14. - 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 Cross-multiplatform nope, UI is developed natively yes, the UI UI is developed natively always arguable, but you’re leaving UI to native, so…
  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 Cross-multiplatform nope, UI is developed natively yes, the UI UI is developed natively you have direct access to them; although if on shared module it might give you extra effort always arguable, but you’re leaving UI to native, so…
  16. - 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 Cross-multiplatform nope, UI is developed natively yes, the UI UI is developed natively kotlin, is one of the most trending languages nowadays, with strong community support always arguable, but you’re leaving UI to native, so… you have direct access to them; although if on shared module it might give you extra effort
  17. (advantages) - Language features - Kotlin first! - Low risk

    - You decide what’s worth to share across platforms - Interoperability - Consistency across platforms - Strong investment from JetBrains and community support Kotlin Multiplatform server web native android desktop jvmMain jsMain androidMain iosMain macosX64Main linuxX64Main mingwX64Main
  18. - It’s in alpha - 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
  19. Kotlin Multiplatform

  20. K otlin multiplatform using kotlin in projects that target more

    than one platform
  21. network network parser parser model model presentation presentation model parser

    network view presentation view view presentation presentation web iOS android
  22. business logic business logic business logic model parser network presentation

    model parser network presentation model parser network presentation view view view web iOS android
  23. model parser network presentation common java/kotlin objective-c/ swift (kotlin) JS

    view view view web iOS android
  24. dev-reactions/it-works.gif

  25. None
  26. but how can different platforms expect to communicate?

  27. declared at common module expect declared at android module actual

    declared at iOS module declared at …
  28. in which platform is my application running?

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

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

    - define actual on android - define actual on iOS - define actual on web targets: android, iOS and the web
  31. import android.os.Build.MANUFACTURER actual object Platform { actual val name: String

    = $MANUFACTURER } shared/src/androidMain/sample/Platform.kt Platform-dependent code
  32. import android.os.Build.MANUFACTURER actual object Platform { actual val name: String

    = $MANUFACTURER } shared/src/androidMain/sample/Platform.kt Platform-dependent code
  33. import android.os.Build.MANUFACTURER actual object Platform { actual val name: String

    = $MANUFACTURER } shared/src/androidMain/sample/Platform.kt import platform.UIKit.UIDevice actual object Platform { actual val name: String = UIDevice.currentDevice().name } Platform-dependent code shared/src/iosMain/sample/Platform.kt
  34. import android.os.Build.MANUFACTURER actual object Platform { actual val name: String

    = $MANUFACTURER } shared/src/androidMain/sample/Platform.kt import platform.UIKit.UIDevice actual object Platform { actual val name: String = UIDevice.currentDevice().name } Platform-dependent code import kotlinx.browser.window actual object Platform { actual val name: String = window.navigator.useragent } shared/src/iosMain/sample/Platform.kt shared/src/jsMain/sample/Platform.kt
  35. Platform-dependent code Hello Google-Chrome Hello Pixel-XL Hello iOS-Simulator

  36. androidApp/ iosApp/ shared/ web/

  37. androidApp/ iosApp/ shared/ web/ main/ MainActivity.kt src/ java/ app.package.name/ activities/

  38. src/ commonMain/ commonTest/ shared/ androidApp/ iosApp/ web/

  39. src/ commonMain/ commonTest/ shared/ <your code shared across all targets

    goes here> androidApp/ iosApp/ web/
  40. androidMain androidTest <your code shared across all targets goes here>

    src/ commonMain/ commonTest/ shared/ androidApp/ iosApp/ web/
  41. <your platform android code here> <your code shared across all

    targets goes here> src/ commonMain/ commonTest/ shared/ androidMain androidTest androidApp/ iosApp/ web/
  42. <your platform android code here> <your code shared across all

    targets goes here> src/ commonMain/ commonTest/ shared/ androidMain androidTest iosMain/ iosTest/ jsMain/ jsTest/ <your platform JS code goes here> <your platform iOS code here>
  43. Let’s get everything together

  44. build.gradle.kts (:shared) kotlin { sourceSets { val commonMain by getting

    { dependencies { implementation("io.ktor:ktor-client-core:1.4.0") implementation("io.ktor:ktor-client-json:1.4.0") implementation("io.ktor:ktor-client-serialization:1.4.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9") implementation("com.squareup.sqldelight:runtime:1.4.3") implementation("com.russhwolf:multiplatform-settings:0.6.2") } } } }
  45. build.gradle.kts (:shared) kotlin { android() sourceSets { val commonMain by

    getting { dependencies { implementation("io.ktor:ktor-client-core:1.4.0") implementation("io.ktor:ktor-client-json:1.4.0") implementation("io.ktor:ktor-client-serialization:1.4.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9") implementation("com.squareup.sqldelight:runtime:1.4.3") implementation("com.russhwolf:multiplatform-settings:0.6.2") } val androidMain by getting { dependencies { implementation("androidx.preference:preference:1.1.1") implementation("io.ktor:ktor-client-android:1.4.0") implementation("io.ktor:ktor-client-okhttp:1.4.0") implementation("com.squareup.sqldelight:android-driver:1.4.3") } } }
  46. build.gradle.kts (:shared) kotlin { android() val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?:

    false if (onPhone) { iosArm64("ios") } else { iosX64("ios") } cocoapods { summary = "Some description for a Kotlin/Native module" homepage = "Link to a Kotlin/Native module homepage" frameworkName = "shared" ios.deploymentTarget = "13.2" } sourceSets { val commonMain by getting { dependencies { implementation("io.ktor:ktor-client-core:1.4.0") implementation("io.ktor:ktor-client-json:1.4.0") implementation("io.ktor:ktor-client-serialization:1.4.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
  47. build.gradle.kts (:shared) kotlin { android() val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?:

    false if (onPhone) { iosArm64("ios") } else { iosX64("ios") } cocoapods { summary = "Some description for a Kotlin/Native module" homepage = "Link to a Kotlin/Native module homepage" frameworkName = "shared" ios.deploymentTarget = "13.2" } sourceSets { val commonMain by getting { dependencies { implementation("io.ktor:ktor-client-core:1.4.0") implementation("io.ktor:ktor-client-json:1.4.0") implementation("io.ktor:ktor-client-serialization:1.4.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9") implementation("com.squareup.sqldelight:runtime:1.4.3") implementation("com.russhwolf:multiplatform-settings:0.6.2") } val androidMain by getting { dependencies { implementation("androidx.preference:preference:1.1.1") implementation("io.ktor:ktor-client-android:1.4.0") implementation("io.ktor:ktor-client-okhttp:1.4.0") implementation("com.squareup.sqldelight:android-driver:1.4.3") } } } val iosMain by getting { dependencies { implementation("io.ktor:ktor-client-ios:1.4.0") implementation("com.squareup.sqldelight:native-driver:1.4.3") } } } }
  48. build.gradle.kts (:shared) kotlin { android() val onPhone = System.getenv("SDK_NAME")?.startsWith("iphoneos") ?:

    false if (onPhone) { iosArm64("ios") } else { iosX64("ios") } cocoapods { summary = "Some description for a Kotlin/Native module" homepage = "Link to a Kotlin/Native module homepage" frameworkName = "shared" ios.deploymentTarget = "13.2" } js { browser { } } sourceSets { val commonMain by getting { dependencies { implementation("io.ktor:ktor-client-core:1.4.0") implementation("io.ktor:ktor-client-json:1.4.0") implementation("io.ktor:ktor-client-serialization:1.4.1") implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:1.0.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9") implementation("com.squareup.sqldelight:runtime:1.4.3") implementation("com.russhwolf:multiplatform-settings:0.6.2") } val androidMain by getting { dependencies { implementation("androidx.preference:preference:1.1.1") implementation("io.ktor:ktor-client-android:1.4.0") implementation("io.ktor:ktor-client-okhttp:1.4.0") implementation("com.squareup.sqldelight:android-driver:1.4.3") } } } val iosMain by getting { dependencies { implementation("io.ktor:ktor-client-ios:1.4.0") implementation("com.squareup.sqldelight:native-driver:1.4.3") } } val jsMain by getting { dependencies { implementation("io.ktor:ktor-client-js:1.4.0") } } } }
  49. *.kt common expect JVM actual *.kt, *.java, *.jar Native actual

    *.kt, *C, Swift, Framework JS actual *.kt, *.js, NPM
  50. dev-reactions/god-mode.gif

  51. None
  52. None
  53. None
  54. - User interface - RecyclerView and more (Android) - UITableViewController

    and more (iOS) - Depends on the framework used (web) - Network request - Parse response objects - Store on local database - Use system preferences Project structure
  55. android iOS Room CoreData Retrofit Alamofire GSON/ Moshi JSONSerialization MVP,

    MVVM, MVI MVVM, ELM RxJava RxSwift Tests Tests Activity UIViewController RecyclerView UITableView web There are a lot of different frameworks here… - redux - Fetch - json-api-serializer …
  56. SQLDelight ktor kotlinx.serialization MVP kotlinx.coroutines kotlin.test Activity UIViewController RecyclerView UITableView

    android iOS web <web stuff>
  57. multiplatform ktor SQLDelight model kotlinx.serialization GetConferences SQLDriver dispatcher view presenter

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

    presenter
  59. Network class ConferencesAPI() { private val client = HttpClient() suspend

    fun fetchConfs() = client.get<String>("$URL") src/commonMain/data/ConferencesAPI.kt ktor multiplatform network database android parser view platform specific presenter presenter domain model
  60. src/commonMain/domain/model/Conference.kt model @Serializable data class Conference ( val name: String,

    val city: String, val country: String, val date: String, val logo: String, val website: String, val status: String) multiplatform network database android parser view platform specific presenter presenter domain model
  61. domain class GetConferences(val api: ConferencesAPI) { suspend operator fun invoke(

    onSuccess: (List<Conference>) "-> Unit, onFailure: (Exception) "-> Unit) { val result = api.fetchConfs() val confs = Json.decodeFromString<List<Conference">>(result) coroutineScope { onSuccess(confs) } ""... } multiplatform network database android parser view platform specific presenter presenter domain model src/commonMain/domain/GetConferences.kt
  62. domain class GetConferences(val api: ConferencesAPI) { suspend operator fun invoke(

    onSuccess: (List<Conference>) "-> Unit, onFailure: (Exception) "-> Unit) { val result = api.fetchConfs() val confs = Json.decodeFromString<List<Conference">>(result) coroutineScope { onSuccess(confs) } ""... } multiplatform network database android parser view platform specific presenter presenter domain model src/commonMain/domain/GetConferences.kt
  63. domain class GetConferences(val api: ConferencesAPI) { suspend operator fun invoke(

    onSuccess: (List<Conference>) "-> Unit, onFailure: (Exception) "-> Unit) { val result = api.fetchConfs() val confs = Json.decodeFromString<List<Conference">>(result) coroutineScope { onSuccess(confs) } ""... } multiplatform network database android parser view platform specific presenter presenter domain model src/commonMain/domain/GetConferences.kt
  64. domain class GetConferences(val api: ConferencesAPI) { suspend operator fun invoke(

    onSuccess: (List<Conference>) "-> Unit, onFailure: (Exception) "-> Unit) { val result = api.fetchConfs() val confs = Json.decodeFromString<List<Conference">>(result) coroutineScope { onSuccess(confs) } ""... } multiplatform network database android parser view platform specific presenter presenter domain model src/commonMain/domain/GetConferences.kt
  65. src/commonMain/presentation/ConfsListPresenter.kt class ConfsListPresenter(val conferences: GetConferences) { lateinit var view: IConferenceData

    fun attachView(currView: IConferenceData) { view = currView fetchConfsList() } … presenter multiplatform network database android parser view platform specific presenter presenter domain model
  66. src/commonMain/presentation/ConfsListPresenter.kt class ConfsListPresenter(val conferences: GetConferences) { lateinit var view: IConferenceData

    fun attachView(currView: IConferenceData) { view = currView fetchConfsList() } … presenter multiplatform network database android parser view platform specific presenter presenter domain model
  67. src/commonMain/presentation/cb/IConferenceData.kt interface IConferenceData { fun onConferenceDataFetched(confs: List<Conference>) fun onConferenceDataFailed(e: Exception)

    } presenter multiplatform network database android parser view platform specific presenter presenter domain model
  68. src/commonMain/presentation/ConfsListPresenter.kt class ConfsListPresenter(val conferences: GetConferences) { lateinit var view: IConferenceData

    fun attachView(currView: IConferenceData) { view = currView fetchConfsList() } private fun fetchConfsList() { PresenterCoroutineScope(coroutineContext).launch { conferences( onSuccess = { view"?.onConferenceDataFetched(it) }, onFailure = { view"?.onConferenceDataFailed(it) }) presenter multiplatform network database android parser view platform specific presenter presenter domain model
  69. object ServiceLocator { private val conferenceAPI by lazy { ConferencesAPI()

    } private val getConferences: GetConferences get() = GetConferences(conferenceAPI) val getConfsPresenter: ConfsListPresenter get() = ConfsListPresenter(getConferences) … presenter multiplatform network database android parser view platform specific presenter presenter domain model src/commonMain/ServiceLocator.kt
  70. object ServiceLocator { private val conferenceAPI by lazy { ConferencesAPI()

    } private val getConferences: GetConferences get() = GetConferences(conferenceAPI) val getConfsPresenter: ConfsListPresenter get() = ConfsListPresenter(getConferences) … presenter multiplatform network database android parser view platform specific presenter presenter domain model src/commonMain/ServiceLocator.kt
  71. src/commonAndroid/presenter/activities/MainActivity.kt android class MainActivity : AppCompatActivity(), IConferencesData { val presenter

    by lazy { ServiceLocator.getConfsPresenter } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) presenter.attachView(this) } override fun onConferenceDataFetched(list: List<Conference>){ setup(list) … multiplatform network database android parser view platform specific presenter presenter domain model
  72. src/commonAndroid/presenter/activities/MainActivity.kt android class MainActivity : AppCompatActivity(), IConferencesData { val presenter

    by lazy { ServiceLocator.getConfsPresenter } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) presenter.attachView(this) } override fun onConferenceDataFetched(list: List<Conference>){ setup(list) … multiplatform network database android parser view platform specific presenter presenter domain model
  73. @cafonsomota > Configure project :shared Kotlin Multiplatform Projects are an

    Alpha feature. cmota.github.io/kmp-codelabs
  74. - 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 alpha - Lookout for plugins/ libraries updates
  75. - 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 - Cross platform solutions 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
  76. - 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
  77. - Ktor (networking) - Kotlinx.Coroutines - Kotlinx.Serialization - SqlDelight (database)

    - Multiplatform Settings - Stately (state management) (libraries) More information
  78. - .../JetBrains/kotlinconf-app - .../touchlab/DroidconKotlin - …/touchlab/KaMPKit - .../wojtek-kalicinski/sudoku-android - .../joreilly/PeopleInSpace

    - .../jonnyzzz/kotlin-game-of-life - .../MarcinMoskala/WorkoutMPP (cool projects to follow at GitHub) More information
  79. - Kotlin Slack - Kotlin by: https://jakewharton.com/presentations/ https://touchlab.co/kamp-kit-touchlab/ https://kmp.icerock.dev/ -

    Kotlin Conf 2019 videos https://www.youtube.com/watch?v=0xKTM0A8gdI - Kotlin 1.4 Online Event https://www.youtube.com/watch?v=PW-jkOLucjM https://www.youtube.com/watch?v=5QPPZV04-50 https://www.youtube.com/watch?v=KObxmllDzPY (useful links) More information
  80. @cafonsomota cmota.github.io/kmp-codelabs/ cmota.github.io/kmp-codelabs

  81. @cafonsomota speakerdeck.com/cmota/a-multiplatform-triathlon github.com/cmota/kmp-a-multiplatform-triathlon cmota.github.io/kmp-codelabs