Share
Embed
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
Transcript
109 slides
-
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. -
Kotlin @ OLX 0 Python Javascript Java C PHP Kotlin
Objective-C Perl -
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 -
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 -
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 -
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 -
Flutter Input Process Output App type Native Language Other Language:
Dart Cross-Compile Include Runtime (Interpreter, VM, Libraries) Native Code Native Language Native Web -
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 -
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 -
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 -
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 -
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 -
kotlin { android() sourceSets { commonMain { /* */ }
} } Build.gradle -
kotlin { android() sourceSets { commonMain { /* */ }
} } Build.gradle -
kotlin { android() sourceSets { commonMain { /* */ }
} } Build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } } } Build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } } } Build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } } } Build.gradle -
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 -
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 -
object Platform { val name: String } fun hello(): String
= "Hello from ${Platform.name}" Common -
object Platform { val name: String = “Android" } fun
hello(): String = "Hello from ${Platform.name}" Android -
object Platform { val name: String = “iOS" } fun
hello(): String = "Hello from ${Platform.name}" iOS -
Common *.kt Kotlin/JVM *.kt, *.java Kotlin/Native *.kt, *.c *.class *.jar,
*.apk native binary iOS Watch -
expect object Platform { val name: String } fun hello():
String = "Hello from ${Platform.name}" Common -
expect object Platform { val name: String } fun hello():
String = "Hello from ${Platform.name}" Common -
expect object Platform { val name: String } fun hello():
String = "Hello from ${Platform.name}" Common -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } androidMain { dependencies { /* */ } } } } build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } androidMain { dependencies { /* */ } } } } build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } androidMain { dependencies { /* */ } } } } build.gradle -
actual object Platform { actual val name: String = "Android"
} fun hello(): String = "Hello from Android” Android -
actual object Platform { actual val name: String = "Android"
} fun hello(): String = "Hello from Android” Android -
actual object Platform { actual val name: String = "Android"
} fun hello(): String = "Hello from Android” Android -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } androidMain { /* */ } iosArm64Main { } } } build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } androidMain { /* */ } iosArm64Main { } } } build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } androidMain { /* */ } iosArm64Main { } } } build.gradle -
actual object Platform { actual val name: String = "iOS"
} fun hello(): String = "Hello from iOS” iOS -
actual object Platform { actual val name: String = "iOS"
} fun hello(): String = "Hello from iOS” iOS -
actual object Platform { actual val name: String = "iOS"
} fun hello(): String = "Hello from iOS” iOS -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } commonTest { /* */ } androidMain { /* */ } androidTest { /* */ } iosArm64Main { /* */ } iosArm64Test { /* */ } } } build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } commonTest { /* */ } androidMain { /* */ } androidTest { /* */ } iosArm64Main { /* */ } iosArm64Test { /* */ } } } build.gradle -
kotlin { android() iosArm64 { binaries { framework(“shared”) } }
sourceSets { commonMain { /* */ } commonTest { /* */ } androidMain { /* */ } androidTest { /* */ } iosArm64Main { /* */ } iosArm64Test { /* */ } } } build.gradle -
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 -
import kotlin.test.Test import kotlin.test.assertTrue class SampleTestsAndroid { @Test fun testHello()
{ assertTrue(“Android" in hello()) } } Android -
import kotlin.test.Test import kotlin.test.assertTrue class SampleTestsIOS { @Test fun testHello()
{ assertTrue(“iOS" in hello()) } } iOS -
expect class Sample() { fun checkMe(): Int } object Platform
{ val name: String } fun hello(): String = "Hello from ${Platform.name}" Common -
import kotlin.test.Test import kotlin.test.assertTrue class SampleTests { @Test fun testMe()
{ assertTrue(Sample().checkMe() > 0) } } Common -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
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