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

Expecting fun with Kotlin Multiplatform Mobile

Felipe Costa
December 02, 2020

Expecting fun with Kotlin Multiplatform Mobile

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 Android and iOS.

References:
- https://kotlinlang.org/lp/mobile/
- https://github.com/felipehjcosta/chat-app

Delivered in the following events

- TDC 2020 Porto Alegre, December 2 to 4, 2020

Felipe Costa

December 02, 2020
Tweet

More Decks by Felipe Costa

Other Decks in Programming

Transcript

  1. Felipe Costa Software engineering manager @ OLX Brasil Software professional

    passionate about mobile and open source technologies with over 10 years of experience as a software engineer. Kotlin enthusiast since 2016.
  2. 2011 Jetbrains unveiled Project Kotlin 2012 Jetbrains open sourced the

    project 2015 Using Project Kotlin for Android (Square) 2016 Kotlin 1.0 (first officially stable release) 2017 First-class support for Kotlin on Android
  3. 2017 Kotlin 1.2 (Kotlin Multiplatform) 2018 Kotlin 1.3 (coroutines) 2019

    Preferred language for Android app developers 2020 Kotlin 1.4 (support for Apple's platforms) 2020 Kotlin Multiplatform Mobile Goes Alpha
  4. 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
  5. 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
  6. Flutter Input Process Output App type Native Language Other Language:

    Dart Cross-Compile Include Runtime (Interpreter, VM, Libraries) Native Code Native Language Native Web
  7. 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
  8. plugins { id 'kotlin-multiplatform' version ‘1.4.0' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation “io.ktor:ktor-client-core:1.4.0” } } } } Build.gradle
  9. plugins { id 'kotlin-multiplatform' version ‘1.4.0' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation "io.ktor:ktor-client-core:1.4.0" } } } } Build.gradle
  10. plugins { id 'kotlin-multiplatform' version '1.4.0' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation “io.ktor:ktor-client-core:1.4.0” } } } } Build.gradle
  11. plugins { id 'kotlin-multiplatform' version '1.4.0' } repositories { mavenCentral()

    } kotlin { sourceSets { commonMain { dependencies { implementation “io.ktor:ktor-client-core:1.4.0” } } } } Build.gradle
  12. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } } } Build.gradle
  13. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } } } Build.gradle
  14. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } } } Build.gradle
  15. 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"; } } Android
  16. iOS 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
  17. object Platform { val name: String } fun hello(): String

    = "Hello from ${Platform.name}" Common
  18. object Platform { val name: String = “Android" } fun

    hello(): String = "Hello from ${Platform.name}" Android
  19. object Platform { val name: String = “iOS" } fun

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

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

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

    String = "Hello from ${Platform.name}" Common
  23. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } androidMain { dependencies { /* */ } } } } build.gradle
  24. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } androidMain { dependencies { /* */ } } } } build.gradle
  25. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } androidMain { dependencies { /* */ } } } } build.gradle
  26. actual object Platform { actual val name: String = "Android"

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

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

    } fun hello(): String = "Hello from Android” Android
  29. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } androidMain { /* */ } iosArm64Main { } } } build.gradle
  30. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } androidMain { /* */ } iosArm64Main { } } } build.gradle
  31. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } androidMain { /* */ } iosArm64Main { } } } build.gradle
  32. actual object Platform { actual val name: String = "iOS"

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

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

    } fun hello(): String = "Hello from iOS” iOS
  35. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } commonTest { /* */ } androidMain { /* */ } androidTest { /* */ } iosArm64Main { /* */ } iosArm64Test { /* */ } } } build.gradle
  36. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } commonTest { /* */ } androidMain { /* */ } androidTest { /* */ } iosArm64Main { /* */ } iosArm64Test { /* */ } } } build.gradle
  37. kotlin { android() iosArm64 { binaries { framework(“shared”) } }

    sourceSets { commonMain { /* */ } commonTest { /* */ } androidMain { /* */ } androidTest { /* */ } iosArm64Main { /* */ } iosArm64Test { /* */ } } } build.gradle
  38. kotlin { /* */ sourceSets { /* */ commonTest {

    dependencies { implementation kotlin('test-common') implementation kotlin('test-annotations-common') } } androidTest { dependencies { implementation kotlin('test') implementation kotlin('test-junit') } } iosArm64Test { } } } build.gradle
  39. expect class Sample() { fun checkMe(): Int } object Platform

    { val name: String } fun hello(): String = "Hello from ${Platform.name}" Common
  40. expect class PlatformSocket(url: String) { fun openSocket(listener: PlatformSocketListener) fun closeSocket(code:

    Int, reason: String) fun sendMessage(msg: String) } interface PlatformSocketListener { fun onOpen() fun onFailure(t: Throwable) fun onMessage(msg: String) fun onClosing(code: Int, reason: String) fun onClosed(code: Int, reason: String) } Common
  41. expect class PlatformSocket(url: String) { fun openSocket(listener: PlatformSocketListener) fun closeSocket(code:

    Int, reason: String) fun sendMessage(msg: String) } interface PlatformSocketListener { fun onOpen() fun onFailure(t: Throwable) fun onMessage(msg: String) fun onClosing(code: Int, reason: String) fun onClosed(code: Int, reason: String) } Common
  42. expect class PlatformSocket(url: String) { fun openSocket(listener: PlatformSocketListener) fun closeSocket(code:

    Int, reason: String) fun sendMessage(msg: String) } interface PlatformSocketListener { fun onOpen() fun onFailure(t: Throwable) fun onMessage(msg: String) fun onClosing(code: Int, reason: String) fun onClosed(code: Int, reason: String) } Common
  43. import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket actual class

    PlatformSocket actual constructor(url: String) { private val socketEndpoint = url private var webSocket: WebSocket? = null actual fun openSocket(listener: PlatformSocketListener) { val socketRequest = Request.Builder().url(socketEndpoint).build() val webClient = OkHttpClient().newBuilder().build() webSocket = webClient.newWebSocket( socketRequest, object : okhttp3.WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) = listener.onOpen() override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) = listener.onFailure(t) override fun onMessage(webSocket: WebSocket, text: String) = listener.onMessage(text) override fun onClosing(webSocket: WebSocket, code: Int, reason: String) = listener.onClosing(code, reason) override fun onClosed(webSocket: WebSocket, code: Int, reason: String) = listener.onClosed(code, reason) } ) } actual fun closeSocket(code: Int, reason: String) { webSocket?.close(code, reason) webSocket = null } actual fun sendMessage(msg: String) { webSocket?.send(msg) } } Android
  44. import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket actual class

    PlatformSocket actual constructor(url: String) { private val socketEndpoint = url private var webSocket: WebSocket? = null actual fun openSocket(listener: PlatformSocketListener) { val socketRequest = Request.Builder().url(socketEndpoint).build() val webClient = OkHttpClient().newBuilder().build() webSocket = webClient.newWebSocket( socketRequest, object : okhttp3.WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) = listener.onOpen() override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) = listener.onFailure(t) override fun onMessage(webSocket: WebSocket, text: String) = listener.onMessage(text) override fun onClosing(webSocket: WebSocket, code: Int, reason: String) = listener.onClosing(code, reason) override fun onClosed(webSocket: WebSocket, code: Int, reason: String) = listener.onClosed(code, reason) } ) } actual fun closeSocket(code: Int, reason: String) { webSocket?.close(code, reason) webSocket = null } actual fun sendMessage(msg: String) { webSocket?.send(msg) } } Android
  45. import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response import okhttp3.WebSocket actual class

    PlatformSocket actual constructor(url: String) { private val socketEndpoint = url private var webSocket: WebSocket? = null actual fun openSocket(listener: PlatformSocketListener) { val socketRequest = Request.Builder().url(socketEndpoint).build() val webClient = OkHttpClient().newBuilder().build() webSocket = webClient.newWebSocket( socketRequest, object : okhttp3.WebSocketListener() { override fun onOpen(webSocket: WebSocket, response: Response) = listener.onOpen() override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) = listener.onFailure(t) override fun onMessage(webSocket: WebSocket, text: String) = listener.onMessage(text) override fun onClosing(webSocket: WebSocket, code: Int, reason: String) = listener.onClosing(code, reason) override fun onClosed(webSocket: WebSocket, code: Int, reason: String) = listener.onClosed(code, reason) } ) } actual fun closeSocket(code: Int, reason: String) { webSocket?.close(code, reason) webSocket = null } actual fun sendMessage(msg: String) { webSocket?.send(msg) } } Android
  46. import platform.Foundation.* import platform.darwin.NSObject actual class PlatformSocket actual constructor(url: String)

    { private val socketEndpoint = NSURL.URLWithString(url)!! private var webSocket: NSURLSessionWebSocketTask? = null actual fun openSocket(listener: PlatformSocketListener) { val urlSession = NSURLSession.sessionWithConfiguration( configuration = NSURLSessionConfiguration.defaultSessionConfiguration(), delegate = object : NSObject(), NSURLSessionWebSocketDelegateProtocol { override fun URLSession(session: NSURLSession, webSocketTask: NSURLSessionWebSocketTask, didOpenWithProtocol: String?) { listener.onOpen() } override fun URLSession(session: NSURLSession, webSocketTask: NSURLSessionWebSocketTask, didCloseWithCode: NSURLSessionWebSocketCloseCode, reason: NSData?) { listener.onClosed(didCloseWithCode.toInt(), reason.toString()) } }, delegateQueue = NSOperationQueue.currentQueue() ) webSocket = urlSession.webSocketTaskWithURL(socketEndpoint) listenMessages(listener) webSocket?.resume() } private fun listenMessages(listener: PlatformSocketListener) { webSocket?.receiveMessageWithCompletionHandler { message, nsError -> when { nsError != null -> { listener.onFailure(Throwable(nsError.description)) } message != null -> { message.string?.let { listener.onMessage(it) } } } listenMessages(listener) } } actual fun closeSocket(code: Int, reason: String) { webSocket?.cancelWithCloseCode(code.toLong(), null) webSocket = null } actual fun sendMessage(msg: String) { val message = NSURLSessionWebSocketMessage(msg) webSocket?.sendMessage(message) { err -> err?.let { println("send $msg error: $it") } } } } iOS
  47. import platform.Foundation.* import platform.darwin.NSObject actual class PlatformSocket actual constructor(url: String)

    { private val socketEndpoint = NSURL.URLWithString(url)!! private var webSocket: NSURLSessionWebSocketTask? = null actual fun openSocket(listener: PlatformSocketListener) { val urlSession = NSURLSession.sessionWithConfiguration( configuration = NSURLSessionConfiguration.defaultSessionConfiguration(), delegate = object : NSObject(), NSURLSessionWebSocketDelegateProtocol { override fun URLSession(session: NSURLSession, webSocketTask: NSURLSessionWebSocketTask, didOpenWithProtocol: String?) { listener.onOpen() } override fun URLSession(session: NSURLSession, webSocketTask: NSURLSessionWebSocketTask, didCloseWithCode: NSURLSessionWebSocketCloseCode, reason: NSData?) { listener.onClosed(didCloseWithCode.toInt(), reason.toString()) } }, delegateQueue = NSOperationQueue.currentQueue() ) webSocket = urlSession.webSocketTaskWithURL(socketEndpoint) listenMessages(listener) webSocket?.resume() } private fun listenMessages(listener: PlatformSocketListener) { webSocket?.receiveMessageWithCompletionHandler { message, nsError -> when { nsError != null -> { listener.onFailure(Throwable(nsError.description)) } message != null -> { message.string?.let { listener.onMessage(it) } } } listenMessages(listener) } } actual fun closeSocket(code: Int, reason: String) { webSocket?.cancelWithCloseCode(code.toLong(), null) webSocket = null } actual fun sendMessage(msg: String) { val message = NSURLSessionWebSocketMessage(msg) webSocket?.sendMessage(message) { err -> err?.let { println("send $msg error: $it") } } } } iOS
  48. import platform.Foundation.* import platform.darwin.NSObject actual class PlatformSocket actual constructor(url: String)

    { private val socketEndpoint = NSURL.URLWithString(url)!! private var webSocket: NSURLSessionWebSocketTask? = null actual fun openSocket(listener: PlatformSocketListener) { val urlSession = NSURLSession.sessionWithConfiguration( configuration = NSURLSessionConfiguration.defaultSessionConfiguration(), delegate = object : NSObject(), NSURLSessionWebSocketDelegateProtocol { override fun URLSession(session: NSURLSession, webSocketTask: NSURLSessionWebSocketTask, didOpenWithProtocol: String?) { listener.onOpen() } override fun URLSession(session: NSURLSession, webSocketTask: NSURLSessionWebSocketTask, didCloseWithCode: NSURLSessionWebSocketCloseCode, reason: NSData?) { listener.onClosed(didCloseWithCode.toInt(), reason.toString()) } }, delegateQueue = NSOperationQueue.currentQueue() ) webSocket = urlSession.webSocketTaskWithURL(socketEndpoint) listenMessages(listener) webSocket?.resume() } private fun listenMessages(listener: PlatformSocketListener) { webSocket?.receiveMessageWithCompletionHandler { message, nsError -> when { nsError != null -> { listener.onFailure(Throwable(nsError.description)) } message != null -> { message.string?.let { listener.onMessage(it) } } } listenMessages(listener) } } actual fun closeSocket(code: Int, reason: String) { webSocket?.cancelWithCloseCode(code.toLong(), null) webSocket = null } actual fun sendMessage(msg: String) { val message = NSURLSessionWebSocketMessage(msg) webSocket?.sendMessage(message) { err -> err?.let { println("send $msg error: $it") } } } } iOS