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

Code reuse with Kotlin Multiplatform @ KotlinConf Global 2019 - Curitiba

Code reuse with Kotlin Multiplatform @ KotlinConf Global 2019 - Curitiba

This talk aims to introduce the Kotlin Multiplatform concept, with its advantages and disadvantages, and how this concept is different from cross-platform solutions in code reuse between platforms. Finally, the application of this concept will be shown in a simple chat made with kotlin for Web, Android and iOS.

Kotlin Multiplatform samples:
- https://github.com/felipehjcosta/chat-app
- https://github.com/felipehjcosta/hello-world-multiplatform

References:
- https://touchlab.co/future-cross-platform-native/
- https://www.youtube.com/playlist?list=PLQ176FUIyIUY6SKGl3Cj9yeYibBuRr3Hl
- https://www.youtube.com/watch?v=c8IkWGmlcNE
- https://www.youtube.com/watch?v=nw6YTfEyfO0

Felipe Costa

January 25, 2020
Tweet

More Decks by Felipe Costa

Other Decks in Technology

Transcript

  1. Felipe Costa Senior Software engineer @ OLX Brasil Passionate Software

    Craftsman. I have been developing mobile applications since 2011 and enjoying it mostly in Kotlin since 2016.
  2. DRY

  3. Input Process Output App type Native Language: Kotlin & Swift

    & JS Outra linguagem Cross-Compile Inclui Runtime (Interpretador, VM, Bibliotecas) Native Code Native Language: Javascript Native Web Native Web
  4. Input Process Output App type Native Language Other Language: Javascript

    Cross-Compile Include Runtime (Interpreter, VM, Libraries) Native Code + Bundle JS Native Language Native Web React Native
  5. Flutter Input Process Output App type Native Language Other Language:

    Dart Cross-Compile Include Runtime (Interpreter, VM, Libraries) Native Code Native Language Native Web
  6. Kotlin Multiplatform Input Process Output App Type Native Language: Kotlin

    Other Language Cross-Compile Include Runtime (Interpreter, VM, Libraries) Native Code Native Language: Javascript Native Web Android iOS Web
  7. plugins { id 'kotlin-multiplatform' version '1.3.61' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation kotlin('stdlib-common') } } } } Build.gradle
  8. plugins { id 'kotlin-multiplatform' version '1.3.61' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation kotlin('stdlib-common') } } } } Build.gradle
  9. plugins { id 'kotlin-multiplatform' version '1.3.61' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation kotlin('stdlib-common') } } } } Build.gradle
  10. plugins { id 'kotlin-multiplatform' version '1.3.61' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation kotlin('stdlib-common') } } } } Build.gradle
  11. kotlin { jvm() js { browser() nodejs() } sourceSets {

    commonMain { /* */ } } } Build.gradle
  12. kotlin { jvm() js { browser() nodejs() } sourceSets {

    commonMain { /* */ } } } Build.gradle
  13. kotlin { jvm() js { browser() nodejs() } sourceSets {

    commonMain { /* */ } } } Build.gradle
  14. kotlin { jvm() js() // For ARM, should be changed

    to iosArm32 or iosArm64 // For Linux, should be changed to e.g. linuxX64 // For MacOS, should be changed to e.g. macosX64 // For Windows, should be changed to e.g. mingwX64 macosX64("macos") sourceSets { commonMain { /* */ } } } Build.gradle
  15. kotlin { jvm() js() // For ARM, should be changed

    to iosArm32 or iosArm64 // For Linux, should be changed to e.g. linuxX64 // For MacOS, should be changed to e.g. macosX64 // For Windows, should be changed to e.g. mingwX64 macosX64("macos") sourceSets { commonMain { /* */ } } } Build.gradle
  16. kotlin { jvm() js() // For ARM, should be changed

    to iosArm32 or iosArm64 // For Linux, should be changed to e.g. linuxX64 // For MacOS, should be changed to e.g. macosX64 // For Windows, should be changed to e.g. mingwX64 macosX64("macos") sourceSets { commonMain { /* */ } } } Build.gradle
  17. import kotlin.Metadata; import org.jetbrains.annotations.NotNull; @Metadata( mv = {1, 1, 15},

    bv = {1, 0, 3}, k = 2, d1 = {"\u0000\b\n\u0000\n\u0002\u0010\u000e\n\u0000\u001a\u0006\u0010\u0000\u001a\u00020\u0001¨\u0006\u0002"}, d2 = {"hello", "", "Platform module com.example.hello-world-multiplatform.jvmMain including [com.example.hello-world-multiplatform.commonMain]"} ) public final class SampleKt { @NotNull public static final String hello() { return "Hello from Kotlin Multiplatform"; } } JVM
  18. if (typeof kotlin === 'undefined') { throw new Error("Error loading

    module 'hello-world-multiplatform'. Its dependency 'kotlin' was not found. Please, check whether 'kotlin' is loaded prior to 'hello-world-multiplatform'."); } this['hello-world-multiplatform'] = function (_, Kotlin) { 'use strict'; function hello() { return 'Hello from Kotlin Multiplatform'; } var package$sample = _.sample || (_.sample = {}); package$sample.hello = hello; Kotlin.defineModule('hello-world-multiplatform', _); return _; }(typeof this['hello-world-multiplatform'] === 'undefined' ? {} : this['hello-world-multiplatform'], kotlin); JS
  19. kotlin { jvm() js() macosX64("macos") { binaries { sharedLib("SharedLib") }

    } sourceSets { commonMain { /* */ } } } Build.gradle
  20. kotlin { jvm() js() macosX64("macos") { binaries { sharedLib("SharedLib") }

    } sourceSets { commonMain { /* */ } } } Build.gradle
  21. kotlin { jvm() js() macosX64("macos") { binaries { sharedLib("SharedLib") }

    } sourceSets { commonMain { /* */ } } } Build.gradle
  22. MacOS Dynamic Library #ifndef KONAN_LIBSHAREDLIB_H #define KONAN_LIBSHAREDLIB_H #ifdef __cplusplus extern

    "C" { #endif /* typedefes */ typedef struct { /* Service functions. */ /* ... */ /* User functions. */ struct { struct { struct { const char* (*hello)(); } sample; } root; } kotlin; } libsharedLib_ExportedSymbols; extern libsharedLib_ExportedSymbols* libsharedLib_symbols(void); #ifdef __cplusplus } /* extern "C" */ #endif #endif /* KONAN_LIBSHAREDLIB_H */
  23. kotlin { jvm() js() macosX64("macos") { binaries { framework("Framework") }

    } sourceSets { commonMain { /* */ } } } Build.gradle
  24. kotlin { jvm() js() macosX64("macos") { binaries { framework("Framework") }

    } sourceSets { commonMain { /* */ } } } Build.gradle
  25. kotlin { jvm() js() macosX64("macos") { binaries { framework("Framework") }

    } sourceSets { commonMain { /* */ } } } Build.gradle
  26. MacOS Framework #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface KotlinBase : NSObject -

    (instancetype)init __attribute__((unavailable)); + (instancetype)new __attribute__((unavailable)); + (void)initialize __attribute__((objc_requires_super)); @end; @interface KotlinBase (KotlinBaseCopying) <NSCopying> @end; /* More types */ __attribute__((objc_subclassing_restricted)) __attribute__((swift_name("SampleKt"))) @interface FrameworkSampleKt : KotlinBase + (NSString *)hello __attribute__((swift_name("hello()"))); @end; NS_ASSUME_NONNULL_END
  27. object Platform { val name: String } fun hello(): String

    = "Hello from ${Platform.name}" Common
  28. object Platform { val name: String = "JVM" } fun

    hello(): String = "Hello from ${Platform.name}" JVM
  29. object Platform { val name: String = "JS" } fun

    hello(): String = "Hello from ${Platform.name}" JS
  30. object Platform { val name: String = "Native" } fun

    hello(): String = "Hello from ${Platform.name}" Native
  31. Common *.kt Kotlin/JVM *.kt, *.java Kotlin/JS *.kt, *.js Kotlin/Native *.kt,

    *.c *.class *.jar, *.apk *.js binário nativo Android/ NDK iOS Mac Linux Windows Webassembly Others
  32. expect object Platform { val name: String } fun hello():

    String = "Hello from ${Platform.name}" Common
  33. expect object Platform { val name: String } fun hello():

    String = "Hello from ${Platform.name}" Common
  34. expect object Platform { val name: String } fun hello():

    String = "Hello from ${Platform.name}" Common
  35. kotlin { jvm() macosX64("macos") sourceSets { commonMain { /* */

    } jvmMain { dependencies { implementation kotlin('stdlib-jdk8') } } } } build.gradle
  36. kotlin { jvm() macosX64("macos") sourceSets { commonMain { /* */

    } jvmMain { dependencies { implementation kotlin('stdlib-jdk8') } } } } build.gradle
  37. kotlin { jvm() macosX64("macos") sourceSets { commonMain { /* */

    } jvmMain { dependencies { implementation kotlin('stdlib-jdk8') } } } } build.gradle
  38. actual object Platform { actual val name: String = "JVM"

    } fun hello(): String = "Hello from JVM" JVM
  39. actual object Platform { actual val name: String = "JVM"

    } fun hello(): String = "Hello from JVM" JVM
  40. actual object Platform { actual val name: String = "JVM"

    } fun hello(): String = "Hello from JVM" JVM
  41. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } jvmMain { /* */ } jsMain { dependencies { implementation kotlin('stdlib-js') } } } } build.gradle
  42. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } jvmMain { /* */ } jsMain { dependencies { implementation kotlin('stdlib-js') } } } } build.gradle
  43. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } jvmMain { /* */ } jsMain { dependencies { implementation kotlin('stdlib-js') } } } } build.gradle
  44. actual object Platform { actual val name: String = "JS"

    } fun hello(): String = "Hello from JS" JS
  45. actual object Platform { actual val name: String = "JS"

    } fun hello(): String = "Hello from JS" JS
  46. actual object Platform { actual val name: String = "JS"

    } fun hello(): String = "Hello from JS" JS
  47. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } jvmMain { /* */ } jsMain { /* */ } macosMain { } } } build.gradle
  48. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } jvmMain { /* */ } jsMain { /* */ } macosMain { } } } build.gradle
  49. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } jvmMain { /* */ } jsMain { /* */ } macosMain { } } } build.gradle
  50. actual object Platform { actual val name: String = "Native"

    } fun hello(): String = "Hello from Native” Native
  51. actual object Platform { actual val name: String = "Native"

    } fun hello(): String = "Hello from Native” Native
  52. actual object Platform { actual val name: String = "Native"

    } fun hello(): String = "Hello from Native” Native
  53. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } commonTest { /* */ } jvmMain { /* */ } jvmTest { /* */ } jsMain { /* */ } jsTest { /* */ } macosMain { /* */ } macosTest { /* */ } } } build.gradle
  54. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } commonTest { /* */ } jvmMain { /* */ } jvmTest { /* */ } jsMain { /* */ } jsTest { /* */ } macosMain { /* */ } macosTest { /* */ } } } build.gradle
  55. kotlin { jvm() js() macosX64("macos") sourceSets { commonMain { /*

    */ } commonTest { /* */ } jvmMain { /* */ } jvmTest { /* */ } jsMain { /* */ } jsTest { /* */ } macosMain { /* */ } macosTest { /* */ } } } build.gradle
  56. kotlin { /* */ sourceSets { /* */ commonTest {

    dependencies { implementation kotlin('test-common') implementation kotlin('test-annotations-common') } } jvmTest { dependencies { implementation kotlin('test') implementation kotlin('test-junit') } } jsTest { dependencies { implementation kotlin('test-js') } } macosTest { } } } build.gradle
  57. expect class Sample() { fun checkMe(): Int } object Platform

    { val name: String } fun hello(): String = "Hello from ${Platform.name}" Common
  58. Common ChatClient.kt Kotlin/JVM ChatClient.kt Kotlin/JS ChatClient.kt Kotlin/Native ChatClient.kt *.class *.jar,

    *.apk *.js binário nativo Android/ NDK iOS Mac Linux Windows Webassembly Others
  59. internal expect open class ChatClient(url: String) { open fun start()

    open fun send(message: Message) open fun receive(receiveBlock: (Message) -> Unit) open fun onFailure(throwableBlock: (Throwable) -> Unit) } Common
  60. internal expect open class ChatClient(url: String) { open fun start()

    open fun send(message: Message) open fun receive(receiveBlock: (Message) -> Unit) open fun onFailure(throwableBlock: (Throwable) -> Unit) } Common
  61. internal expect open class ChatClient(url: String) { open fun start()

    open fun send(message: Message) open fun receive(receiveBlock: (Message) -> Unit) open fun onFailure(throwableBlock: (Throwable) -> Unit) } Common
  62. internal actual open class ChatClient actual constructor(val url: String) :

    WebSocketListener() { actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } override fun onMessage(webSocket: WebSocket, text: String) { /* */ } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { /* */ } } Android
  63. internal actual open class ChatClient actual constructor(val url: String) :

    WebSocketListener() { actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } override fun onMessage(webSocket: WebSocket, text: String) { /* */ } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { /* */ } } Android
  64. internal actual open class ChatClient actual constructor(val url: String) :

    WebSocketListener() { actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } override fun onMessage(webSocket: WebSocket, text: String) { /* */ } override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) { /* */ } } Android
  65. internal actual open class ChatClient actual constructor(val url: String) {

    actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } } JS
  66. internal actual open class ChatClient actual constructor(val url: String) {

    actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } } JS
  67. internal actual open class ChatClient actual constructor(val url: String) {

    actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } } JS
  68. internal actual open class ChatClient actual constructor(val url: String) {

    actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } } iOS
  69. internal actual open class ChatClient actual constructor(val url: String) {

    actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } } iOS
  70. internal actual open class ChatClient actual constructor(val url: String) {

    actual open fun start() { /* */ } actual open fun send(message: Message) { /* */ } actual open fun receive(receiveBlock: (Message) -> Unit) { /* */ } actual open fun onFailure(throwableBlock: (Throwable) -> Unit) { /* */ } } iOS