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

A Multiplatform Triathlon

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.

cmota

October 17, 2020
Tweet

More Decks by cmota

Other Decks in Programming

Transcript

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

    Author @rwenderlich and @opinioesonline Podcaster wannabe Loves travel, photography and running @cafonsomota
  2. - 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 )
  3. - 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
  4. - 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
  5. - 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
  6. - 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…
  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 Cross-multiplatform nope, UI is developed natively yes, the UI UI is developed natively always arguable, but you’re leaving UI to native, so…
  8. - 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…
  9. - 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
  10. (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
  11. - 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
  12. network network parser parser model model presentation presentation model parser

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

    model parser network presentation model parser network presentation view view view web iOS android
  14. 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
  15. import android.os.Build.MANUFACTURER actual object Platform { actual val name: String

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

    = $MANUFACTURER } shared/src/androidMain/sample/Platform.kt Platform-dependent code
  17. 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
  18. 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
  19. androidMain androidTest <your code shared across all targets goes here>

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

    targets goes here> src/ commonMain/ commonTest/ shared/ androidMain androidTest androidApp/ iosApp/ web/
  21. <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>
  22. 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") } } } }
  23. 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") } } }
  24. 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")
  25. 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") } } } }
  26. 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") } } } }
  27. *.kt common expect JVM actual *.kt, *.java, *.jar Native actual

    *.kt, *C, Swift, Framework JS actual *.kt, *.js, NPM
  28. - 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
  29. 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 …
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. 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
  42. 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
  43. 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
  44. - 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
  45. - 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
  46. - 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
  47. - Ktor (networking) - Kotlinx.Coroutines - Kotlinx.Serialization - SqlDelight (database)

    - Multiplatform Settings - Stately (state management) (libraries) More information
  48. - .../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
  49. - 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