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

Code reuse with Kotlin Multiplatform

Code reuse with Kotlin Multiplatform

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/hello-world-multiplatform
- https://github.com/felipehjcosta/marvel-client
- https://github.com/felipehjcosta/chat-app

References:
- https://touchlab.co/future-cross-platform-native/
- https://www.youtube.com/watch?v=c8IkWGmlcNE
- https://www.youtube.com/watch?v=Dul17VSiejo
- https://www.youtube.com/watch?v=nw6YTfEyfO0
- https://www.youtube.com/watch?v=pcwIs749KSE
- https://medium.com/xebia-france/understanding-the-basics-of-multiplatform-projects-in-kotlin-1-3-1221689b3084

Felipe Costa

July 19, 2019
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.31' } repositories { mavenCentral()

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

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

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

    } kotlin { sourceSets { commonMain { dependencies { implementation kotlin('stdlib-common') } } } } Build.gradle
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. kotlin { jvm() js() macosX64("macos") { binaries { sharedLib("sharedLib") }

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

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

    } sourceSets { commonMain { /* */ } } } Build.gradle
  19. 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 */
  20. kotlin { jvm() js() macosX64("macos") { binaries { framework("framework") }

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

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

    } sourceSets { commonMain { /* */ } } } Build.gradle
  23. 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
  24. object Platform { val name: String } fun hello(): String

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    */ } commonTest { /* */ } jvmMain { /* */ } jvmTest { /* */ } jsMain { /* */ } jsTest { /* */ } macosMain { /* */ } macosTest { /* */ } } } build.gradle
  53. 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
  54. expect class Sample() { fun checkMe(): Int } object Platform

    { val name: String } fun hello(): String = "Hello from ${Platform.name}" Common
  55. 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
  56. 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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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) { /* */ } inner class WebSocketDelegate : NSObject(), SRWebSocketDelegateProtocol { override fun webSocket(webSocket: SRWebSocket?, didReceiveMessage: Any?) { /* */ } } } iOS
  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) { /* */ } inner class WebSocketDelegate : NSObject(), SRWebSocketDelegateProtocol { override fun webSocket(webSocket: SRWebSocket?, didReceiveMessage: Any?) { /* */ } } } iOS
  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) { /* */ } inner class WebSocketDelegate : NSObject(), SRWebSocketDelegateProtocol { override fun webSocket(webSocket: SRWebSocket?, didReceiveMessage: Any?) { /* */ } } } iOS